/*
 * This file is part of EasyRPG Player.
 *
 * EasyRPG Player 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.
 *
 * EasyRPG Player 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 EasyRPG Player. If not, see <http://www.gnu.org/licenses/>.
 */

// Headers
#include <iomanip>
#include <sstream>
#include "window_base.h"
#include "cache.h"
#include <lcf/data.h>
#include "game_system.h"
#include "bitmap.h"
#include "font.h"
#include "player.h"

Window_Base::Window_Base(int x, int y, int width, int height, Drawable::Flags flags)
	: Window(flags)
{
	SetWindowskin(Cache::SystemOrBlack());

	SetX(x);
	SetY(y);
	SetWidth(width);
	SetHeight(height);
	SetStretch(Main_Data::game_system->GetMessageStretch() == lcf::rpg::System::Stretch_stretch);
	SetZ(Priority_Window);
}

bool Window_Base::InitMovement(int old_x, int old_y, int new_x, int new_y, int duration) {
	current_frame = 0;
	old_position[0] = old_x;
	old_position[1] = old_y;
	new_position[0] = new_x;
	new_position[1] = new_y;
	SetX(old_position[0]);
	SetY(old_position[1]);
	total_frames = 0;
	if (old_x != new_x || old_y != new_y) {
		total_frames = duration;
		return true;
	}
	return false;
}

bool Window_Base::IsMovementActive() {
	return total_frames > 0 && current_frame <= total_frames;
}

void Window_Base::Update() {
	Window::Update();
	if (IsSystemGraphicUpdateAllowed()) {
		SetWindowskin(Cache::SystemOrBlack());
		SetStretch(Main_Data::game_system->GetMessageStretch() == lcf::rpg::System::Stretch_stretch);
	}
	UpdateMovement();
}

void Window_Base::UpdateMovement() {
	if (!IsMovementActive()) {
		return;
	}
	current_frame++;
	if (IsMovementActive()) {
		SetX(old_position[0] + (new_position[0] - old_position[0]) * current_frame / total_frames);
		SetY(old_position[1] + (new_position[1] - old_position[1]) * current_frame / total_frames);
	} else {
		SetX(new_position[0]);
		SetY(new_position[1]);
	}
}

void Window_Base::OnFaceReady(FileRequestResult* result, int face_index, int cx, int cy, bool flip) {
	BitmapRef faceset = Cache::Faceset(result->file);

	Rect src_rect(
		(face_index % 4) * 48,
		face_index / 4 * 48,
		48,
		48
		);

	if (flip) {
		contents->FlipBlit(cx, cy, *faceset, src_rect, true, false, Opacity::Opaque());
	}
	else {
		contents->Blit(cx, cy, *faceset, src_rect, 255);
	}
}

// All these functions assume that the input is valid

void Window_Base::DrawFace(std::string_view face_name, int face_index, int cx, int cy, bool flip) {
	if (face_name.empty()) { return; }

	FileRequestAsync* request = AsyncHandler::RequestFile("FaceSet", face_name);
	request->SetGraphicFile(true);
	face_request_ids.push_back(request->Bind(&Window_Base::OnFaceReady, this, face_index, cx, cy, flip));
	request->Start();
}

void Window_Base::DrawActorFace(const Game_Actor& actor, int cx, int cy) {
	DrawFace(actor.GetFaceName(), actor.GetFaceIndex(), cx, cy);
}

void Window_Base::DrawActorName(const Game_Battler& actor, int cx, int cy) const {
	contents->TextDraw(cx, cy, Font::ColorDefault, actor.GetName());
}

void Window_Base::DrawActorTitle(const Game_Actor& actor, int cx, int cy) const {
	contents->TextDraw(cx, cy, Font::ColorDefault, actor.GetTitle());
}

void Window_Base::DrawActorClass(const Game_Actor& actor, int cx, int cy) const {
	contents->TextDraw(cx, cy, Font::ColorDefault, actor.GetClassName());
}

void Window_Base::DrawActorLevel(const Game_Actor& actor, int cx, int cy) const {
	// Draw LV-String
	contents->TextDraw(cx, cy, 1, lcf::Data::terms.lvl_short);

	// Draw Level of the Actor
	contents->TextDraw(cx + (lcf::Data::system.easyrpg_max_level >= 100 ? 30 : 24), cy, Font::ColorDefault, std::to_string(actor.GetLevel()), Text::AlignRight);
}

void Window_Base::DrawActorState(const Game_Battler& actor, int cx, int cy) const {
	// Unit has Normal state if no state is set
	const lcf::rpg::State* state = actor.GetSignificantState();
	if (!state) {
		contents->TextDraw(cx, cy, Font::ColorDefault, lcf::Data::terms.normal_status);
	} else {
		contents->TextDraw(cx, cy, state->color, state->name);
	}
}

void Window_Base::DrawActorExp(const Game_Actor& actor, int cx, int cy) const {
	// Draw EXP-String
	int width = 7;
	if (actor.MaxExpValue() < 1000000) {
		width = 6;
		contents->TextDraw(cx, cy, 1, lcf::Data::terms.exp_short);
	}

	// Current Exp of the Actor
	// ------/------
	std::stringstream ss;
	ss << std::setfill(' ') << std::setw(width) << actor.GetExpString();

	// Delimiter
	ss << '/';

	// Exp for Level up
	ss << std::setfill(' ') << std::setw(width) << actor.GetNextExpString();
	contents->TextDraw(cx + (width == 6 ? 12 : 0), cy, Font::ColorDefault, ss.str(), Text::AlignLeft);
}

void Window_Base::DrawActorHp(const Game_Battler& actor, int cx, int cy, int digits, bool draw_max) const {
	// Draw HP-String
	contents->TextDraw(cx, cy, 1, lcf::Data::terms.hp_short);

	// Draw Current HP of the Actor
	cx += 12;
	// Color: 0 okay, 4 critical, 5 dead
	int color = GetValueFontColor(actor.GetHp(), actor.GetMaxHp(), true);
	auto dx = digits * 6;
	contents->TextDraw(cx + dx, cy, color, std::to_string(actor.GetHp()), Text::AlignRight);

	if (!draw_max)
		return;

	// Draw the /
	cx += dx;
	contents->TextDraw(cx, cy, Font::ColorDefault, "/");

	// Draw Max Hp
	cx += 6;
	contents->TextDraw(cx + dx, cy, Font::ColorDefault, std::to_string(actor.GetMaxHp()), Text::AlignRight);
}

void Window_Base::DrawActorSp(const Game_Battler& actor, int cx, int cy, int digits, bool draw_max) const {
	// Draw SP-String
	contents->TextDraw(cx, cy, 1, lcf::Data::terms.sp_short);

	// Draw Current SP of the Actor
	cx += 12;
	// Color: 0 okay, 4 critical/empty
	int color = GetValueFontColor(actor.GetSp(), actor.GetMaxSp(), false);
	auto dx = digits * 6;
	contents->TextDraw(cx + dx, cy, color, std::to_string(actor.GetSp()), Text::AlignRight);

	if (!draw_max)
		return;

	// Draw the /
	cx += dx;
	contents->TextDraw(cx, cy, Font::ColorDefault, "/");

	// Draw Max Sp
	cx += 6;
	contents->TextDraw(cx + dx, cy, Font::ColorDefault, std::to_string(actor.GetMaxSp()), Text::AlignRight);
}

void Window_Base::DrawActorParameter(const Game_Battler& actor, int cx, int cy, int type) const {
	std::string_view name;
	int value;

	switch (type) {
	case 0:
		name = lcf::Data::terms.attack;
		value = actor.GetAtk();
		break;
	case 1:
		name = lcf::Data::terms.defense;
		value = actor.GetDef();
		break;
	case 2:
		name = lcf::Data::terms.spirit;
		value = actor.GetSpi();
		break;
	case 3:
		name = lcf::Data::terms.agility;
		value = actor.GetAgi();
		break;
	default:
		return;
	}

	// Draw Term
	contents->TextDraw(cx, cy, 1, name);

	// Draw Value
	contents->TextDraw(cx + 78, cy, Font::ColorDefault, std::to_string(value), Text::AlignRight);
}

void Window_Base::DrawEquipmentType(const Game_Actor& actor, int cx, int cy, int type) const {
	std::string_view name;

	switch (type) {
	case 0:
		name = lcf::Data::terms.weapon;
		break;
	case 1:
		if (actor.HasTwoWeapons()) {
			name = lcf::Data::terms.weapon;
		} else {
			name = lcf::Data::terms.shield;
		}
		break;
	case 2:
		name = lcf::Data::terms.armor;
		break;
	case 3:
		name = lcf::Data::terms.helmet;
		break;
	case 4:
		name = lcf::Data::terms.accessory;
		break;
	default:
		return;
	}

	contents->TextDraw(cx, cy, 1, name);
}

void Window_Base::DrawItemName(const lcf::rpg::Item& item, int cx, int cy, bool enabled) const {
	int color = enabled ? Font::ColorDefault : Font::ColorDisabled;

	contents->TextDraw(cx, cy, color, item.name);
}

void Window_Base::DrawSkillName(const lcf::rpg::Skill& skill, int cx, int cy, bool enabled) const {
	int color = enabled ? Font::ColorDefault : Font::ColorDisabled;

	contents->TextDraw(cx, cy, color, skill.name);
}

void Window_Base::DrawCurrencyValue(int money, int cx, int cy) const {
	// This function draws right aligned because of the dynamic with of the
	// gold output (cx and cy define the right border)
	std::stringstream gold;
	gold << money;

	Rect gold_text_size = Text::GetSize(*Font::Default(), lcf::Data::terms.gold);
	contents->TextDraw(cx, cy, 1, lcf::Data::terms.gold, Text::AlignRight);

	contents->TextDraw(cx - gold_text_size.width, cy, Font::ColorDefault, gold.str(), Text::AlignRight);
}

void Window_Base::DrawGauge(const Game_Battler& actor, int cx, int cy, int alpha) const {
	BitmapRef system2 = Cache::System2();
	if (!system2) {
		return;
	}

	bool full = actor.IsAtbGaugeFull();

	// Which gauge (0 - 2)
	int gauge_y = 32 + 2 * 16;

	// Three components of the gauge
	Rect gauge_left(0, gauge_y, 16, 16);
	Rect gauge_center(16, gauge_y, 16, 16);
	Rect gauge_right(32, gauge_y, 16, 16);

	Rect dst_rect(cx + 16, cy, 25, 16);

	contents->Blit(cx + 0, cy, *system2, gauge_left, alpha);
	contents->Blit(cx + 16 + 25, cy, *system2, gauge_right, alpha);
	contents->StretchBlit(dst_rect, *system2, gauge_center, alpha);

	const auto atb = actor.GetAtbGauge();
	const auto gauge_w = 25 * atb / actor.GetMaxAtbGauge();
	if (gauge_w > 0) {
		// Full or not full bar
		Rect gauge_bar(full ? 64 : 48, gauge_y, 16, 16);
		Rect bar_rect(cx + 16, cy, gauge_w, 16);
		contents->StretchBlit(bar_rect, *system2, gauge_bar, alpha);
	}
}

void Window_Base::DrawActorHpValue(const Game_Battler& actor, int cx, int cy) const {
	contents->TextDraw(cx, cy, GetValueFontColor(actor.GetHp(), actor.GetMaxHp(), true), std::to_string(actor.GetHp()), Text::AlignRight);
}

void Window_Base::DrawActorSpValue(const Game_Battler& actor, int cx, int cy) const {
	contents->TextDraw(cx, cy, GetValueFontColor(actor.GetSp(), actor.GetMaxSp(), false), std::to_string(actor.GetSp()), Text::AlignRight);
}

int Window_Base::GetValueFontColor(int have, int max, bool can_knockout) const {
	if (can_knockout && have == 0) return Font::ColorKnockout;
	if (max > 0 && (have <= max / 4)) return Font::ColorCritical;
	return Font::ColorDefault;
}
