#include "io/LayoutXml.h"
#include <fstream>
#include <sstream>
#include <iomanip>

// Minimal XML writer/reader — no external dependencies

namespace TD {

// ── Helpers ───────────────────────────────────────────────────────────────────
static std::string esc(const std::string& s) {
    std::string r;
    for (char c : s) {
        if      (c == '<')  r += "&lt;";
        else if (c == '>')  r += "&gt;";
        else if (c == '&')  r += "&amp;";
        else if (c == '"')  r += "&quot;";
        else                r += c;
    }
    return r;
}

static std::string attr(const std::string& k, const std::string& v) {
    return " " + k + "=\"" + esc(v) + "\"";
}
static std::string attr(const std::string& k, double v) {
    std::ostringstream ss; ss << std::fixed << std::setprecision(4) << v;
    return attr(k, ss.str());
}
static std::string attr(const std::string& k, int v) {
    return attr(k, std::to_string(v));
}

// Simple XML value extractor
static std::string xmlAttr(const std::string& line, const std::string& key) {
    std::string search = key + "=\"";
    auto pos = line.find(search);
    if (pos == std::string::npos) return "";
    pos += search.size();
    auto end = line.find('"', pos);
    if (end == std::string::npos) return "";
    return line.substr(pos, end - pos);
}

// ── Write pieces ──────────────────────────────────────────────────────────────
static void WritePieces(std::ostream& f,
                        const std::vector<MC::PlacedPiece>& pieces,
                        const std::string& indent)
{
    for (const auto& p : pieces) {
        f << indent << "<piece"
          << attr("id",      p.id)
          << attr("cat",     p.catalogNumber)
          << attr("x",       p.transform.pos.x)
          << attr("y",       p.transform.pos.y)
          << attr("heading", p.transform.heading)
          << attr("sel",     p.selected ? 1 : 0)
          << ">\n";
        for (int i = 0; i < (int)p.joints.size(); ++i) {
            if (p.joints[i].pieceId != -1) {
                f << indent << "  <joint"
                  << attr("conn",   i)
                  << attr("target", p.joints[i].pieceId)
                  << attr("tconn",  p.joints[i].connIdx)
                  << "/>\n";
            }
        }
        f << indent << "</piece>\n";
    }
}

// ── Read pieces ───────────────────────────────────────────────────────────────
static void ReadPieces(std::istream& in,
                       std::vector<MC::PlacedPiece>& pieces,
                       int& maxId)
{
    std::string line;
    MC::PlacedPiece* cur = nullptr;

    while (std::getline(in, line)) {
        if (line.find("</pieces>") != std::string::npos ||
            line.find("</block>")  != std::string::npos ||
            line.find("</layout>") != std::string::npos) break;

        if (line.find("<piece ") != std::string::npos) {
            MC::PlacedPiece p;
            p.id                  = std::stoi(xmlAttr(line, "id"));
            p.catalogNumber       = xmlAttr(line, "cat");
            p.transform.pos.x     = std::stod(xmlAttr(line, "x"));
            p.transform.pos.y     = std::stod(xmlAttr(line, "y"));
            p.transform.heading   = std::stod(xmlAttr(line, "heading"));
            p.selected            = xmlAttr(line, "sel") == "1";
            maxId = std::max(maxId, p.id);
            pieces.push_back(std::move(p));
            cur = &pieces.back();
        } else if (line.find("<joint ") != std::string::npos && cur) {
            int conn   = std::stoi(xmlAttr(line, "conn"));
            int target = std::stoi(xmlAttr(line, "target"));
            int tconn  = std::stoi(xmlAttr(line, "tconn"));
            if (conn >= (int)cur->joints.size())
                cur->joints.resize(conn + 1, {-1, -1});
            cur->joints[conn] = { target, tconn };
        }
    }
}

// ── Save ──────────────────────────────────────────────────────────────────────
std::string LayoutXml::Save(const MC::CanvasState& state,
                             const std::string& filePath,
                             const std::string& layoutName)
{
    std::ofstream f(filePath);
    if (!f) return "Cannot open file: " + filePath;

    f << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
    f << "<layout" << attr("name", layoutName)
      << attr("version", "1")
      << ">\n";
    f << "  <pieces>\n";
    WritePieces(f, state.pieces, "    ");
    f << "  </pieces>\n";
    f << "</layout>\n";
    return "";
}

// ── Load ──────────────────────────────────────────────────────────────────────
std::string LayoutXml::Load(MC::CanvasState& state,
                             const std::string& filePath,
                             std::string& layoutNameOut)
{
    std::ifstream f(filePath);
    if (!f) return "Cannot open file: " + filePath;

    state.pieces.clear();
    layoutNameOut.clear();
    int maxId = 0;

    std::string line;
    while (std::getline(f, line)) {
        if (line.find("<layout ") != std::string::npos)
            layoutNameOut = xmlAttr(line, "name");
        if (line.find("<pieces>") != std::string::npos)
            ReadPieces(f, state.pieces, maxId);
    }

    state.nextId = maxId + 1;
    return "";
}

// ── SaveBlock ─────────────────────────────────────────────────────────────────
std::string LayoutXml::SaveBlock(const MC::CanvasState& state,
                                  const std::string& filePath,
                                  const std::string& blockName)
{
    auto selected = state.Selected();
    if (selected.empty()) return "No pieces selected";

    std::ofstream f(filePath);
    if (!f) return "Cannot open file: " + filePath;

    f << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
    f << "<block" << attr("name", blockName) << attr("version", "1") << ">\n";

    // Write only selected pieces
    std::vector<MC::PlacedPiece> sel;
    for (auto* p : selected) sel.push_back(*p);
    WritePieces(f, sel, "  ");
    f << "</block>\n";
    return "";
}

// ── LoadBlock ─────────────────────────────────────────────────────────────────
std::string LayoutXml::LoadBlock(const std::string& filePath,
                                  std::string& blockNameOut,
                                  std::vector<MC::PlacedPiece>& piecesOut)
{
    std::ifstream f(filePath);
    if (!f) return "Cannot open file: " + filePath;

    blockNameOut.clear();
    piecesOut.clear();
    int maxId = 0;

    std::string line;
    while (std::getline(f, line)) {
        if (line.find("<block ") != std::string::npos)
            blockNameOut = xmlAttr(line, "name");
        if (line.find("<piece ") != std::string::npos) {
            // Put line back by seeking — instead just re-parse inline
            std::istringstream ss(line);
            // parse the piece line directly
            MC::PlacedPiece p;
            p.id                = std::stoi(xmlAttr(line, "id"));
            p.catalogNumber     = xmlAttr(line, "cat");
            p.transform.pos.x   = std::stod(xmlAttr(line, "x"));
            p.transform.pos.y   = std::stod(xmlAttr(line, "y"));
            p.transform.heading = std::stod(xmlAttr(line, "heading"));
            maxId = std::max(maxId, p.id);
            piecesOut.push_back(std::move(p));
        } else if (line.find("<joint ") != std::string::npos && !piecesOut.empty()) {
            int conn   = std::stoi(xmlAttr(line, "conn"));
            int target = std::stoi(xmlAttr(line, "target"));
            int tconn  = std::stoi(xmlAttr(line, "tconn"));
            auto& cur = piecesOut.back();
            if (conn >= (int)cur.joints.size())
                cur.joints.resize(conn + 1, {-1, -1});
            cur.joints[conn] = { target, tconn };
        }
    }
    return "";
}

} // namespace TD
