/*
 * OpenClonk, http://www.openclonk.org
 *
 * Copyright (c) 1998-2000  Matthes Bender
 * Copyright (c) 2013  Sven Eberhardt
 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
 *
 * Portions might be copyrighted by other authors who have contributed
 * to OpenClonk.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 * See isc_license.txt for full license and disclaimer.
 *
 * "Clonk" is a registered trademark of Matthes Bender.
 * See clonk_trademark_license.txt for full license.
 */

/* Handles scripted map creation */

#ifndef INC_C4MapScript
#define INC_C4MapScript

#include <C4ComponentHost.h>
#include <C4Aul.h>
#include <C4ScriptHost.h>
#include <C4Rect.h>
#include <CSurface8.h>
#include <C4Landscape.h>

// mattex masks: Array of bools for each possible material-texture index
class C4MapScriptMatTexMask
{
	std::vector<bool> mask; // vector of size 256: true means pixel color is let through; false means it is blocked

	void UnmaskSpec(C4String *spec);
public:
	C4MapScriptMatTexMask() : mask(256,false) { }
	C4MapScriptMatTexMask(const C4Value &spec) : mask(256,false) { Init(spec); }
	void Init(const C4Value &spec);

	bool operator()(uint8_t mattex) const { return mask[mattex]; }
};

// algorithms may be either indicator functions (int,int)->bool that tell whether a map pixel should be
// set (to be used in C4MapScriptLayer::Fill) or functions (int,int)->int that tell which material should
// be set (to be used in Fill or C4MapScriptLayer::Blit).
class C4MapScriptAlgo
{
protected:
	bool GetXYProps(const C4PropList *props, C4PropertyName k, int32_t *out_xy, bool zero_defaults);
public:
	virtual uint8_t operator () (int32_t x, int32_t y) const = 0;
	virtual ~C4MapScriptAlgo() {}
};

// List of possible algorithms. Also registered as script constants in void C4MapScriptHost::InitFunctionMap.
enum C4MapScriptAlgoType
{
	MAPALGO_None       = 0,

	MAPALGO_Layer      = 1,

	MAPALGO_RndChecker = 10,

	MAPALGO_Rect       = 20,
	MAPALGO_Ellipsis   = 21,
	MAPALGO_Polygon    = 22,

	MAPALGO_And        = 30,
	MAPALGO_Or         = 31,
	MAPALGO_Not        = 32,
	MAPALGO_Xor        = 33,

	MAPALGO_Offset     = 40,
	MAPALGO_Scale      = 41,
	MAPALGO_Rotate     = 42,
	MAPALGO_Turbulence = 43,

	MAPALGO_Border     = 50,
	MAPALGO_Filter     = 51,
};

// MAPALGO_Layer: Just query pixel in layer. Pixels outside the layer range are zero.
class C4MapScriptAlgoLayer : public C4MapScriptAlgo
{
	const class C4MapScriptLayer *layer;
public:
	C4MapScriptAlgoLayer(const C4MapScriptLayer *layer) : layer(layer) {}
	C4MapScriptAlgoLayer(const C4PropList *props);

	virtual uint8_t operator () (int32_t x, int32_t y) const;
};

// MAPALGO_RndChecker: checkerboard on which areas are randomly set or unset
class C4MapScriptAlgoRndChecker : public C4MapScriptAlgo
{
	int32_t seed, set_percentage, checker_wdt, checker_hgt;
	bool is_fixed_offset;
public:
	C4MapScriptAlgoRndChecker(const C4PropList *props);

	virtual uint8_t operator () (int32_t x, int32_t y) const;
};

// MAPALGO_Rect: 1 for pixels contained in rect, 0 otherwise
class C4MapScriptAlgoRect : public C4MapScriptAlgo
{
	C4Rect rect;
public:
	C4MapScriptAlgoRect(const C4PropList *props);

	virtual uint8_t operator () (int32_t x, int32_t y) const;
};

// MAPALGO_Ellipsis: 1 for pixels within ellipsis, 0 otherwise
class C4MapScriptAlgoEllipsis : public C4MapScriptAlgo
{
	int32_t cx,cy;
	int32_t wdt,hgt;
public:
	C4MapScriptAlgoEllipsis(const C4PropList *props);

	virtual uint8_t operator () (int32_t x, int32_t y) const;
};

// MAPALGO_Polygon: 1 for pixels within polygon or on border, 0 otherwise
class C4MapScriptAlgoPolygon : public C4MapScriptAlgo
{
	struct Pt { int32_t x,y; };
	std::vector<Pt> poly;
	int32_t wdt;
	bool empty; // don't fill inside of polygon
	bool open; // don't draw closing segment. only valid if empty=true
public:
	C4MapScriptAlgoPolygon(const C4PropList *props);

	virtual uint8_t operator () (int32_t x, int32_t y) const;
};

// base class for algo that takes one or more operands
class C4MapScriptAlgoModifier : public C4MapScriptAlgo
{
protected:
	std::vector<C4MapScriptAlgo *> operands;
public:
	C4MapScriptAlgoModifier(const C4PropList *props, int32_t min_ops=0, int32_t max_ops=0);
	virtual ~C4MapScriptAlgoModifier() { Clear(); }
	void Clear();
};

// MAPALGO_And: 0 if any of the operands is 0. Otherwise, returns value of last operand.
class C4MapScriptAlgoAnd : public C4MapScriptAlgoModifier
{
public:
	C4MapScriptAlgoAnd(const C4PropList *props) : C4MapScriptAlgoModifier(props) { }

	virtual uint8_t operator () (int32_t x, int32_t y) const;
};

// MAPALGO_Or: First nonzero operand
class C4MapScriptAlgoOr : public C4MapScriptAlgoModifier
{
public:
	C4MapScriptAlgoOr(const C4PropList *props) : C4MapScriptAlgoModifier(props) { }

	virtual uint8_t operator () (int32_t x, int32_t y) const;
};

// MAPALGO_Not: 1 if operand is 0, 0 otherwise.
class C4MapScriptAlgoNot : public C4MapScriptAlgoModifier
{
public:
	C4MapScriptAlgoNot(const C4PropList *props) : C4MapScriptAlgoModifier(props,1,1) { }

	virtual uint8_t operator () (int32_t x, int32_t y) const;
};

// MAPALGO_Xor: If exactly one of the two operands is nonzero, return it. Otherwise, return zero.
class C4MapScriptAlgoXor : public C4MapScriptAlgoModifier
{
public:
	C4MapScriptAlgoXor(const C4PropList *props) : C4MapScriptAlgoModifier(props,2,2) { }

	virtual uint8_t operator () (int32_t x, int32_t y) const;
};

// MAPALGO_Offset: Base layer shifted by ox,oy
class C4MapScriptAlgoOffset : public C4MapScriptAlgoModifier
{
	int32_t ox,oy;
public:
	C4MapScriptAlgoOffset(const C4PropList *props);

	virtual uint8_t operator () (int32_t x, int32_t y) const;
};

// MAPALGO_Scale: Base layer scaled by sx,sy percent from fixed point cx,cy
class C4MapScriptAlgoScale : public C4MapScriptAlgoModifier
{
	int32_t sx,sy,cx,cy;
public:
	C4MapScriptAlgoScale(const C4PropList *props);

	virtual uint8_t operator () (int32_t x, int32_t y) const;
};

// MAPALGO_Rotate: Base layer rotated by angle r around point ox,oy
class C4MapScriptAlgoRotate : public C4MapScriptAlgoModifier
{
	int32_t sr,cr,ox,oy;
	enum {Precision=1000};
public:
	C4MapScriptAlgoRotate(const C4PropList *props);

	virtual uint8_t operator () (int32_t x, int32_t y) const;
};

// MAPALGO_Turbulence: move by a random offset iterations times
class C4MapScriptAlgoTurbulence : public C4MapScriptAlgoModifier
{
	int32_t amp[2], scale[2], seed, iterations;
public:
	C4MapScriptAlgoTurbulence(const C4PropList *props);

	virtual uint8_t operator () (int32_t x, int32_t y) const;
};

// MAPALGO_Border: Border of operand layer
class C4MapScriptAlgoBorder : public C4MapScriptAlgoModifier
{
	int32_t left[2], top[2], right[2], bottom[2];
	void ResolveBorderProps(int32_t *p);
public:
	C4MapScriptAlgoBorder(const C4PropList *props);

	virtual uint8_t operator () (int32_t x, int32_t y) const;
};

// MAPALGO_Filter: Original color of operand if it's marked to go through filter. 0 otherwise.
class C4MapScriptAlgoFilter : public C4MapScriptAlgoModifier
{
	C4MapScriptMatTexMask filter;
public:
	C4MapScriptAlgoFilter(const C4PropList *props);

	virtual uint8_t operator () (int32_t x, int32_t y) const;
};

// layer of a script-controlled map
// IFT can be used to mark Sky
class C4MapScriptLayer : public C4PropListNumbered
{
	CSurface8 *surface;
	bool surface_owned;
protected:
	class C4MapScriptMap *map;

public:
	C4MapScriptLayer(C4PropList *prototype, C4MapScriptMap *map);
	virtual ~C4MapScriptLayer() { ClearSurface(); }
	virtual C4MapScriptLayer * GetMapScriptLayer() { return this; }
	class C4MapScriptMap *GetMap() { return map; }

	// Surface management
	bool CreateSurface(int32_t wdt, int32_t hgt);
	void ClearSurface();
	CSurface8 *ReleaseSurface() { surface_owned=false; return surface; } // return surface and mark it as not owned by self
	void SetSurface(CSurface8 *s) { ClearSurface(); surface=s; UpdateSurfaceSize(); }
	bool HasSurface() const { return surface && surface->Bits; }

	// Size management
	void UpdateSurfaceSize(); // updates width and height properties by current surface
	C4Rect GetBounds () const;
	int32_t GetWdt() const { return surface ? surface->Wdt : 0; }
	int32_t GetHgt() const { return surface ? surface->Hgt : 0; }

	// Pixel functions
	uint8_t GetPix(int32_t x, int32_t y, uint8_t outside_col) const { return (!HasSurface()||x<0||y<0||x>=surface->Wdt||y>=surface->Hgt) ? outside_col : surface->_GetPix(x,y); }
	bool SetPix(int32_t x, int32_t y, uint8_t col) const { if (!HasSurface()||x<0||y<0||x>=surface->Wdt||y>=surface->Hgt) return false; surface->_SetPix(x,y,col); return true; }
	bool IsPixMasked(int32_t x, int32_t y) const { return GetPix(x,y,0)!=0; } // masking: If pixel is inside surface and not transparent
	void ConvertSkyToTransparent(); // change all pixels that are IFT to 0
	int32_t GetPixCount(const C4Rect &rcBounds, const C4MapScriptMatTexMask &col_mask); // return number of pixels that match mask

	// Drawing functions
	bool Fill(int col, const C4Rect &rcBounds, const C4MapScriptAlgo *algo);
	bool Blit(const C4Rect &rcBounds, const C4MapScriptAlgo *algo);
	bool Blit(const C4MapScriptLayer *src, const C4Rect &src_rect, const C4MapScriptMatTexMask &col_mask, int32_t tx, int32_t ty);

	// Search functions
	bool FindPos(const C4Rect &search_rect, const C4MapScriptMatTexMask &col_mask, int32_t *out_x, int32_t *out_y, int32_t max_tries);
};

class C4MapScriptMap : public C4MapScriptLayer
{
	std::list<C4MapScriptLayer *> layers;
public:
	C4MapScriptMap(C4PropList *prototype) : C4MapScriptLayer(prototype, NULL) { map=this; }
	~C4MapScriptMap() { Clear(); }
	void Clear();
	virtual C4MapScriptMap * GetMapScriptMap() { return this; }

	C4MapScriptLayer *CreateLayer(int32_t wdt, int32_t hgt);
};

// Script host for scenario Map.c, parsed in static MapLayer prop list context
// Also hosts engine functions MapLayer prop list.
class C4MapScriptHost : public C4ScriptHost
{
private:
	C4PropListStaticMember *LayerPrototype, *MapPrototype;

	C4MapScriptMap *CreateMap();
public:
	C4MapScriptHost();
	~C4MapScriptHost();
	void InitFunctionMap(C4AulScriptEngine *pEngine);
	virtual void AddEngineFunctions();
	virtual bool Load(C4Group &, const char *, const char *, C4LangStringTable *);
	void Clear();
	virtual C4PropListStatic * GetPropList();
	bool InitializeMap(C4Group &group, CSurface8 **pmap_surface);
	C4PropListStatic *GetLayerPrototype() { return LayerPrototype; }
};

extern C4MapScriptHost MapScript;

#endif