import { emitEvent } from "./events.js";
import { checkEquivalence } from "./ncpf-utils.js";
import { registerLoadCallback } from "./main.js";

const defaultSize = 16; // px

export let atlases = [];

registerLoadCallback(() => {
  document.body.style.setProperty("--default-size", `${defaultSize}px`);
});

async function getTextureSize(data) {
  const texture = data.texture;
  if (!texture) {
    data.size = [ 0, 0 ];
    return;
  }
  await new Promise(resolve => {
    const image = new Image();
    image.addEventListener("load", () => {
      data.size = [ image.width, image.height ];
      data.image = image;
      resolve();
    });
    image.src = `data:image/png;base64,${texture}`;
  });
}

function putTextureOnCanvas(ctx, atlasSize, data) {
  if (!data.duplicate) {
    const atlasIndexX = data.textureIndex % atlasSize;
    const atlasIndexY = (data.textureIndex - atlasIndexX) / atlasSize;
    const x = atlasIndexX * data.size[0];
    const y = atlasIndexY * data.size[1];
    ctx.drawImage(data.image, x, y);
  }

  // free up image data
  delete data.image;
  delete data.texture;
  delete data.duplicate;
}

export async function buildAtlas(data) {
  // TODO: atlas caching
  
  emitEvent("config:process:atlas:start");
  const textures = [
    ...Object.entries(data.configuration),
    ...data.addons.flatMap(a => Object.entries(a.configuration)),
  ]
      .map(([ config, data ]) =>
          [ config, data.modules["plannerator:configuration_metadata"], data ])
      // .map(v => {console.log(v); return v})
      .flatMap(([ config, addon, data ]) => Object.entries(data)
          .map(([ type, entries ]) => [ config, type, addon, entries ]))
      .flatMap(([ config, type, addon, entry ]) => {
        if (Array.isArray(entry)) {
          return [[ config, type, addon, entry ]];
        }
        if (type === "modules" && entry["plannerator:global_elements"]) {
          return [[
            config,
            "plannerator:global_elements",
            addon,
            entry["plannerator:global_elements"].elements,
          ]];
        }
        return [];
      })
      .flatMap(([ config, type, addon, list ]) => list.flatMap((entry) => {
        if (entry.modules && entry.modules["ncpf:block_recipes"]) {
          return [
              [ config, type, addon, entry ],
              ...entry.modules["ncpf:block_recipes"].recipes.map((recipe) => [ config, "ncpf:block_recipes", addon, recipe ]),
          ];
        }
        return [[ config, type, addon, entry ]];
      }))
      .filter(([,,, b]) => b && b.modules && b.modules["plannerator:texture"])
      .map(([c, t, a, b]) => ({
        configuration: c,
        configurationList: t,
        configurationAddon: a,
        texture: b.modules["plannerator:texture"].texture,
        ...(Object.fromEntries(Object.entries(b).filter(([k]) => k !== "modules")))
      }));

  if (textures.length === 0) {
    throw new UserError("Configuration processing failed - no textured blocks found");
  }

  await Promise.all(textures.map(getTextureSize));

  atlases = [];
  let offset = 0;
  for (const t of Object.values(Object.groupBy(textures, t => t.size.toString()))) {
    if (t[0].size[0] !== t[0].size[1]) {
      throw new UserError("Configuration processing failed - textures must be square");
    }

    const dedupTextureMap = new Map();
    t.forEach((texture, i) => {
      texture.index = i;
      if (dedupTextureMap.has(texture.texture)) {
        texture.textureIndex = dedupTextureMap.get(texture.texture)
        texture.duplicate = true;
      } else {
        texture.textureIndex = dedupTextureMap.size;
        dedupTextureMap.set(texture.texture, texture.textureIndex);
        texture.duplicate = false;
      }
    });

    const atlasEdgeSizeBlocks = 2 ** Math.ceil(Math.log2(Math.sqrt(t.length)));
    const atlasEdgeSize = atlasEdgeSizeBlocks * t[0].size[0];

    const canvas = new OffscreenCanvas(atlasEdgeSize, atlasEdgeSize);
    const ctx = canvas.getContext("2d");

    await Promise.all(t.map(b => putTextureOnCanvas(ctx, atlasEdgeSizeBlocks, b)));

    const blob = await canvas.convertToBlob({ type: "image/png" });
    atlases.push({
      offset,
      url: URL.createObjectURL(blob),
      metadata: t,
      size: atlasEdgeSizeBlocks,
      resolution: atlasEdgeSize,
      length: t.length,
    });
    offset += t.length;
  }

  console.log(atlases);

  emitEvent("config:process:atlas:end");
}

export function makeAtlasImage(index) {
  if (index < -1) {
    return null;
  }
  if (index === -1) {
    const div = document.createElement("div");
    div.classList.add("atlas-air");
    return div;
  }

  const originalIndex = index;
  for (const atlas of atlases) {
    if (index < atlas.length) {
      const meta = atlas.metadata[index];
      const atlasIndexX = meta.textureIndex % atlas.size;
      const atlasIndexY = (meta.textureIndex - atlasIndexX) / atlas.size;
      const size = meta.size;
      const x = atlasIndexX * size[0];
      const y = atlasIndexY * size[1];

      const div = document.createElement("div");
      div.draggable = false;
      div.dataset.originalIndex = originalIndex;
      div.classList.add("atlas-texture");
      div.style.backgroundImage = `url(${atlas.url})`;
      div.style.setProperty("--atlas-resolution", `${atlas.resolution}px`);
      div.style.setProperty("--atlas-scale", `${defaultSize / size[0]}`);
      div.style.setProperty("--raw-x", `${x}px`);
      div.style.setProperty("--raw-y", `${y}px`);
      div.style.setProperty("--size-x", `${size[0]}px`);
      div.style.setProperty("--size-y", `${size[1]}px`);

      return div;
    }
    index -= atlas.length;
  }

  return null;
}

export function getAtlasMetadata(index) {
  if (index < -1) return null;
  if (index === -1) return {};

  for (const atlas of atlases) {
    if (index < atlas.length - 1) {
      return atlas.metadata[index];
    }
    index -= atlas.length;
  }

  return null;
}

export function getAtlasIndexFromMetadata(metadata, configuration, configurationList = null) {
  let index = 0;
  for (const atlas of atlases) {
    for (const atlasItemMeta of atlas.metadata) {
      if (checkEquivalence(metadata, atlasItemMeta)
          && atlasItemMeta.configuration === configuration
          && (!configurationList || atlasItemMeta.configurationList === configurationList)) {
        return index;
      }
      index++;
    }
  }
  return -1;
}
