import GlobalEvents from "/app/js/common/EventCaller.js";
import Utils from "/app/js/common/Utils.js";
import JSONSchema from "/app/js/common/JSONSchema.js";
import EditableText from "/app/js/ui/EditableText.js";
import Renderer from "/app/js/ui/Renderer.js";
import NESNameTable from "/app/js/models/NESNameTable.js";
import NESPalette from "/app/js/models/NESPalette.js";
import CTRLBanksStore from "/app/js/ctrls/CTRLBanksStore.js";

const NTLI_TEMPLATE = "nametable-list-item-template";
const NTLI_CANVAS = "nametable-img";
const NTLI_TITLE = "title";
const NTLI_SELECTED = "list-item-selected";


var Nametables = {};
var CurrentNT = "";


const SCHEMA_ID="http://nespaint/NametableStoreSchema.json";
JSONSchema.add({
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": SCHEMA_ID,
  "type": "array",
  "items":{
    "type": "object",
    "properties":{
      "name":{
        "type": "string",
        "minLength": 1
      },
      "data":{
        "type": "string",
        "media": {
          "binaryEncoding": "base64"
        }
      },
      "bank":{
        "type": "string",
        "minLength": 1
      }
    },
    "required":["name", "data"]
  }
});


function HANDLE_NametableClick(e){
  var name = this.getAttribute("ntname");
  if (name !== CurrentNT){
    if (CurrentNT !== "")
      Nametables[CurrentNT].el.classList.remove(NTLI_SELECTED);
    CurrentNT = name;
    Nametables[CurrentNT].el.classList.add(NTLI_SELECTED);
    GlobalEvents.emit("change_surface", Nametables[CurrentNT].nametable);
  }
}


function SetElNTName(el, name){
  var et = new EditableText(el, "title");
  et.listen("value_change", (v) => {el.setAttribute("ntname", v);});
  et.value = name;
  return et;
}


var RenderNametableToEl = Utils.throttle(function(el, nametable){
  var cnv = el.querySelector("." + NTLI_CANVAS);
  var ctx = cnv.getContext("2d");

  Renderer.renderToFit(nametable, ctx);
}, 500); // Only update twice a second.


function HANDLE_NametableDataChange(nametable, e){
  RenderNametableToEl(this, nametable);
}

function ConnectElementToNametable(el, nametable){
  nametable.listen("data_changed", HANDLE_NametableDataChange.bind(el, nametable));
}


function CreateNametableDOMEntry(name, nametable){
  var baseel = document.querySelector("." + NTLI_TEMPLATE);
  if (!baseel){
    console.log("WARNING: Failed to find nametable list item template.");
    return null;
  }
  var el = baseel.cloneNode(true);
  el.classList.remove(NTLI_TEMPLATE);
  el.classList.remove("hidden");
  el.setAttribute("ntname", name);
  ConnectElementToNametable(el, nametable); 
  el.addEventListener("click", HANDLE_NametableClick);
  baseel.parentNode.appendChild(el);
  setTimeout(()=>{
    RenderNametableToEl(el, nametable);
  }, 500); // Make the render call in about a half second. Allow DOM time to catch up?
  return el;
}


class CTRLNametablesStore{
  constructor(){
    var HANDLE_ChangeSurface = function(surf){
      if (!(surf instanceof NESNameTable)){
        if (CurrentNT !== ""){
          Nametables[CurrentNT].el.classList.remove(NTLI_SELECTED);
          CurrentNT = "";
        }
      } else {
        if (Nametables.length <= 0 || (CurrentNT !== "" && Nametables[CurrentNT].nametable !== surf)){
          console.log("WARNING: Nametable object being set outside of Nametables Store.");
        }
      }
    }
    GlobalEvents.listen("change_surface", HANDLE_ChangeSurface);


    GlobalEvents.listen("ntstore-add", (function(ev){
      GlobalEvents.emit("modal-close");
      var e = document.querySelector(".nt-store-add");
      if (e){
        var eform = e.querySelector("form");
        var einput = e.querySelector('input[name="storeitemname"]');
        if (eform && einput){
          var name = einput.value;
          eform.reset();
          this.createNametable(name);
          this.activateNametable(name);
        } 
      }
    }).bind(this));

    GlobalEvents.listen("ntstore-remove", (function(e){
      if (CurrentNT !== "")
        this.removeNametable(CurrentNT);
    }).bind(this));
  }

  get length(){
    return Object.keys(Nametables).length;
  }

  get obj(){
    var data = [];
    Object.keys(Nametables).forEach((key) => {
      if (Nametables.hasOwnProperty(key)){
        var jdata = {
          name: key,
          data: Nametables[key].nametable.base64
        };
        if (Nametables[key].nametable.bank !== null){
          var bankname = CTRLBanksStore.getBankName(Nametables[key].nametable.bank);
          if (bankname !== null)
            jdata.bank = bankname;
        }
        data.push(jdata);
      }
    });
    return data;
  }

  set obj(d){
    var validator = JSONSchema.getValidator(SCHEMA_ID);
    if (validator !== null && validator(d)){
      this.clear();
      d.forEach((item) => {
        this.createNametable(item.name, item.data);
        if ("bank" in item){
          var bnk = CTRLBanksStore.getBank(item.bank);
          if (bnk !== null && item.name in Nametables)
            Nametables[item.name].nametable.bank = bnk;
        }
      });
    } else {
      var errs = JSONSchema.getLastErrors();
      if (errs !== null){
        console.log(errs);
      }
      throw new Error("Object failed to validate against NametableStoreSchema.");
    }
  }

  get json(){ 
    return JSON.stringify(this.obj);
  }

  set json(j){
    try {
      this.obj = JSON.parse(j);
    } catch (e) {
      throw e;
    }
  }

  get currentNametable(){
    return (CurrentNT === "") ? null : Nametables[CurrentNT].nametable;
  }

  get currentNTName(){
    return CurrentNT;
  }

  get keys(){
    return Object.keys(Nametables);
  }

  initialize(){
    //if (this.length <= 0){
    //  this.createNametable("Nametable");
    //}
    return this;
  }


  createNametable(name, bbase64){
    if (!(name in Nametables)){
      var nametable = new NESNameTable();
      if (typeof(bbase64) === "string"){
        try {
          nametable.base64 = bbase64; 
        } catch (e) {
          console.log("Failed to create Nametable. " + e.toString());
          nametable = null;
        }
      }
      if (nametable !== null){
        var el = CreateNametableDOMEntry(name, nametable);
        if (el){
          var elname = SetElNTName(el, name);
          Nametables[name] = {nametable:nametable, el:el, elname:elname};

          if (this.length <= 1){
            Nametables[name].el.click();
          }
        }
      }
    }
    return this;
  }


  removeNametable(name){
    if (name in Nametables){
      if (name === CurrentNT){
        var keys = Object.keys(Nametables);
        if (keys.length > 1){
          CurrentNT = (keys[0] !== name) ? keys[0] : keys[1];
        } else {
          CurrentNT = "";
        }
      }
      Nametables[name].el.parentNode.removeChild(Nametables[name].el);
      delete Nametables[name];
      if (CurrentNT !== ""){
        Nametables[CurrentNT].el.click();
      } else {
        GlobalEvents.emit("change_surface", null);
      }
    }
    return this;
  }

  renameNametable(name, newname){
    if ((name in Nametables) && !(newname in Nametables)){
      Nametables[newname] = Nametables[name];
      Nametables[newname].elname.value = newname;
      delete Nametables[name];
    }
    return this;
  }

  activateNametable(name){
    if (CurrentNT !== name && (name in Nametables)){
      Nametables[name].el.click();
    }
    return this;
  }

  getNametableName(nametable){
    if (!(nametable instanceof NESNameTable))
      throw new TypeError("Expected NESNameTable instance.");
    var keys = Object.keys(Nametables);
    for (let i=0; i < keys.length; i++){
      if (Nametables[keys[i]].nametable.eq(nametable))
        return keys[i];
    }
    return null;
  }

  getNametable(name){
    return (name in Nametables) ? Nametables[name].nametable : null;
  }

  clear(){
    Object.keys(Nametables).forEach((item) => {
      Nametables[item].el.parentNode.removeChild(Nametables[item].el);
    });
    Nametables = {};
    if (CurrentNT !== ""){
      CurrentNT = "";
      GlobalEvents.emit("change_surface", null);
    }
  }
}


const instance = new CTRLNametablesStore();
export default instance;