/*
	Copyright 2017 TeamWin
	This file is part of TWRP/TeamWin Recovery Project.

	TWRP is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	TWRP is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with TWRP.  If not, see <http://www.gnu.org/licenses/>.
*/

// slidervalue.cpp - GUISliderValue object

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/reboot.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>

#include <string>

extern "C" {
#include "../twcommon.h"
}
#include "../minuitwrp/minui.h"

#include "rapidxml.hpp"
#include "objects.hpp"

GUISliderValue::GUISliderValue(xml_node<>* node) : GUIObject(node)
{
	xml_attribute<>* attr;
	xml_node<>* child;

	mMin = 0;
	mMax = 100;
	mValue = 0;
	mLineH = 2;
	mLinePadding = 10;
	mSliderW = 5;
	mSliderH = 30;
	mLineX = 0;
	mLineY = 0;
	mValueStr = NULL;
	mAction = NULL;
	mShowCurr = true;
	mShowRange = false;
	mChangeOnDrag = false;
	mRendered = false;
	mBackgroundImage = NULL;
	mHandleImage = NULL;
	mHandleHoverImage = NULL;
	mDragging = false;

	mLabel = NULL;
	ConvertStrToColor("white", &mTextColor);
	ConvertStrToColor("white", &mLineColor);
	ConvertStrToColor("blue", &mSliderColor);

	if (!node)
	{
		LOGERR("GUISliderValue created without XML node\n");
		return;
	}

	mLabel = new GUIText(node);
	if (mLabel->Render() < 0)
	{
		delete mLabel;
		mLabel = NULL;
	}

	mAction = new GUIAction(node);

	child = FindNode(node, "font");
	if (child)
	{
		mFont = LoadAttrFont(child, "resource");
		mTextColor = LoadAttrColor(child, "color", mTextColor);
	}

	// Load the placement
	LoadPlacement(FindNode(node, "placement"), &mRenderX, &mRenderY, &mRenderW);

	child = FindNode(node, "colors");
	if (child)
	{
		mLineColor = LoadAttrColor(child, "line");
		mSliderColor = LoadAttrColor(child, "slider");
	}

	child = FindNode(node, "resource");
	if (child)
	{
		mBackgroundImage = LoadAttrImage(child, "background");
		mHandleImage = LoadAttrImage(child, "handle");
		mHandleHoverImage = LoadAttrImage(child, "handlehover");
	}

	child = FindNode(node, "data");
	if (child)
	{
		attr = child->first_attribute("variable");
		if (attr)
			mVariable = attr->value();

		attr = child->first_attribute("min");
		if (attr)
		{
			mMinStr = gui_parse_text(attr->value());
			mMin = atoi(mMinStr.c_str());
		}

		attr = child->first_attribute("max");
		if (attr)
		{
			mMaxStr = gui_parse_text(attr->value());
			mMax = atoi(mMaxStr.c_str());
		}

		if (mMin > mMax)
			mMin = mMax;

		attr = child->first_attribute("default");
		if (attr)
		{
			string parsevalue = gui_parse_text(attr->value());
			int def = atoi(parsevalue.c_str());

			if (def < mMin)
				def = mMin;
			else if (def > mMax)
				def = mMax;
			DataManager::SetValue(mVariable, def);
		}

		attr = child->first_attribute("showrange");
		if (attr)
			mShowRange = atoi(attr->value());

		attr = child->first_attribute("showcurr");
		if (attr)
			mShowCurr = atoi(attr->value());

		attr = child->first_attribute("changeondrag");
		if (attr)
			mChangeOnDrag = atoi(attr->value());
	}

	child = FindNode(node, "dimensions");
	if (child)
	{
		mLineH = LoadAttrIntScaleY(child, "lineh", mLineH);
		mLinePadding = LoadAttrIntScaleX(child, "linepadding", mLinePadding);
		mSliderW = LoadAttrIntScaleX(child, "sliderw", mSliderW);
		mSliderH = LoadAttrIntScaleY(child, "sliderh", mSliderH);
	}

	if (mFont && mFont->GetResource())
		mFontHeight = mFont->GetHeight();
	else
		mFontHeight = 0;

	if (mShowCurr)
	{
		int maxLen = std::max(strlen(mMinStr.c_str()), strlen(mMaxStr.c_str()));
		mValueStr = new char[maxLen+1];
	}

	loadValue(true);

	if (mShowRange)
	{
		int textW = std::max(measureText(mMaxStr), measureText(mMinStr));
		mLinePadding += textW;
	}

	SetRenderPos(mRenderX, mRenderY, mRenderW);
}

GUISliderValue::~GUISliderValue()
{
	delete mLabel;
	delete mAction;
	delete[] mValueStr;
}

void GUISliderValue::loadValue(bool force)
{
	if (!mVariable.empty())
	{
		int value = DataManager::GetIntValue(mVariable);
		if (mValue == value && !force)
			return;

		mValue = value;
	}

	mValue = std::max(mValue, mMin);
	mValue = std::min(mValue, mMax);
	mValuePct = pctFromValue(mValue);
	mRendered = false;
}

int GUISliderValue::SetRenderPos(int x, int y, int w, int h)
{
	mRenderX = x;
	mRenderY = y;
	if (w || h)
	{
		mRenderW = w;
		mRenderH = h;
	}

	mRenderH = mSliderH;
	if (mShowCurr)
		mRenderH += mFontHeight;

	if (mLabel)
	{
		int lw, lh;
		mLabel->GetCurrentBounds(lw, lh);
		int textX = mRenderX + (mRenderW/2 - lw/2);

		mLabel->SetRenderPos(textX, mRenderY);

		y += lh;
		mRenderH += lh;
	}

	mSliderY = y;

	mActionX = mRenderX;
	mActionY = mRenderY;
	mActionW = mRenderW;
	mActionH = mRenderH;

	if (mBackgroundImage && mBackgroundImage->GetResource())
	{
		mLineW = mBackgroundImage->GetWidth();
		mLineH = mBackgroundImage->GetHeight();
	}
	else
		mLineW = mRenderW - (mLinePadding * 2);

	mLineY = y + (mSliderH/2 - mLineH/2);
	mLineX = mRenderX + (mRenderW/2 - mLineW/2);

	return 0;
}

int GUISliderValue::measureText(const std::string& str)
{
	void* fontResource = NULL;
	if (mFont)  fontResource = mFont->GetResource();

	return gr_ttf_measureEx(str.c_str(), fontResource);
}

int GUISliderValue::Render(void)
{
	if (!isConditionTrue())
	{
		mRendered = false;
		return 0;
	}

	if (mLabel)
	{
		int w, h;
		mLabel->GetCurrentBounds(w, h);
		if (w != mLabelW) {
			mLabelW = w;
			int textX = mRenderX + (mRenderW/2 - mLabelW/2);
			mLabel->SetRenderPos(textX, mRenderY);
		}
		int res = mLabel->Render();
		if (res < 0)
			return res;
	}

	// line
	if (mBackgroundImage && mBackgroundImage->GetResource())
	{
		gr_blit(mBackgroundImage->GetResource(), 0, 0, mLineW, mLineH, mLineX, mLineY);
	}
	else
	{
		gr_color(mLineColor.red, mLineColor.green, mLineColor.blue, mLineColor.alpha);
		gr_fill(mLineX, mLineY, mLineW, mLineH);
	}

	// slider
	uint32_t sliderX = mLineX + (mValuePct*(mLineW - mSliderW))/100;

	if (mHandleImage && mHandleImage->GetResource())
	{
		gr_surface s = mHandleImage->GetResource();
		if (mDragging && mHandleHoverImage && mHandleHoverImage->GetResource())
			s = mHandleHoverImage->GetResource();
		gr_blit(s, 0, 0, mSliderW, mSliderH, sliderX, mLineY + (mLineH/2 - mSliderH/2));
	}
	else
	{
		gr_color(mSliderColor.red, mSliderColor.green, mSliderColor.blue, mSliderColor.alpha);
		gr_fill(sliderX, mSliderY, mSliderW, mSliderH);
	}

	void *fontResource = NULL;
	if (mFont) fontResource = mFont->GetResource();
	gr_color(mTextColor.red, mTextColor.green, mTextColor.blue, mTextColor.alpha);
	if (mShowRange)
	{
		int rangeY = (mLineY - mLineH/2) - mFontHeight/2;
		gr_textEx_scaleW(mRenderX + mPadding/2, rangeY, mMinStr.c_str(), fontResource, mRenderW, TOP_LEFT, 0);
		gr_textEx_scaleW(mLineX + mLineW + mPadding/2, rangeY, mMaxStr.c_str(), fontResource, mRenderW, TOP_LEFT, 0);
	}

	if (mValueStr && mShowCurr)
	{
		sprintf(mValueStr, "%d", mValue);
		int textW = measureText(mValueStr);
		gr_textEx_scaleW(mRenderX + (mRenderW/2 - textW/2), mSliderY+mSliderH, mValueStr, fontResource, mRenderW, TOP_LEFT, 0);
	}

	mRendered = true;
	return 0;
}

int GUISliderValue::Update(void)
{
	if (!isConditionTrue())
		return mRendered ? 2 : 0;
	if (!mRendered)
		return 2;

	if (mLabel)
		return mLabel->Update();
	return 0;
}

int GUISliderValue::valueFromPct(float pct)
{
	int range = abs(mMax - mMin);
	return mMin + (pct * range) / 100;
}

float GUISliderValue::pctFromValue(int value)
{
	return float((value - mMin) * 100) / abs(mMax - mMin);
}

int GUISliderValue::NotifyTouch(TOUCH_STATE state, int x, int y)
{
	if (!isConditionTrue())
		return -1;

	switch (state)
	{
	case TOUCH_START:
		if (x >= mLineX && x <= mLineX + mLineW &&
			y >= mRenderY && y <= mRenderY + mRenderH)
		{
			mDragging = true;
		}
		// no break
	case TOUCH_DRAG:
	{
		if (!mDragging)  return 0;

		x = std::max(mLineX + mSliderW/2, x);
		x = std::min(mLineX + mLineW - mSliderW/2, x);

		mValuePct = float(((x - (mLineX + mSliderW/2)) * 100) / (mLineW - mSliderW));
		int newVal = valueFromPct(mValuePct);
		if (newVal != mValue) {
			mRendered = false;
			mValue = newVal;
			if (mChangeOnDrag) {
				if (!mVariable.empty())
					DataManager::SetValue(mVariable, mValue);
				if (mAction)
					mAction->doActions();
			}
		}
		break;
	}
	case TOUCH_RELEASE:
	{
		if (!mDragging)  return 0;
		mDragging = false;

		if (!mVariable.empty())
			DataManager::SetValue(mVariable, mValue);
		if (mAction)
			mAction->doActions();
		break;
	}
	case TOUCH_REPEAT:
	case TOUCH_HOLD:
		break;
	}
	return 0;
}

int GUISliderValue::NotifyVarChange(const std::string& varName, const std::string& value)
{
	GUIObject::NotifyVarChange(varName, value);

	if (mLabel)
		mLabel->NotifyVarChange(varName, value);
	if (varName == mVariable) {
		int newVal = atoi(value.c_str());
		if (newVal != mValue) {
			mValue = newVal;
			mValuePct = pctFromValue(mValue);
			mRendered = false;
		}
	}
	return 0;
}

void GUISliderValue::SetPageFocus(int inFocus)
{
	if (inFocus)
		loadValue();
}
