// Init EJS stuff
class HomeplayEmulator {
  scriptElement = null;
  initInterval = null;
  oldGetRetroArchCfg = null;
  gameStarted = false;

  saveUpdates = false;
  saveInterval = null;
  saveLock = false;

  constructor() {
    // Set up the EJS stuff
    window.EJS_player = "#game";
    window.EJS_pathtodata = "https://cdn.emulatorjs.org/stable/data/";
    window.EJS_language = "en-US";
    window.EJS_volume = 0;
    window.EJS_startOnLoaded = true;
    window.EJS_backgroundColor = '#000';

    window.EJS_Buttons = {
      playPause: false,
      restart: false,
      mute: false,
      settings: false,
      fullscreen: false,
      saveState: false,
      loadState: false,
      screenRecord: false,
      gamepad: false,
      cheat: false,
      volume: false,
      saveSavFiles: false,
      loadSavFiles: false,
      quickSave: false,
      quickLoad: false,
      screenshot: false,
      cacheManager: false,
      exitEmulation: false
    }

    window.EJS_defaultOptions = {
    
    };

    // Bind events
    window.EJS_ready = this.onEJSReady;
    window.EJS_onGameStart = this.onEJSGameStart;
    window.EJS_onGameEnd = this.onEJSGameEnd;
    window.EJS_onSaveState = this.onEJSSaveState;
    window.EJS_onLoadState = this.onEJSLoadState;
    window.onmessage = this.onMessage;

    // Begin the init interval
    this.initInterval = setInterval(() => {
      this.send({ message: 'iframe_loaded' });
    }, 1000);

    // Check for save updates
    this.saveInterval = setInterval(async () => {
      if(this.saveLock) return;
      this.saveLock = true;
      try {
        this.saveUpdates = await this.hasSaveUpdates();
        if(this.saveUpdates) {
          console.log('Saving');
          await this.save();
        }
      } catch(e) {
        console.error('Error checking for save updates', e);
      } finally {
        this.saveLock = false;
      }
    }, 2000);
  }

  send = data => {
    window.parent.postMessage(data, '*');
  }

  init = data => {
    window.EJS_gameUrl = `/api/v1/rom?id=${data.gameId}`;
    window.EJS_core = data.core;
    window.EJS_gameName = data.gameName;
    window.EJS_externalFiles = {
      '/homeplay/saves/': `/api/v1/save?id=${data.gameId}`
    }

    this.scriptElement = document.createElement('script');
    this.scriptElement.src = 'https://cdn.emulatorjs.org/stable/data/loader.js';
    document.body.appendChild(this.scriptElement);

    clearInterval(this.initInterval);
  }

  onEJSReady = () => {
    // Modify the EJS stuff
    this.oldGetRetroArchCfg = window.EJS_GameManager.prototype.getRetroArchCfg;
    const raConfig = (...a) => this.getRetroArchCfg(...a);
    window.EJS_GameManager.prototype.getRetroArchCfg = function() {
      return raConfig(this);
    }

    this.send({ message: 'ready' });

    console.log('EJS Ready');
  }

  onEJSGameStart = () => {
    console.log('EJS Game Start');
    
    setTimeout(() => {
      window.EJS_emulator.gameManager.saveSaveFiles();
      this.gameStarted = true;
    }, 1000)
  }

  onEJSGameEnd = () => {
    console.log('EJS Game End');
  }

  onEJSSaveState = () => {
    console.log('EJS Save State');
  }

  onEJSLoadState = () => {
    console.log('EJS Load State');;
  }

  onMessage = e => {
    const { data } = e;

    if(!data) {
      console.error('No data');
      return;
    }

    const { message } = data;
    switch(message) {
      case 'init':
        this.init(data);
        break;

      case 'volume':
        window.EJS_volume = data.volume;
        break;

      case 'save':
        window.EJS_emulator.saveSaveFiles();
        break;

      default:
        console.error('Unknown message', message);
    }
  }

  getFileMD5 = async path => {
    const buffer = window.EJS_emulator.gameManager.FS.readFile(path);
    const hash = await crypto.subtle.digest('SHA-256', buffer);
    const hashArray = Array.from(new Uint8Array(hash));
    const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    return hashHex;
  }

  getRetroArchCfg = (gameManager) => {
    const ejsConfig = this.oldGetRetroArchCfg.call(gameManager);

    // Parse back into key value pair
    const raConfig = ejsConfig.split('\n').reduce((acc, line) => {
      if(!line.length) return acc;
      const [key, value] = line.split('=');
      acc[key.trim()] = value.trim();
      return acc;
    }, {});

    // Add new options
    raConfig['savefiles_in_content_dir'] = false;
    raConfig['sort_savefiles_by_content_enable'] = false;
    raConfig['sort_savefiles_enable'] = false;
    raConfig['savefile_directory'] = '/homeplay/saves';
    
    // Return back as RA config string
    return Object.entries(raConfig).map(([k, v]) => `${k} = ${v}`).join('\n');
  }

  getSaveFiles = () => {
    return [ 'rom.srm' ];
  }

  hasSaveUpdates = async () => {
    if(!window.EJS_emulator) return false;
    if(!window.EJS_emulator.gameManager) return false;
    if(!this.gameStarted) return false;

    const saveFiles = this.getSaveFiles();
    const hashes = await Promise.all(saveFiles.map(f => this.getFileMD5(`/homeplay/saves/${f}`)));
    
    window.EJS_emulator.gameManager.saveSaveFiles();

    const newHashes = await Promise.all(saveFiles.map(f => this.getFileMD5(`/homeplay/saves/${f}`)));
    const changedFiles = saveFiles.filter((f, i) => hashes[i] !== newHashes[i]);

    return !!changedFiles.length;
  }

  save = async () => {
    // Check if updates
    if(!this.hasSaveUpdates) return;

    // Get save file contents
    const saveFiles = this.getSaveFiles();
    
    // Now we need to create a zip archive. EJS provides a nice ZIP library.
    const zip = new JSZip();

    await Promise.all(saveFiles.map(async f => {
      const data = window.EJS_emulator.gameManager.FS.readFile(`/homeplay/saves/${f}`);
      zip.file(f, data);
    }));

    this.send({
      message: 'save',
      data: await zip.generateAsync({ type:"base64" })
    })
  }
}

window.homeplay = new HomeplayEmulator();