/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program 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.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "ags/engine/ac/button.h"
#include "ags/shared/ac/common.h"
#include "ags/engine/ac/gui.h"
#include "ags/shared/ac/view.h"
#include "ags/shared/ac/game_setup_struct.h"
#include "ags/engine/ac/global_translation.h"
#include "ags/engine/ac/object.h"
#include "ags/engine/ac/string.h"
#include "ags/engine/ac/view_frame.h"
#include "ags/engine/debugging/debug_log.h"
#include "ags/engine/gui/animating_gui_button.h"
#include "ags/shared/gui/gui_main.h"
#include "ags/shared/debugging/out.h"
#include "ags/engine/script/script_api.h"
#include "ags/engine/script/script_runtime.h"
#include "ags/engine/ac/dynobj/script_string.h"
#include "ags/engine/main/game_run.h"
#include "ags/globals.h"

namespace AGS3 {

using namespace AGS::Shared;

// *** BUTTON FUNCTIONS

// Update the actual button's image from the current animation frame
void UpdateButtonState(const AnimatingGUIButton &abtn) {
	_GP(guibuts)[abtn.buttonid].Image = _GP(views)[abtn.view].loops[abtn.loop].frames[abtn.frame].pic;
	if (_GP(guibuts)[abtn.buttonid].CurrentImage != _GP(guibuts)[abtn.buttonid].Image) {
		_GP(guibuts)[abtn.buttonid].CurrentImage = _GP(guibuts)[abtn.buttonid].Image;
		_GP(guibuts)[abtn.buttonid].MarkChanged();
	}
	_GP(guibuts)[abtn.buttonid].PushedImage = 0;
	_GP(guibuts)[abtn.buttonid].MouseOverImage = 0;
}

void Button_AnimateEx(GUIButton *butt, int view, int loop, int speed,
		int repeat, int blocking, int direction, int sframe, int volume = 100) {
	int guin = butt->ParentId;
	int objn = butt->Id;

	if (direction == FORWARDS)
		direction = 0;
	else if (direction == BACKWARDS)
		direction = 1;
	if (blocking == BLOCKING)
		blocking = 1;
	else if (blocking == IN_BACKGROUND)
		blocking = 0;

	if ((view < 1) || (view > _GP(game).numviews))
		quit("!AnimateButton: invalid view specified");
	view--;
	if ((loop < 0) || (loop >= _GP(views)[view].numLoops))
		quit("!AnimateButton: invalid loop specified for view");
	if (sframe < 0 || sframe >= _GP(views)[view].loops[loop].numFrames)
		quit("!AnimateButton: invalid starting frame number specified");
	if ((repeat < 0) || (repeat > 1))
		quit("!AnimateButton: invalid repeat value");
	if ((blocking < 0) || (blocking > 1))
		quit("!AnimateButton: invalid blocking value");
	if ((direction < 0) || (direction > 1))
		quit("!AnimateButton: invalid direction");

	volume = Math::Clamp(volume, 0, 100);

	// if it's already animating, stop it
	FindAndRemoveButtonAnimation(guin, objn);

	// reverse animation starts at the *previous frame*
	if (direction) {
		if (--sframe < 0)
			sframe = _GP(views)[view].loops[loop].numFrames - (-sframe);
	}

	int but_id = _GP(guis)[guin].GetControlID(objn);
	AnimatingGUIButton abtn;
	abtn.ongui = guin;
	abtn.onguibut = objn;
	abtn.buttonid = but_id;
	abtn.view = view;
	abtn.loop = loop;
	abtn.speed = speed;
	abtn.repeat = repeat;
	abtn.blocking = blocking;
	abtn.direction = direction;
	abtn.frame = sframe;
	abtn.wait = abtn.speed + _GP(views)[abtn.view].loops[abtn.loop].frames[abtn.frame].speed;
	abtn.volume = volume;
	_GP(animbuts).push_back(abtn);
	// launch into the first frame, and play the first frame's sound
	UpdateButtonState(abtn);
	CheckViewFrame(abtn.view, abtn.loop, abtn.frame);

	// Blocking animate
	if (blocking)
		GameLoopUntilButAnimEnd(guin, objn);
}

void Button_Animate(GUIButton *butt, int view, int loop, int speed, int repeat) {
	Button_AnimateEx(butt, view, loop, speed, repeat, IN_BACKGROUND, FORWARDS, 0, 100);
}

void Button_Animate7(GUIButton *butt, int view, int loop, int speed, int repeat, int blocking, int direction, int sframe) {
	 Button_AnimateEx(butt, view, loop, speed, repeat, blocking, direction, sframe, 100);
 }

const char *Button_GetText_New(GUIButton *butt) {
	return CreateNewScriptString(butt->GetText().GetCStr());
}

void Button_GetText(GUIButton *butt, char *buffer) {
	snprintf(buffer, MAX_MAXSTRLEN, "%s", butt->GetText().GetCStr());
}

void Button_SetText(GUIButton *butt, const char *newtx) {
	newtx = get_translation(newtx);

	if (butt->GetText() != newtx) {
		butt->SetText(newtx);
	}
}

void Button_SetFont(GUIButton *butt, int newFont) {
	if ((newFont < 0) || (newFont >= _GP(game).numfonts))
		quit("!Button.Font: invalid font number.");

	if (butt->Font != newFont) {
		butt->Font = newFont;
		butt->MarkChanged();
	}
}

int Button_GetFont(GUIButton *butt) {
	return butt->Font;
}

int Button_GetClipImage(GUIButton *butt) {
	return butt->IsClippingImage() ? 1 : 0;
}

void Button_SetClipImage(GUIButton *butt, int newval) {
	if (butt->IsClippingImage() != (newval != 0)) {
		butt->SetClipImage(newval != 0);
	}
}

int Button_GetGraphic(GUIButton *butt) {
	// return currently displayed pic
	if (butt->CurrentImage < 0)
		return butt->Image;
	return butt->CurrentImage;
}

int Button_GetMouseOverGraphic(GUIButton *butt) {
	return butt->MouseOverImage;
}

void Button_SetMouseOverGraphic(GUIButton *guil, int slotn) {
	debug_script_log("GUI %d Button %d mouseover set to slot %d", guil->ParentId, guil->Id, slotn);

	if ((guil->IsMouseOver != 0) && (guil->IsPushed == 0) && (guil->CurrentImage != slotn)) {
		guil->CurrentImage = slotn;
		guil->MarkChanged();
	}
	guil->MouseOverImage = slotn;
	FindAndRemoveButtonAnimation(guil->ParentId, guil->Id);
}

int Button_GetNormalGraphic(GUIButton *butt) {
	return butt->Image;
}

void Button_SetNormalGraphic(GUIButton *guil, int slotn) {
	debug_script_log("GUI %d Button %d normal set to slot %d", guil->ParentId, guil->Id, slotn);
	// update the clickable area to the same size as the graphic
	int width, height;
	if (slotn < 0 || (size_t)slotn >= _GP(game).SpriteInfos.size()) {
		width = 0;
		height = 0;
	} else {
		width = _GP(game).SpriteInfos[slotn].Width;
		height = _GP(game).SpriteInfos[slotn].Height;
	}

	if ((slotn != guil->Image) || (width != guil->Width) || (height != guil->Height)) {
		// normal pic - update if mouse is not over, or if there's no MouseOverImage
		if (((guil->IsMouseOver == 0) || (guil->MouseOverImage < 1)) && (guil->IsPushed == 0))
			guil->CurrentImage = slotn;
		guil->Image = slotn;
		guil->Width = width;
		guil->Height = height;
		guil->MarkChanged();
	}

	FindAndRemoveButtonAnimation(guil->ParentId, guil->Id);
}

int Button_GetPushedGraphic(GUIButton *butt) {
	return butt->PushedImage;
}

void Button_SetPushedGraphic(GUIButton *guil, int slotn) {
	debug_script_log("GUI %d Button %d pushed set to slot %d", guil->ParentId, guil->Id, slotn);

	if (guil->IsPushed && (guil->CurrentImage != slotn)) {
		guil->CurrentImage = slotn;
		guil->MarkChanged();
	}
	guil->PushedImage = slotn;
	FindAndRemoveButtonAnimation(guil->ParentId, guil->Id);
}

int Button_GetTextColor(GUIButton *butt) {
	return butt->TextColor;
}

void Button_SetTextColor(GUIButton *butt, int newcol) {
	if (butt->TextColor != newcol) {
		butt->TextColor = newcol;
		butt->MarkChanged();
	}
}

// ** start animating buttons code

size_t GetAnimatingButtonCount() {
	return _GP(animbuts).size();
}

AnimatingGUIButton *GetAnimatingButtonByIndex(int idxn) {
	return idxn >= 0 && (size_t)idxn < _GP(animbuts).size() ?
		&_GP(animbuts)[idxn] : nullptr;
}

void AddButtonAnimation(const AnimatingGUIButton &abtn) {
	_GP(animbuts).push_back(abtn);
}

// returns 1 if animation finished
bool UpdateAnimatingButton(int bu) {
	AnimatingGUIButton &abtn = _GP(animbuts)[bu];
	if (abtn.wait > 0) {
		abtn.wait--;
		return true;
	}
	if (!CycleViewAnim(abtn.view, abtn.loop, abtn.frame, !abtn.direction,
		abtn.repeat != 0 ? ANIM_REPEAT : ANIM_ONCE))
		return false;
	CheckViewFrame(abtn.view, abtn.loop, abtn.frame, abtn.volume);
	abtn.wait = abtn.speed + _GP(views)[abtn.view].loops[abtn.loop].frames[abtn.frame].speed;
	UpdateButtonState(abtn);
	return true;
}

void StopButtonAnimation(int idxn) {
	_GP(animbuts).erase(_GP(animbuts).begin() + idxn);
}

void RemoveAllButtonAnimations() {
	_GP(animbuts).clear();
}

// Returns the index of the AnimatingGUIButton object corresponding to the
// given button ID; returns -1 if no such animation exists
int FindButtonAnimation(int guin, int objn) {
	for (size_t i = 0; i < _GP(animbuts).size(); ++i) {
		if (_GP(animbuts)[i].ongui == guin && _GP(animbuts)[i].onguibut == objn)
			return i;
	}
	return -1;
}

void FindAndRemoveButtonAnimation(int guin, int objn) {
	int idx = FindButtonAnimation(guin, objn);
	if (idx >= 0)
		StopButtonAnimation(idx);
}

// ** end animating buttons code

void Button_Click(GUIButton *butt, int mbut) {
	process_interface_click(butt->ParentId, butt->Id, mbut);
}

bool Button_IsAnimating(GUIButton *butt) {
	return FindButtonAnimation(butt->ParentId, butt->Id) >= 0;
}

// NOTE: in correspondance to similar functions for Character & Object,
// GetView returns (view index + 1), while GetLoop and GetFrame return
// zero-based index and 0 in case of no animation.
int Button_GetAnimView(GUIButton *butt) {
	int idx = FindButtonAnimation(butt->ParentId, butt->Id);
	return idx >= 0 ? _GP(animbuts)[idx].view + 1 : 0;
}

int Button_GetAnimLoop(GUIButton *butt) {
	int idx = FindButtonAnimation(butt->ParentId, butt->Id);
	return idx >= 0 ? _GP(animbuts)[idx].loop : 0;
}

int Button_GetAnimFrame(GUIButton *butt) {
	int idx = FindButtonAnimation(butt->ParentId, butt->Id);
	return idx >= 0 ? _GP(animbuts)[idx].frame : 0;
}

int Button_GetTextAlignment(GUIButton *butt) {
	return butt->TextAlignment;
}

void Button_SetTextAlignment(GUIButton *butt, int align) {
	if (butt->TextAlignment != align) {
		butt->TextAlignment = (FrameAlignment)align;
		butt->MarkChanged();
	}
}

//=============================================================================
//
// Script API Functions
//
//=============================================================================

// void | GUIButton *butt, int view, int loop, int speed, int repeat
RuntimeScriptValue Sc_Button_Animate(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_OBJCALL_VOID_PINT4(GUIButton, Button_Animate);
}

RuntimeScriptValue Sc_Button_Animate7(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_OBJCALL_VOID_PINT7(GUIButton, Button_Animate7);
}

RuntimeScriptValue Sc_Button_Animate8(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_OBJCALL_VOID_PINT8(GUIButton, Button_AnimateEx);
}

// const char* | GUIButton *butt
RuntimeScriptValue Sc_Button_GetText_New(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_CONST_OBJCALL_OBJ(GUIButton, const char, _GP(myScriptStringImpl), Button_GetText_New);
}

// void | GUIButton *butt, char *buffer
RuntimeScriptValue Sc_Button_GetText(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_OBJCALL_VOID_POBJ(GUIButton, Button_GetText, char);
}

// void | GUIButton *butt, const char *newtx
RuntimeScriptValue Sc_Button_SetText(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_OBJCALL_VOID_POBJ(GUIButton, Button_SetText, const char);
}

// void | GUIButton *butt, int newFont
RuntimeScriptValue Sc_Button_SetFont(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_OBJCALL_VOID_PINT(GUIButton, Button_SetFont);
}

// int | GUIButton *butt
RuntimeScriptValue Sc_Button_GetFont(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_OBJCALL_INT(GUIButton, Button_GetFont);
}

// int | GUIButton *butt
RuntimeScriptValue Sc_Button_GetClipImage(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_OBJCALL_INT(GUIButton, Button_GetClipImage);
}

// void | GUIButton *butt, int newval
RuntimeScriptValue Sc_Button_SetClipImage(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_OBJCALL_VOID_PINT(GUIButton, Button_SetClipImage);
}

// int | GUIButton *butt
RuntimeScriptValue Sc_Button_GetGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_OBJCALL_INT(GUIButton, Button_GetGraphic);
}

// int | GUIButton *butt
RuntimeScriptValue Sc_Button_GetMouseOverGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_OBJCALL_INT(GUIButton, Button_GetMouseOverGraphic);
}

// void | GUIButton *guil, int slotn
RuntimeScriptValue Sc_Button_SetMouseOverGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_OBJCALL_VOID_PINT(GUIButton, Button_SetMouseOverGraphic);
}

// int | GUIButton *butt
RuntimeScriptValue Sc_Button_GetNormalGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_OBJCALL_INT(GUIButton, Button_GetNormalGraphic);
}

// void | GUIButton *guil, int slotn
RuntimeScriptValue Sc_Button_SetNormalGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_OBJCALL_VOID_PINT(GUIButton, Button_SetNormalGraphic);
}

// int | GUIButton *butt
RuntimeScriptValue Sc_Button_GetPushedGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_OBJCALL_INT(GUIButton, Button_GetPushedGraphic);
}

// void | GUIButton *guil, int slotn
RuntimeScriptValue Sc_Button_SetPushedGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_OBJCALL_VOID_PINT(GUIButton, Button_SetPushedGraphic);
}

// int | GUIButton *butt
RuntimeScriptValue Sc_Button_GetTextColor(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_OBJCALL_INT(GUIButton, Button_GetTextColor);
}

// void | GUIButton *butt, int newcol
RuntimeScriptValue Sc_Button_SetTextColor(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_OBJCALL_VOID_PINT(GUIButton, Button_SetTextColor);
}

RuntimeScriptValue Sc_Button_Click(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_OBJCALL_VOID_PINT(GUIButton, Button_Click);
}

RuntimeScriptValue Sc_Button_GetAnimating(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_OBJCALL_BOOL(GUIButton, Button_IsAnimating);
}

RuntimeScriptValue Sc_Button_GetTextAlignment(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_OBJCALL_INT(GUIButton, Button_GetTextAlignment);
}

RuntimeScriptValue Sc_Button_SetTextAlignment(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_OBJCALL_VOID_PINT(GUIButton, Button_SetTextAlignment);
}

RuntimeScriptValue Sc_Button_GetFrame(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_OBJCALL_INT(GUIButton, Button_GetAnimFrame);
}

RuntimeScriptValue Sc_Button_GetLoop(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_OBJCALL_INT(GUIButton, Button_GetAnimLoop);
}

RuntimeScriptValue Sc_Button_GetView(void *self, const RuntimeScriptValue *params, int32_t param_count) {
	API_OBJCALL_INT(GUIButton, Button_GetAnimView);
}

void RegisterButtonAPI() {
	ccAddExternalObjectFunction("Button::Animate^4", Sc_Button_Animate);
	ccAddExternalObjectFunction("Button::Animate^7", Sc_Button_Animate7);
	ccAddExternalObjectFunction("Button::Animate^8", Sc_Button_Animate8);
	ccAddExternalObjectFunction("Button::Click^1", Sc_Button_Click);
	ccAddExternalObjectFunction("Button::GetText^1", Sc_Button_GetText);
	ccAddExternalObjectFunction("Button::SetText^1", Sc_Button_SetText);
	ccAddExternalObjectFunction("Button::get_TextAlignment", Sc_Button_GetTextAlignment);
	ccAddExternalObjectFunction("Button::set_TextAlignment", Sc_Button_SetTextAlignment);
	ccAddExternalObjectFunction("Button::get_Animating", Sc_Button_GetAnimating);
	ccAddExternalObjectFunction("Button::get_ClipImage", Sc_Button_GetClipImage);
	ccAddExternalObjectFunction("Button::set_ClipImage", Sc_Button_SetClipImage);
	ccAddExternalObjectFunction("Button::get_Font", Sc_Button_GetFont);
	ccAddExternalObjectFunction("Button::set_Font", Sc_Button_SetFont);
	ccAddExternalObjectFunction("Button::get_Frame", Sc_Button_GetFrame);
	ccAddExternalObjectFunction("Button::get_Graphic", Sc_Button_GetGraphic);
	ccAddExternalObjectFunction("Button::get_Loop", Sc_Button_GetLoop);
	ccAddExternalObjectFunction("Button::get_MouseOverGraphic", Sc_Button_GetMouseOverGraphic);
	ccAddExternalObjectFunction("Button::set_MouseOverGraphic", Sc_Button_SetMouseOverGraphic);
	ccAddExternalObjectFunction("Button::get_NormalGraphic", Sc_Button_GetNormalGraphic);
	ccAddExternalObjectFunction("Button::set_NormalGraphic", Sc_Button_SetNormalGraphic);
	ccAddExternalObjectFunction("Button::get_PushedGraphic", Sc_Button_GetPushedGraphic);
	ccAddExternalObjectFunction("Button::set_PushedGraphic", Sc_Button_SetPushedGraphic);
	ccAddExternalObjectFunction("Button::get_Text", Sc_Button_GetText_New);
	ccAddExternalObjectFunction("Button::set_Text", Sc_Button_SetText);
	ccAddExternalObjectFunction("Button::get_TextColor", Sc_Button_GetTextColor);
	ccAddExternalObjectFunction("Button::set_TextColor", Sc_Button_SetTextColor);
	ccAddExternalObjectFunction("Button::get_View", Sc_Button_GetView);
}

} // namespace AGS3
