// Jo, šachec, :sjetec:

import { APIEmbed, Message } from "discord.js";
import { Modul } from "../utils/types";
import { fokinLookupTable, klikance, pocetKeStrane } from "../utils/sachyEtc";
import { emiter, sachyDomena } from "../utils/sachyServer";
import { messageReply } from "../utils/utils";

const hry = new Map<number, Hra>(); // gameID -> Hra
const hraci = new Map<string, number>(); // hrac -> gameID
let nextGameID = 0;

const smery = [-8, 1, 8, -1, -7, 9, 7, -9];

const mkPis = (barva: Figurka["barva"], typ: Figurka["typ"]) => ({ barva, typ, netahnuta: true, enPassantovatelna: false });

function createGame(gameID: number, bilej: string, cernej: string, mes: Message) {

  const deska: Deska = [...new Array(64)];

  for (let i = 0; i < 8; i++) {
    deska[i + 8] = mkPis("cerna", "pawn");
    deska[i + 48] = mkPis("bila", "pawn");
  }

  deska[0] = mkPis("cerna", "rook");
  deska[7] = mkPis("cerna", "rook");
  deska[56] = mkPis("bila", "rook");
  deska[63] = mkPis("bila", "rook");

  deska[1] = mkPis("cerna", "knight");
  deska[6] = mkPis("cerna", "knight");
  deska[57] = mkPis("bila", "knight");
  deska[62] = mkPis("bila", "knight");

  deska[2] = mkPis("cerna", "bishop");
  deska[5] = mkPis("cerna", "bishop");
  deska[58] = mkPis("bila", "bishop");
  deska[61] = mkPis("bila", "bishop");

  deska[4] = mkPis("cerna", "king");
  deska[60] = mkPis("bila", "king");

  deska[3] = mkPis("cerna", "queen");
  deska[59] = mkPis("bila", "queen");

  const hra: Hra = {
    id: gameID,
    bila: bilej,
    cerna: cernej,
    deska,
    hraje: "bila",
    tahy: [],
    message: mes
  };

  hry.set(gameID, hra);

  return hra;
}

function jeCheck(barva: Hra["hraje"], deska: Deska) {

  let check = false;
  const index = deska.findIndex(pis => pis?.typ == "king" && pis.barva == barva);

  // Od tahačů
  for (let smer = 0; smer < 8; smer++) {
    for (let n = 1; n <= pocetKeStrane[index][smer]; n++) {

      const target = index + smery[smer] * n;
      const pisNaTargetu = deska[target];

      if (pisNaTargetu?.barva != barva && [(smer < 4 ? "rook" : "bishop"), "queen"].includes(pisNaTargetu?.typ || "")) return true;

      if (pisNaTargetu) break;
    }
  }

  // Od koliků
  [[-1, 2], [1, 2], [-2, 1], [2, 1], [-2, -1], [2, -1], [-1, -2], [1, -2]].forEach(smery => {

    const x = smery[0] > 0 ? 1 : 3;
    const y = smery[1] > 0 ? 2 : 0;

    // Estli je mimo board
    if (pocetKeStrane[index][x] < Math.abs(smery[0]) || pocetKeStrane[index][y] < Math.abs(smery[1])) return;

    const target = index + (8 * smery[1] + smery[0]);
    const pis = deska[target];

    // Estli už tam felí náš
    if (pis?.barva == barva) return;

    if (pis?.typ == "knight") check = true;
  });

  // od pawnů
  [7, 9].forEach(i => {
    const pis = deska[index - i];
    if (pis?.typ == "pawn" && pocetKeStrane[index][i == 7 ? 1 : 3] > 0 && pis.barva != barva) check = true;
  });

  // od králika
  for (let smer = 0; smer < 8; smer++) {
    if (pocetKeStrane[index][smer] == 0) continue;

    if (deska[index + smery[smer]]?.typ == "king") return true;
  }

  return check;
}

function tahyProJezdiciPisi(index: number, pis: "queen" | "bishop" | "rook", deska: Deska) {

  const tahy: number[] = [];
  const barva = deska[index]!.barva;

  const startSmer = pis == "bishop" ? 4 : 0;
  const endSmer = pis == "rook" ? 4 : 8;

  for (let smer = startSmer; smer < endSmer; smer++) {
    for (let n = 1; n <= pocetKeStrane[index][smer]; n++) {

      const target = index + smery[smer] * n;
      const pisNaTargetu = deska[target];

      if (pisNaTargetu?.barva == barva) break;

      tahy.push(target);

      if (pisNaTargetu && pisNaTargetu.barva != barva) break;
    }
  }

  return tahy;
}

function tahyProKoniky(index: number, deska: Deska) {

  const tahy: number[] = [];
  const barva = deska[index]!.barva;

  [[-1, 2], [1, 2], [-2, 1], [2, 1], [-2, -1], [2, -1], [-1, -2], [1, -2]].forEach(smery => {

    const x = smery[0] > 0 ? 1 : 3;
    const y = smery[1] > 0 ? 2 : 0;

    // Estli je mimo board
    if (pocetKeStrane[index][x] < Math.abs(smery[0]) || pocetKeStrane[index][y] < Math.abs(smery[1])) return;

    const target = index + (8 * smery[1] + smery[0]);
    const pis = deska[target];

    // Estli už tam felí náš
    if (pis?.barva == barva) return;

    tahy.push(target);
  });

  return tahy;
}

function tahyProPawny(index: number, deska: Deska) {

  const tahy: number[] = [];
  const barva = deska[index]!.barva;

  // Takey
  if (index % 8 != 0 && deska[index - 9] && deska[index - 9]!.barva != barva)
    tahy.push(index - 9);

  if (index % 8 != 7 && deska[index - 7] && deska[index - 7]!.barva != barva)
    tahy.push(index - 7);

  // en croassant
  if (pocetKeStrane[index][3] > 0 && deska[index - 1]?.enPassantovatelna)
    tahy.push(index - 9);

  if (pocetKeStrane[index][1] > 0 && deska[index + 1]?.enPassantovatelna)
    tahy.push(index - 7);

  // Blokáda
  if (deska[index - 8])
    return tahy;

  tahy.push(index - 8);

  // Double-advance
  if (deska[index]?.netahnuta && !deska[index - 16])
    tahy.push(index - 16);

  return tahy;
}

function tahyProKrale(index: number, deska: Deska) {

  const tahy: number[] = [];
  const barva = deska[index]!.barva;

  for (let smer = 0; smer < 8; smer++) {
    const target = index + smery[smer];
    if (pocetKeStrane[index][smer] != 0 && deska[target]?.barva != barva) tahy.push(target);
  }

  // Castling
  if (jeCheck(barva, deska)) return tahy;
  for (let strana = 0; strana < 2; strana++) {

    const offset = (barva == "bila" ? (strana ? 3 : -4) : (strana ? 4 : -3));
    const rook = deska[index + offset];

    if (deska[index]!.netahnuta && rook?.typ == "rook" && rook.netahnuta) {
      let muze = true;
      for (let i = (strana > 0 ? 1 : -1); (strana > 0 ? i < offset : i > offset); strana > 0 ? i++ : i--) {
        if (deska[index + i]) {
          muze = false;
          break;
        }

        if (Math.abs(i) == 3) continue;

        const novaDeska = [...deska];
        novaDeska[index + i] = deska[index];
        novaDeska[index] = undefined;
        if (jeCheck(barva, novaDeska))
          muze = false;
      }

      if (muze) tahy.push(index + (2 * (offset > 0 ? 1 : -1)));
    }
  }

  return tahy;
}

function legalniTahy(index: number, deska: Deska) {

  let tahy: number[];
  const pis = deska[index]!;

  if (pis.typ == "queen" || pis.typ == "bishop" || pis.typ == "rook")
    tahy = tahyProJezdiciPisi(index, pis.typ, deska);

  else if (pis.typ == "knight")
    tahy = tahyProKoniky(index, deska);

  else if (pis.typ == "pawn")
    tahy = tahyProPawny(index, deska);

  else
    tahy = tahyProKrale(index, deska);


  // Otestovat jestli jsou fakt legální
  for (let i = 0; i < tahy.length; i++) {
    const tah = tahy[i];

    if (jeCheck(pis.barva, udelejTah(index, tah, deska))) {
      tahy.splice(i, 1);
      i--;
    }
  }

  return tahy;
}

function udelejTah(z: number, kam: number, deska: Deska, fakt?: true) {

  const mojeDeska = [...deska];
  const pis = mojeDeska[z]!;

  if (fakt) pis.netahnuta = false;

  mojeDeska[kam] = pis;
  mojeDeska[z] = undefined;

  // croassant
  if (pis.typ == "pawn" && mojeDeska[kam + 8]?.enPassantovatelna)
    mojeDeska[kam + 8] = undefined;

  if (fakt)
    mojeDeska.forEach(pole => {
      if (!pole) return;

      pole.enPassantovatelna = false;
    });

  if (fakt && pis.typ == "pawn" && z - kam == 16) pis.enPassantovatelna = true;


  // Castling
  const rozdil = z - kam;
  if (pis.typ == "king" && Math.abs(rozdil) == 2) {
    const i = (rozdil > 0 ? 56 : 63);
    mojeDeska[(rozdil > 0 ? kam + 1 : kam - 1)] = mojeDeska[i];
    mojeDeska[i] = undefined;
  }

  return mojeDeska;
}

function renderHra(hra: Hra, konec?: true) {
  const deska = [...hra.deska];
  const vybranejPis = hra.vybranejPis;

  const policka: Policko[] = [];

  hra.tahy = [];

  if (typeof vybranejPis == "number") hra.tahy = legalniTahy(vybranejPis, deska);

  for (let i = 0; i < 64; i++) {

    const figurka = deska[i];
    const jeTah = hra.tahy.includes(i);

    const cernyPozadi = (Math.floor(i / 8) + (i % 8)) % 2;

    if (!figurka) {

      policka.push(jeTah
        ? { typ: "tahnutelne", emoutId: cernyPozadi ? "1066376051422404628" : "1066376343807336488" }
        : { typ: "prazdne", barva: cernyPozadi ? "cerna" : "bila" }
      );

    } else {

      let klic = (cernyPozadi ? "g" : "w") + (figurka.barva == "bila" ? "w" : "b") + figurka.typ;
      if (jeTah) klic += "t";
      else if (figurka.typ == "king" && figurka.barva == hra.hraje && jeCheck(figurka.barva, deska)) klic += "c";
      const emoutId = fokinLookupTable[klic];

      const tahnutelny = !konec && figurka.barva == hra.hraje || jeTah;
      policka.push({ emoutId, typ: tahnutelny ? "tahnutelne" : "figurka" });

    }

  }

  return policka;
}

function generateGameEmbed(hra: Hra, konec?: true) {

  const embed: APIEmbed = {
    fields: [
      {
        name: "Sachy sou",
        value: "",
        inline: true
      },
      {
        name: "gamesa vole",
        value: "",
        inline: true
      }
    ]
  };

  const policka = renderHra(hra, konec);

  for (let i = 0; i < 64; i++) {
    const policko = policka[i];
    const field = embed.fields![(i % 8) < 4 ? 0 : 1];

    if (policko.typ == "prazdne")
      field.value += policko.barva == "bila" ? "⬜" : "🟩";
    else {
      const emout = `<:h:${policko.emoutId}>`;
      field.value += policko.typ == "figurka" ? emout : `[${emout}](http://${sachyDomena}/${klikance[i]})`;
    }

    if ((i % 8 == 3 || i % 8 == 7)) field.value += "\n";
  }

  if (embed.fields![0].value.length > 1024 || embed.fields![1].value.length > 1024) {
    embed.fields = [
      {
        name: "a je to pici!",
        value: `Tuta pozice by vobsahovala moc vodkazu a diky pice diskordu se teda neda vykreslit. Tuten tah budes muset bohuzel vodehrat [na webu](http://${sachyDomena}/play).`
      }
    ];
  }

  return embed;

}

emiter.on("render", (hrac, respond) => {
  const gameID = hraci.get(hrac)!;
  if (!gameID) return respond("Však vůbec nehraješ ty magore", true);
  const hra = hry.get(gameID)!;
  const hraje = hra.hraje == (hra.bila == hrac ? "bila" : "cerna");

  const policka = renderHra(hra);
  let html = "<tr>";


  for (let i = 0; i < 64; i++) {
    const policko = policka[i];

    if (policko.typ == "prazdne")
      html += policko.barva == "bila" ? `<td><img class="nic" src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/2b1c.svg"></td>` : `<td><img class="nic" src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f7e9.svg"></td>`;
    else {
      const obrazek = `<img src="https://cdn.discordapp.com/emojis/${policko.emoutId}.webp">`;
      html += (policko.typ == "figurka" || !hraje) ? `<td>${obrazek}</td>` : `<td><a href="http://${sachyDomena}/play/${klikance[i]}">${obrazek}</a></td>`;
    }

    if (i % 8 == 7) html += "</tr>";
  }

  respond(html);

});

function konec(mes: Message, hra: Hra, duvod: string) {
  mes.edit({ content: `hra zkoncila ${duvod}`, embeds: [generateGameEmbed(hra, true)] });

  const gameID = hra.id;

  hraci.delete(hra.bila);
  hraci.delete(hra.cerna);
  hry.delete(gameID);
}

function prekreslitHru(hra: Hra) {
  const mes = hra.message;

  // Checkmate
  if (jeCheck(hra.hraje, hra.deska)) {
    let mat = true;

    hra.deska.forEach((pole, i) => {
      if (!pole || pole.barva != hra.hraje) return;

      if (legalniTahy(i, hra.deska).length != 0) mat = false;
    });

    if (mat)
      return konec(mes, hra, `${hra.hraje} je zasachmatovana`);

  }

  // Stalemate
  let pat = true;
  hra.deska.forEach((pole, i) => {
    if (!pole || pole.barva != hra.hraje) return;

    if (legalniTahy(i, hra.deska).length != 0) pat = false;
  });

  if (pat)
    return konec(mes, hra, `achylova sytuace`);

  mes.edit({ content: "", embeds: [generateGameEmbed(hra)] });
}

emiter.on("tah", (hrac, policko, respond, promo) => {
  const gameID = hraci.get(hrac);
  if (!gameID) return respond("Však vůbec nehraješ ty magore");

  const hra = hry.get(gameID)!;
  const barva = hra.bila == hrac ? "bila" : "cerna";
  if (hra.hraje != barva) return respond("Teď nehraješ ty ty magore");

  const index = klikance.findIndex(e => e == policko);

  if (index == -1) return respond("Co to meleš za hovna?");

  // Kliknul na stejnej pis
  if (hra.vybranejPis == index) {

    hra.vybranejPis = undefined;

    prekreslitHru(hra);
    return respond();
  }

  // Kliknul nějakej pis
  const pis = hra.deska[index];
  if (pis && pis.barva == barva) {

    hra.vybranejPis = index;

    prekreslitHru(hra);
    return respond();
  }

  // Táhnul
  if (typeof hra.vybranejPis == "number" && hra.tahy.includes(index)) {

    // Promotuje
    if (hra.deska[hra.vybranejPis!]?.typ == "pawn" && index < 8) {
      if (!promo) return respond("--promote--");

      const promoNa = promo == "q" ? "queen" :
        promo == "r" ? "rook" :
          promo == "n" ? "knight" :
            promo == "b" ? "bishop" : undefined;

      if (!promoNa) return respond("Co to meleš za hovna?");

      hra.deska[hra.vybranejPis] = mkPis(barva, promoNa);
    }

    hra.deska = udelejTah(hra.vybranejPis, index, hra.deska, true);

    hra.vybranejPis = undefined;

    hra.hraje = barva == "bila" ? "cerna" : "bila";

    hra.deska = hra.deska.reduce<Deska>((acc, item) => [item].concat(acc), []);

    prekreslitHru(hra);
    return respond();
  }

  respond("cos to tam zandal");
});

const exp: Modul = {
  more_komandy: {

    sachy: {
      DMUnsafe: true,
      run: async mes => {

        const druhej = mes.mentions.members?.first()?.id;
        if (!druhej) return "no sam asi hrat nebudez vet";

        for (const player of [mes.author.id, druhej]) {
          if (hraci.has(player)) return "zakaz symultanki";
        }

        const message = await messageReply(mes, "hraj zopiciva");

        const hra = createGame(++nextGameID, mes.author.id, druhej, message);
        hraci.set(mes.author.id, nextGameID);
        hraci.set(druhej, nextGameID);

        message.edit({ content: "", embeds: [generateGameEmbed(hra)] });

      }
    },

    jasevzdavam: mes => {

      const gameID = hraci.get(mes.author.id);
      if (!gameID) return "vsak nehrajes magore";

      const hra = hry.get(gameID)!;

      konec(hra.message, hra, `${mes.author} je pusi`);
    }

  }
};

module.exports = exp;

type Hra = {
  id: number;
  bila: string;
  cerna: string;
  deska: Deska;
  hraje: Figurka["barva"];
  tahy: number[];
  vybranejPis?: number;
  message: Message;
};

type Deska = (Figurka | undefined)[];

type Figurka = {
  barva: "bila" | "cerna";
  typ: "pawn" | "rook" | "knight" | "bishop" | "queen" | "king";
  netahnuta: boolean;
  enPassantovatelna: boolean;
};

type Policko = {
  typ: "prazdne";
  barva: Figurka["barva"];
} | {
  typ: "figurka" | "tahnutelne";
  emoutId: string;
};
