/*
 * Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
 */

#include <imgui.h>
#include <lodepng.h>

#include <nostalgia/core/media.hpp>
#include <nostalgia/geo/point.hpp>

#include "tilesheeteditor-imgui.hpp"

namespace nostalgia::core {

template<bool alpha = false>
ox::Error toPngFile(const ox::String &path, const TileSheet::SubSheet &s, const Palette &pal, int8_t bpp) noexcept {
	ox::Vector<uint8_t> pixels;
	s.readPixelsTo(&pixels, bpp);
	const unsigned rows = s.rows == -1 ? pixels.size() / PixelsPerTile : static_cast<unsigned>(s.rows);
	const unsigned cols = s.columns == -1 ? 1 : static_cast<unsigned>(s.columns);
	const auto width = cols * TileWidth;
	const auto height = rows * TileHeight;
	constexpr auto bytesPerPixel = alpha ? 4 : 3;
	ox::Vector<unsigned char> outData(pixels.size() * bytesPerPixel);
	for (auto idx = 0; const auto colorIdx : pixels) {
		const auto pt = idxToPt(idx, static_cast<int>(cols));
		const auto i = static_cast<unsigned>(pt.y * static_cast<int>(width) + pt.x) * bytesPerPixel;
		const auto c = pal.colors[colorIdx];
		outData[i + 0] = red32(c);
		outData[i + 1] = green32(c);
		outData[i + 2] = blue32(c);
		if constexpr(alpha) {
			outData[i + 3] = colorIdx ? 255 : 0;
		}
		++idx;
	}
	constexpr auto fmt = alpha ? LCT_RGBA : LCT_RGB;
	return OxError(lodepng_encode_file(path.c_str(), outData.data(), width, height, fmt, 8));
}

TileSheetEditorImGui::TileSheetEditorImGui(Context *ctx, const ox::String &path): m_tileSheetEditor(ctx, path) {
	m_ctx = ctx;
	m_itemPath = path;
	const auto lastSlash = ox::find(m_itemPath.rbegin(), m_itemPath.rend(), '/').offset();
	m_itemName = m_itemPath.substr(lastSlash + 1);
	// init palette idx
	const auto &palPath = model()->palPath();
	auto sctx = applicationData<studio::StudioContext>(m_ctx);
	const auto &palList = sctx->project->fileList(core::FileExt_npal + 1);
	for (std::size_t i = 0; const auto &pal : palList) {
		if (palPath == pal) {
			m_selectedPaletteIdx = i;
			break;
		}
		++i;
	}
	// connect signal/slots
	undoStack()->changeTriggered.connect(this, &TileSheetEditorImGui::markUnsavedChanges);
	m_subsheetEditor.inputSubmitted.connect(this, &TileSheetEditorImGui::updateActiveSubsheet);
}

const ox::String &TileSheetEditorImGui::itemName() const noexcept {
	return m_itemPath;
}

const ox::String &TileSheetEditorImGui::itemDisplayName() const noexcept {
	return m_itemName;
}

void TileSheetEditorImGui::exportFile() {
	exportSubhseetToPng();
}

void TileSheetEditorImGui::cut() {
	model()->cut();
}

void TileSheetEditorImGui::copy() {
	model()->copy();
}

void TileSheetEditorImGui::paste() {
	model()->paste();
}

void TileSheetEditorImGui::keyStateChanged(core::Key key, bool down) {
	if (!down) {
		return;
	}
	auto pal = model()->pal();
	if (pal) {
		const auto colorCnt = pal->colors.size();
		if (key == core::Key::Alpha_D) {
			m_tool = Tool::Draw;
			model()->clearSelection();
		} else if (key == core::Key::Alpha_S) {
			m_tool = Tool::Select;
		} else if (key == core::Key::Alpha_F) {
			m_tool = Tool::Fill;
			model()->clearSelection();
		} else if (key >= core::Key::Num_1 && key <= core::Key::Num_9 && key <= core::Key::Num_0 + colorCnt) {
			auto idx = ox::min<std::size_t>(static_cast<uint32_t>(key - core::Key::Num_1), colorCnt - 1);
			m_tileSheetEditor.setPalIdx(idx);
		} else if (key == core::Key::Num_0 && colorCnt >= 10) {
			auto idx = ox::min<std::size_t>(static_cast<uint32_t>(key - core::Key::Num_1 + 9), colorCnt - 1);
			m_tileSheetEditor.setPalIdx(idx);
		}
	}
}

void TileSheetEditorImGui::draw(core::Context*) noexcept {
	const auto paneSize = ImGui::GetContentRegionAvail();
	const auto tileSheetParentSize = ImVec2(paneSize.x - m_palViewWidth, paneSize.y);
	const auto fbSize = geo::Vec2(tileSheetParentSize.x - 16, tileSheetParentSize.y - 16);
	ImGui::BeginChild("TileSheetView", tileSheetParentSize, true);
	{
		drawTileSheet(fbSize);
	}
	ImGui::EndChild();
	ImGui::SameLine();
	ImGui::BeginChild("Controls", ImVec2(m_palViewWidth - 8, paneSize.y), true);
	{
		ImGui::BeginChild("ToolBox", ImVec2(m_palViewWidth - 24, 30), true);
		{
			const auto btnSz = ImVec2(45, 14);
			if (ImGui::Selectable("Select", m_tool == Tool::Select, 0, btnSz)) {
				m_tool = Tool::Select;
			}
			ImGui::SameLine();
			if (ImGui::Selectable("Draw", m_tool == Tool::Draw, 0, btnSz)) {
				m_tool = Tool::Draw;
				model()->clearSelection();
			}
			ImGui::SameLine();
			if (ImGui::Selectable("Fill", m_tool == Tool::Fill, 0, btnSz)) {
				m_tool = Tool::Fill;
				model()->clearSelection();
			}
		}
		ImGui::EndChild();
		const auto ySize = paneSize.y - 36;
		// draw palette/color picker
		ImGui::BeginChild("Palette", ImVec2(m_palViewWidth - 24, ySize / 2.07f), true);
		{
			drawPaletteSelector();
		}
		ImGui::EndChild();
		ImGui::BeginChild("SubSheets", ImVec2(m_palViewWidth - 24, ySize / 2.03f), true);
		{
			static constexpr auto btnHeight = 18;
			const auto btnSize = ImVec2(18, btnHeight);
			if (ImGui::Button("+", btnSize)) {
				auto insertOnIdx = model()->activeSubSheetIdx();
				const auto &parent = *model()->activeSubSheet();
				model()->addSubsheet(insertOnIdx);
				insertOnIdx.emplace_back(parent.subsheets.size() - 1);
				model()->setActiveSubsheet(insertOnIdx);
			}
			ImGui::SameLine();
			if (ImGui::Button("-", btnSize)) {
				const auto &activeSubsheetIdx = model()->activeSubSheetIdx();
				if (activeSubsheetIdx.size() > 0) {
					model()->rmSubsheet(activeSubsheetIdx);
				}
			}
			ImGui::SameLine();
			if (ImGui::Button("Edit", ImVec2(51, btnHeight))) {
				showSubsheetEditor();
			}
			ImGui::SameLine();
			if (ImGui::Button("Export", ImVec2(51, btnHeight))) {
				exportSubhseetToPng();
			}
			TileSheet::SubSheetIdx path;
			static constexpr auto flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody;
			if (ImGui::BeginTable("Subsheets", 3, flags)) {
				ImGui::TableSetupColumn("Subsheet", ImGuiTableColumnFlags_NoHide);
				ImGui::TableSetupColumn("Columns", ImGuiTableColumnFlags_WidthFixed, 50);
				ImGui::TableSetupColumn("Rows", ImGuiTableColumnFlags_WidthFixed, 50);
				ImGui::TableHeadersRow();
				drawSubsheetSelector(&m_tileSheetEditor.img().subsheet, &path);
				ImGui::EndTable();
			}
		}
		ImGui::EndChild();
	}
	ImGui::EndChild();
	m_subsheetEditor.draw();
}

void TileSheetEditorImGui::drawSubsheetSelector(TileSheet::SubSheet *subsheet, TileSheet::SubSheetIdx *path) {
	ImGui::TableNextRow(0, 5);
	using Str = ox::BasicString<100>;
	auto pathStr = ox::join<Str>("##", *path).value;
	auto lbl = ox::sfmt<Str>("{}##{}", subsheet->name, pathStr);
	const auto rowSelected = *path == model()->activeSubSheetIdx();
	const auto flags = ImGuiTreeNodeFlags_SpanFullWidth
	                 | ImGuiTreeNodeFlags_OpenOnArrow
	                 | ImGuiTreeNodeFlags_DefaultOpen
	                 | (subsheet->subsheets.size() ? 0 : ImGuiTreeNodeFlags_Leaf)
	                 | (rowSelected ? ImGuiTreeNodeFlags_Selected : 0);
	ImGui::TableNextColumn();
	const auto open = ImGui::TreeNodeEx(lbl.c_str(), flags);
	Str newName = subsheet->name.c_str();
	ImGui::SameLine();
	if (ImGui::IsItemClicked()) {
		model()->setActiveSubsheet(*path);
	}
	if (ImGui::IsMouseDoubleClicked(0) && ImGui::IsItemHovered()) {
		showSubsheetEditor();
	}
	if (subsheet->subsheets.size()) {
		ImGui::TableNextColumn();
		ImGui::Text("--");
		ImGui::TableNextColumn();
		ImGui::Text("--");
	} else {
		ImGui::TableNextColumn();
		ImGui::Text("%d", subsheet->columns);
		ImGui::TableNextColumn();
		ImGui::Text("%d", subsheet->rows);
	}
	if (open) {
		for (auto i = 0ul; auto &child : subsheet->subsheets) {
			path->push_back(i);
			ImGui::PushID(static_cast<int>(i));
			drawSubsheetSelector(&child, path);
			ImGui::PopID();
			path->pop_back();
			++i;
		}
		ImGui::TreePop();
	}
}

studio::UndoStack *TileSheetEditorImGui::undoStack() noexcept {
	return model()->undoStack();
}

[[nodiscard]]
geo::Vec2 TileSheetEditorImGui::clickPos(const ImVec2 &winPos, geo::Vec2 clickPos) noexcept {
	clickPos.x -= winPos.x + 10;
	clickPos.y -= winPos.y + 10;
	return clickPos;
}

ox::Error TileSheetEditorImGui::saveItem() noexcept {
	return model()->saveFile();
}

void TileSheetEditorImGui::showSubsheetEditor() noexcept {
	const auto sheet = model()->activeSubSheet();
	if (sheet->subsheets.size()) {
		m_subsheetEditor.show(sheet->name, -1, -1);
	} else {
		m_subsheetEditor.show(sheet->name, sheet->columns, sheet->rows);
	}
}

void TileSheetEditorImGui::exportSubhseetToPng() noexcept {
	auto [path, err] = studio::saveFile({{"PNG", "png"}});
	if (err) {
		return;
	}
	// subsheet to png
	const auto &img = model()->img();
	const auto &s = *model()->activeSubSheet();
	const auto &pal = model()->pal();
	err = toPngFile(path, s, *pal, img.bpp);
	if (err) {
		oxErrorf("Tilesheet export failed: {}", toStr(err));
	}
}

void TileSheetEditorImGui::drawTileSheet(const geo::Vec2 &fbSize) noexcept {
	const auto winPos = ImGui::GetWindowPos();
	const auto fbSizei = geo::Size(static_cast<int>(fbSize.x), static_cast<int>(fbSize.y));
	if (m_framebuffer.width != fbSizei.width || m_framebuffer.height != fbSizei.height) {
		m_framebuffer = glutils::generateFrameBuffer(fbSizei.width, fbSizei.height);
		m_tileSheetEditor.resizeView(fbSize);
	} else if (m_tileSheetEditor.updated()) {
		m_tileSheetEditor.resizeView(fbSize);
		m_tileSheetEditor.ackUpdate();
	}
	glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
	// clear screen and draw
	glViewport(0, 0, fbSizei.width, fbSizei.height);
	m_tileSheetEditor.draw();
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	uintptr_t buffId = m_framebuffer.color.id;
	ImGui::Image(
		reinterpret_cast<void*>(buffId),
		static_cast<ImVec2>(fbSize),
		ImVec2(0, 1),
		ImVec2(1, 0));
	// handle input, this must come after drawing
	const auto &io = ImGui::GetIO();
	const auto mousePos = geo::Vec2(io.MousePos);
	if (ImGui::IsItemHovered()) {
		const auto wheel = io.MouseWheel;
		const auto wheelh = io.MouseWheelH;
		if (wheel != 0) {
			const auto zoomMod = ox::defines::OS == ox::OS::Darwin ?
			                     io.KeySuper : core::buttonDown(m_ctx, core::Key::Mod_Ctrl);
			m_tileSheetEditor.scrollV(fbSize, wheel, zoomMod);
		}
		if (wheelh != 0) {
			m_tileSheetEditor.scrollH(fbSize, wheelh);
		}
		if (io.MouseDown[0] && m_prevMouseDownPos != mousePos) {
			m_prevMouseDownPos = mousePos;
			switch (m_tool) {
				case Tool::Draw:
					m_tileSheetEditor.clickDraw(fbSize, clickPos(winPos, mousePos));
					break;
				case Tool::Fill:
					m_tileSheetEditor.clickFill(fbSize, clickPos(winPos, mousePos));
					break;
				case Tool::Select:
					m_tileSheetEditor.clickSelect(fbSize, clickPos(winPos, mousePos));
					break;
				case Tool::None:
					break;
			}
		}
	}
	if (ImGui::BeginPopupContextItem("TileMenu", ImGuiPopupFlags_MouseButtonRight)) {
		const auto popupPos = geo::Vec2(ImGui::GetWindowPos());
		if (ImGui::MenuItem("Insert Tile")) {
			m_tileSheetEditor.insertTile(fbSize, clickPos(winPos, popupPos));
		}
		if (ImGui::MenuItem("Delete Tile")) {
			m_tileSheetEditor.deleteTile(fbSize, clickPos(winPos, popupPos));
		}
		ImGui::EndPopup();
	}
	if (io.MouseReleased[0]) {
		m_prevMouseDownPos = {-1, -1};
		m_tileSheetEditor.releaseMouseButton();
	}
}

void TileSheetEditorImGui::drawPaletteSelector() noexcept {
	auto sctx = applicationData<studio::StudioContext>(m_ctx);
	const auto &files = sctx->project->fileList(core::FileExt_npal + 1);
	const auto first = m_selectedPaletteIdx < files.size() ?
		files[m_selectedPaletteIdx].c_str() : "";
	if (ImGui::BeginCombo("Palette", first, 0)) {
		for (auto n = 0u; n < files.size(); n++) {
			const auto selected = (m_selectedPaletteIdx == n);
			if (ImGui::Selectable(files[n].c_str(), selected) && m_selectedPaletteIdx != n) {
				m_selectedPaletteIdx = n;
				oxLogError(model()->setPalette(files[n]));
			}
			if (selected) {
				ImGui::SetItemDefaultFocus();
			}
		}
		ImGui::EndCombo();
	}
	// header
	if (ImGui::BeginTable("PaletteTable", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp)) {
		ImGui::TableSetupColumn("No.", 0, 0.45);
		ImGui::TableSetupColumn("", 0, 0.22);
		ImGui::TableSetupColumn("Color16", 0, 3);
		ImGui::TableHeadersRow();
		if (auto pal = m_tileSheetEditor.pal()) {
			for (auto i = 0u; auto c: pal->colors) {
				ImGui::PushID(static_cast<int>(i));
				// Column: color idx
				ImGui::TableNextColumn();
				const auto label = ox::BString<8>() + (i + 1);
				const auto rowSelected = i == m_tileSheetEditor.palIdx();
				if (ImGui::Selectable(label.c_str(), rowSelected, ImGuiSelectableFlags_SpanAllColumns)) {
					m_tileSheetEditor.setPalIdx(i);
				}
				// Column: color RGB
				ImGui::TableNextColumn();
				auto ic = ImGui::GetColorU32(ImVec4(redf(c), greenf(c), bluef(c), 1));
				ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ic);
				ImGui::TableNextColumn();
				ImGui::Text("(%02d, %02d, %02d)", red16(c), green16(c), blue16(c));
				ImGui::TableNextRow();
				ImGui::PopID();
				++i;
			}
		}
		ImGui::EndTable();
	}
}

ox::Error TileSheetEditorImGui::updateActiveSubsheet(const ox::String &name, int cols, int rows) noexcept {
	return model()->updateSubsheet(model()->activeSubSheetIdx(), name, cols, rows);
}

ox::Error TileSheetEditorImGui::markUnsavedChanges(const studio::UndoCommand*) noexcept {
	setUnsavedChanges(true);
	return OxError(0);
}

void TileSheetEditorImGui::SubSheetEditor::draw() noexcept {
	constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
	constexpr auto popupName = "Edit Subsheet";
	if (!m_show) {
		return;
	}
	ImGui::OpenPopup(popupName);
	const auto modSize = m_cols > 0;
	const auto popupHeight = modSize ? 125.f : 80.f;
	ImGui::SetNextWindowSize(ImVec2(235, popupHeight));
	if (ImGui::BeginPopupModal(popupName, &m_show, modalFlags)) {
		ImGui::InputText("Name", m_name.data(), m_name.cap());
		if (modSize) {
			ImGui::InputInt("Columns", &m_cols);
			ImGui::InputInt("Rows", &m_rows);
		}
		if (ImGui::Button("OK")) {
			ImGui::CloseCurrentPopup();
			m_show = false;
			inputSubmitted.emit(m_name.c_str(), m_cols, m_rows);
		}
		ImGui::SameLine();
		if (ImGui::Button("Cancel")) {
			ImGui::CloseCurrentPopup();
			m_show = false;
		}
		ImGui::EndPopup();
	}
}

}
