<template>
  <div>
    <ButtonComponent
      label="Download"
      :onClick="onClick"
      :disabled="(!codes || codes.length === 0) && !stageLoaderCode"
    />
  </div>
</template>

<script>
// Data
import gameVersions from '../data/gameVersions.json';

// Util
import { translate, translateCode } from '../i18n/localeHelper';

// customizable code
import codegens from './codes/codegen.js';

export const lskeyLDC = '@/lastDLCodes';

export default {
  props: {
    codes: { type: Array },
    stageLoaderCode: { type: String },
    format: { type: String },
    versionIdentifier: { type: String },
  },
  data() {
    return {};
  },
  methods: {
    onClick() {
      if ((!this.codes || !this.codes.length) && !this.stageLoaderCode) {
        return;
      }
      const codeList = this.codes.map((c) => ({ ...c }));

      if (this.stageLoaderCode)
        codeList.push({
          title: 'Stage List Loader',
          author: 'Noki Doki',
          date: '-',
          version: '',
          source: this.stageLoaderCode,
        });

      try {
        window._paq.push([
          'trackEvent',
          'GCT Generator',
          'Code Download',
          JSON.stringify({
            gameVersion: this.versionIdentifier,
            format: this.format,
            codes: codeList.map((code) => ({
              title: code.title,
              version: code.version,
            })),
          }),
        ]);
      } catch {}

      const version = gameVersions.find((v) => v.identifier === this.versionIdentifier).version;
      // save download code list
      try {
        const codeTitles = codeList.map(c => c.title.find(o => o.lang === 'en-US').content);
        localStorage.setItem(lskeyLDC, JSON.stringify(codeTitles));
      } catch {}

      // apply customizable codes
      for (const code of codeList) {
        const codegen = codegens[code.id];
        if (codegen) {
          code.source = codegen(this.versionIdentifier, code.source);
        }
      }

      let format;
      const formats = this.format.split('+');
      if (formats[0] === 'gci') {
        format = formats[1];
        const codeListGCT = [];
        const codeListGCI = codeList.splice(0).flatMap(c => {
          // TODO
          if (c.id === 'IntroSkip' || c.category === 'memcardpatch') {
            codeListGCT.push(c);
            return [];
          }
          return c;
        });
        // download GCI Loader + GCT only code as remaining format
        const {codes} = gameVersions.find((v) => v.identifier === this.versionIdentifier);
        const gciLoader = codes.find(code => code.id === 'GCILoader');
        codeList.push(gciLoader, ...codeListGCT);
        if (!format && codeListGCT.length) {
          const list = codeListGCT.map(c => (
            c.title.find(o => o.lang === this.$lang) ??
            c.title.find(o => o.lang === 'en-US')
          ).content).join(', ');
          alert(translate('generatorconfig.alert.gci-compatibility', this.$lang)+list);
        }
        // download GCI file
        if (codeListGCI.length) {
          this.generateGCI(codeListGCI, version);
        }
      } else {
        format = formats[0];
      }

      // 16 = 8(00D0C0DE 00D0C0DE) + 8(F0000000 00000000)
      const codeSize = codeList.reduce((a, e) => a + e.source.length, 0) / 2 + 16;
      // generate file
      switch (format) {
        case 'gct':
          this.alertGCTCodeSize(codeSize);
          this.generateGCT(codeList, version);
          break;
        case 'dolphin':
          this.alertDolphinCodeSize(codeSize);
          this.generateDolphinINI(codeList, version);
          break;
        case 'gcm':
          this.alertDolphinCodeSize(codeSize);
          this.generateCheatManagerTXT(codeList, version);
          break;
      }
    },
    alertGCTCodeSize(size) {
      if (size > 5000) {
        alert(translate('generatorconfig.alert.gct', this.$lang).replaceAll('{size}', size));
      }
    },
    alertDolphinCodeSize(size) {
      if (size > 3272) {
        // 0x3000-0x2338
        // excluding header+footer
        alert(
          translate('generatorconfig.alert.dolphin', this.$lang).replaceAll('{size}', size - 16),
        );
      }
    },
    getGCILoader() {
      const {codes} = gameVersions.find((v) => v.identifier === this.versionIdentifier);
      const code = codes.find(code => code.id === 'GCILoader');
      return [code];
    },
    generateGCT(codes, version) {
      let code = '00D0C0DE00D0C0DE';
      codes.forEach((c) => (code += c.source));
      code += 'F000000000000000';

      let rawData = new Uint8Array(code.length / 2);

      for (let x = 0; x < rawData.length; x++) {
        rawData[x] = parseInt(code.substr(x * 2, 2), 16);
      }

      this.downloadFile(rawData, `${version}.gct`);
    },
    generateDolphinINI(codes, version) {
      let data = 'Paste the following on top of your games .ini file:\r\n[Gecko]';

      codes.forEach((code) => {
        const codeTitle =
          typeof code.title === 'string' ? code.title : translateCode(code, this.$lang).title;

        data += `\r\n$${codeTitle} (${code.date}) [${code.author}]\r\n`;
        data += code.source
          .match(/.{8}/g)
          .join(' ')
          .replace(/(.{17})./g, '$1\r\n');
      });

      this.downloadFile(data, `${version}.txt`);
    },
    generateCheatManagerTXT(codes, version) {
      let data = `${version}\r\nSuper Mario Sunshine`;

      codes.forEach((code) => {
        const codeTitle =
          typeof code.title === 'string' ? code.title : translateCode(code, this.$lang).title;

        data += `\r\n\r\n${codeTitle} (${code.date}) [${code.author}]\r\n`;
        data += code.source
          .match(/.{8}/g)
          .join(' ')
          .replace(/(.{17})./g, '$1\r\n');
      });

      this.downloadFile(data, `${version}.txt`);
    },
    generateGCI(codes, version) {
      let code = '';
      codes.forEach((c) => (code += c.source));
      code += 'C0000000000000023C60817F81E317FC7DE478504E800020'; // return
      const codeSize = code.length>>1;

      const fileName = `GCT_${this.versionIdentifier}`; // GMSJ0A
      const blockCount = 6; // Math.ceil(codeSize/0x2000); // TODO
      const headSize = 0x40;
      const gciSize = headSize+0x2000*blockCount;
      const rawData = new Uint8Array(gciSize);

      for (let iD=headSize, iC=0; iC<code.length; iD++, iC+=2) {
        rawData[iD] = parseInt(code.slice(iC, iC+2), 16);
      }

      // game id
      [...new TextEncoder().encode(version), 0xff, 0x00].forEach((e, i) => rawData[i] = e);
      // file name
      [...new TextEncoder().encode(fileName)].forEach((e, i) => rawData[0x8+i] = e);
      // block count
      rawData[0x39] = blockCount;
      // ff*6
      for (let i=0x3A; i<0x40; i++) rawData[i] = 0xff;

      this.downloadFile(rawData, `01-${version.slice(0, 4)}-${fileName}.gci`);
    },
    downloadFile(data, filename) {
      var file = new Blob([data], {
        type: 'application/octet-stream',
      });

      if (window.navigator.msSaveOrOpenBlob) window.navigator.msSaveOrOpenBlob(file, filename);
      else {
        var a = document.createElement('a'),
          url = window.URL.createObjectURL(file);
        a.href = url;
        a.download = filename;
        a.click();
        setTimeout(function () {
          window.URL.revokeObjectURL(url);
        }, 500);
      }
    },
  },
};
</script>