//								-*- c++ -*-

#include "xml/object.h"
#include "render.h"
#include "font.h"

static const std::string ObjectNames[] =
	{ "image", "text", "marquee", "blink", "rectangle", "ellipse", "slope", "progress",
	  "scrolltext", "scrollbar", "block", "list", "item" };

cxObject::cxObject(cxDisplay *Parent):
		mDisplay(Parent),
		mSkin(Parent->Skin()),
		mType((eType)__COUNT_OBJECT__),
		mPos1(0, 0),
		mPos2(-1, -1),
		mVirtSize(-1, -1),
		mAlpha(255),
		mColors(0),
		mArc(0),
		mPath(this, false),
		mText(this, true),
		mAlign(taDefault),
		mCondition(NULL),
		mCurrent(this, false),
		mTotal(this, false),
		mFontFace("Osd"),
		mFontSize(0),
		mFontWidth(0),
		mDelay(150),
		mIndex(0),
		mRefresh(this),
		mObjects(NULL),
		mListIndex(0)
{
}

cxObject::cxObject(const cxObject &Src):
		mDisplay(Src.mDisplay),
		mSkin(Src.mSkin),
		mType(Src.mType),
		mPos1(Src.mPos1),
		mPos2(Src.mPos2),
		mVirtSize(Src.mVirtSize),
		mAlpha(Src.mAlpha),
		mColors(Src.mColors),
		mArc(Src.mArc),
		mFg(Src.mFg),
		mBg(Src.mBg),
		mBl(Src.mBl),
		mMask(Src.mMask),
		mMark(Src.mMark),
		mActive(Src.mActive),
		mKeep(Src.mKeep),
		mPath(Src.mPath),
		mText(Src.mText),
		mAlign(Src.mAlign),
		mCondition(NULL),
		mCurrent(Src.mCurrent),
		mTotal(Src.mTotal),
		mFontFace(Src.mFontFace),
		mFontSize(Src.mFontSize),
		mFontWidth(Src.mFontWidth),
		mDelay(Src.mDelay),
		mRefresh(Src.mRefresh),
		mObjects(NULL),
		mListIndex(Src.mListIndex)
{
	if (Src.mCondition)
		mCondition = new cxFunction(*Src.mCondition);
	if (Src.mObjects)
		mObjects = new cxObjects(*Src.mObjects);
}

cxObject::~cxObject()
{
	delete mCondition;
	delete mObjects;
}

bool cxObject::ParseType(const std::string &Text)
{
	for (int i = 0; i < (int)__COUNT_OBJECT__; ++i) {
		if (ObjectNames[i] == Text) {
			mType = (eType)i;
			return true;
		}
	}
	return false;
}

bool cxObject::ParseCondition(const std::string &Text)
{
	cxFunction *result = new cxFunction(this);
	if (result->Parse(Text)) {
		delete mCondition;
		mCondition = result;
		return true;
	}
	return false;
}

bool cxObject::ParseAlignment(const std::string &Text)
{
	if      (Text == "center") mAlign = (eTextAlignment)(taTop | taCenter);
	else if (Text == "right")  mAlign = (eTextAlignment)(taTop | taRight);
	else if (Text == "left")   mAlign = (eTextAlignment)(taTop | taLeft);
	else
		return false;
	return true;
}

bool cxObject::ParseFontFace(const std::string &Text)
{
	int size = 0, width = 0, pos;
	std::string face = Text;
	if ((pos = face.find('@')) != -1) {
		std::string s = face.substr(pos + 1);
		const char *p = s.c_str();
		char *end;
		size = strtol(p, &end, 10);
		if (*end == ',')
			width = strtol(end + 1, NULL, 10);

		face.erase(pos);
	}

	mFontFace  = face;
	mFontSize  = size;
	mFontWidth = width;
	return true;
}

void cxObject::SetListIndex(uint Index, int Tab)
{
	Tab = (Tab >= 0 ? Tab : -1);
	mListIndex = 1 + Index * cSkinDisplayMenu::MaxTabs + Tab;
	mText.SetListIndex(Index, Tab);
	mPath.SetListIndex(Index, Tab);
	if (mCondition != NULL)
		mCondition->SetListIndex(Index, Tab);
}

const std::string &cxObject::TypeName(void) const
{
	return ObjectNames[mType];
}

const cFont *cxObject::Font(void) const
{
	const cFont *font;

	if ((font = cText2SkinFont::Load(mFontFace, mFontSize)) != NULL)
		return font;

	return cFont::GetFont(fontOsd);
}

txPoint cxObject::Pos(const txPoint &BaseOffset, const txSize &BaseSize, const txSize &VirtSize) const
{
	txPoint bOffset = BaseOffset.x < 0 ? mSkin->BaseOffset() : BaseOffset;
	txSize  bSize   = BaseSize.w   < 0 ? mSkin->BaseSize()   : BaseSize;

	double scale_x = VirtSize.w > 0 ? (double)BaseSize.w / VirtSize.w : 1.0,
	       scale_y = VirtSize.h > 0 ? (double)BaseSize.h / VirtSize.h : 1.0;

	int x1 = mPos1.x < 0 ? (int)((mPos1.x + 1) * scale_x - 1) : (int)(mPos1.x * scale_x);
	int y1 = mPos1.y < 0 ? (int)((mPos1.y + 1) * scale_x - 1) : (int)(mPos1.y * scale_y);

	return txPoint(bOffset.x + (x1 < 0 ? bSize.w + x1 : x1),
	               bOffset.y + (y1 < 0 ? bSize.h + y1 : y1));
}

txSize cxObject::Size(const txPoint &BaseOffset, const txSize &BaseSize, const txSize &VirtSize) const
{
	//txPoint bOffset = BaseOffset.x < 0 ? mSkin->BaseOffset() : BaseOffset;
	txSize  bSize   = BaseSize.w   < 0 ? mSkin->BaseSize()   : BaseSize;

	double scale_x = VirtSize.w > 0 ? (double)BaseSize.w / VirtSize.w : 1.0,
	       scale_y = VirtSize.h > 0 ? (double)BaseSize.h / VirtSize.h : 1.0;

	int x1 = mPos1.x < 0 ? (int)((mPos1.x + 1) * scale_x - 1) : (int)(mPos1.x * scale_x);
	int y1 = mPos1.y < 0 ? (int)((mPos1.y + 1) * scale_x - 1) : (int)(mPos1.y * scale_y);
	int x2 = mPos2.x < 0 ? (int)((mPos2.x + 1) * scale_x - 1) : (int)(mPos2.x * scale_x);
	int y2 = mPos2.y < 0 ? (int)((mPos2.y + 1) * scale_x - 1) : (int)(mPos2.y * scale_y);

	txPoint p1(x1 < 0 ? bSize.w + x1 : x1,
	           y1 < 0 ? bSize.h + y1 : y1);
	txPoint p2(x2 < 0 ? bSize.w + x2 : x2,
	           y2 < 0 ? bSize.h + y2 : y2);

	return txSize(p2.x - p1.x + 1, p2.y - p1.y + 1);
}

const tColor *cxObject::Fg(void) const
{
	static tColor Fg;
	return cText2SkinRender::ItemColor(mFg, Fg) ? &Fg : NULL;
}

const tColor *cxObject::Bg(void) const
{
	static tColor Bg;
	return cText2SkinRender::ItemColor(mBg, Bg) ? &Bg : NULL;
}

const tColor *cxObject::Bl(void) const
{
	static tColor Bl;
	return cText2SkinRender::ItemColor(mBl, Bl) ? &Bl : NULL;
}

const tColor *cxObject::Mask(void) const
{
	static tColor Mask;
	return cText2SkinRender::ItemColor(mMask, Mask) ? &Mask : NULL;
}

const tColor *cxObject::Mark(void) const
{
	static tColor Mark;
	return cText2SkinRender::ItemColor(mMark, Mark) ? &Mark : NULL;
}

const tColor *cxObject::Active(void) const
{
	static tColor Active;
	return cText2SkinRender::ItemColor(mActive, Active) ? &Active : NULL;
}

const tColor *cxObject::Keep(void) const
{
	static tColor Keep;
	return cText2SkinRender::ItemColor(mKeep, Keep) ? &Keep : NULL;
}

cxObjects::cxObjects(void)
{
}

cxObjects::~cxObjects()
{
	for (uint i = 0; i < size(); ++i)
		delete operator[](i);
}


///////////////////////////////////////////////////////////////////////////////
// ---------- class cxRefresh ---------------------------------------------- //

cxRefresh::cxRefresh(cxObject *Object):
	mRefreshType(0xFF),
	mText(NULL),
	mChanged(NULL),
	mObject(Object),
	mForce(true),
	mFull(true)
{
}

cxRefresh::~cxRefresh()
{
	delete mText;
}

bool cxRefresh::Dirty(uint dirty, uint &updatein, bool force, uint now)
{
	// check if the timeout of the object has expired
	uint nexttime = mObject->State().nexttime;

	bool to = force || mForce ||
	          mObject->Type() == cxObject::block || mObject->Type() == cxObject::list;
	bool changed = force || mForce;

	if (now > 0 && nexttime > 0) {
		// timeout was set
		if (now >= nexttime)
			// timeout has expired
			to = true;
		else {
			// time left -> set new update interval
			uint nextin = nexttime - now;
			if (updatein == 0 || nextin < updatein)
				updatein = nextin;
		}
	}

	// Object has changed since last redraw
	if (mChanged != NULL) {
		mEval = mChanged->Evaluate();
		if (mEval != mLastEval)
			changed = true;
	}

	// refresh
	if ((mRefreshType & dirty & ~(1<<timeout) & ~(1<<update))) {
		if (changed)
			mLastEval = mEval;
		return true;
	}

	// timeout
	if ((mRefreshType & dirty & (1<<timeout)) && to) {
		if (changed)
			mLastEval = mEval;
		return true;
	}

	// update
	if ((mRefreshType & dirty & (1<<update)) && changed) {
		mLastEval = mEval;
		return true;
	}

	return false;
}

bool cxRefresh::Parse(const std::string &Text)
{
	uint refresh = 0;
	bool force = false, full = false;

	if (Text.find("all") != std::string::npos)
		refresh |= (1<<all);

	if (Text.find("timeout") != std::string::npos)
		refresh |= (1<<timeout);

	if (Text.find("update") != std::string::npos)
		refresh |= (1<<update);

	//if (Text.find("message") != std::string::npos)
	//	refresh |= (1<<list);

	if (Text.find("list") != std::string::npos)
		refresh |= (1<<list);

	if (Text.find("scroll") != std::string::npos)
		refresh |= (1<<scroll);

	if (Text.find("always") != std::string::npos)
		refresh |= 0xFF;

	if (Text.find("full") != std::string::npos)
		full = true;

	if (Text.find("force") != std::string::npos)
		force = true;

	if (refresh == 0)
		return false;

	mForce = force;
	mFull = full;
	mRefreshType = refresh;

	return true;
}

bool cxRefresh::ParseChanged(const std::string &Text)
{
	if (mObject == NULL)
		return false;

	if (mText == NULL)
		mText = new cxString(mObject, false);

	if (mText->Parse(Text)) {
		mChanged = mText;
		return true;
	}

	return false;
}

cxRefresh &cxRefresh::operator=(const cxRefresh &a)
{
	mRefreshType = a.mRefreshType;
	mForce = a.mForce;
	mFull = a.mFull;
	return *this;
}