#include "dataErrors.h"
#include "engine.h"
#include "instrument.h"
#include "../ta-log.h"
#include "../fileutils.h"

void DivInstrument::putInsData(SafeWriter* w) {
  w->write("INST",4);
  w->writeI(0);

  w->writeS(DIV_ENGINE_VERSION);

  w->writeC(type);
  w->writeC(0);

  w->writeString(name,false);

  // FM
  w->writeC(fm.alg);
  w->writeC(fm.fb);
  w->writeC(fm.fms);
  w->writeC(fm.ams);
  w->writeC(4); // operator count; always 4
  w->writeC(0); // reserved
  w->writeC(0);
  w->writeC(0);

  for (int j=0; j<4; j++) {
    DivInstrumentFM::Operator& op=fm.op[j];
    w->writeC(op.am);
    w->writeC(op.ar);
    w->writeC(op.dr);
    w->writeC(op.mult);
    w->writeC(op.rr);
    w->writeC(op.sl);
    w->writeC(op.tl);
    w->writeC(op.dt2);
    w->writeC(op.rs);
    w->writeC(op.dt);
    w->writeC(op.d2r);
    w->writeC(op.ssgEnv);

    w->writeC(op.dam);
    w->writeC(op.dvb);
    w->writeC(op.egt);
    w->writeC(op.ksl);
    w->writeC(op.sus);
    w->writeC(op.vib);
    w->writeC(op.ws);
    w->writeC(op.ksr);

    // reserved
    for (int k=0; k<12; k++) {
      w->writeC(0);
    }
  }

  // GB
  w->writeC(gb.envVol);
  w->writeC(gb.envDir);
  w->writeC(gb.envLen);
  w->writeC(gb.soundLen);

  // C64
  w->writeC(c64.triOn);
  w->writeC(c64.sawOn);
  w->writeC(c64.pulseOn);
  w->writeC(c64.noiseOn);
  w->writeC(c64.a);
  w->writeC(c64.d);
  w->writeC(c64.s);
  w->writeC(c64.r);
  w->writeS(c64.duty);
  w->writeC(c64.ringMod);
  w->writeC(c64.oscSync);
  w->writeC(c64.toFilter);
  w->writeC(c64.initFilter);
  w->writeC(c64.volIsCutoff);
  w->writeC(c64.res);
  w->writeC(c64.lp);
  w->writeC(c64.bp);
  w->writeC(c64.hp);
  w->writeC(c64.ch3off);
  w->writeS(c64.cut);
  w->writeC(c64.dutyIsAbs);
  w->writeC(c64.filterIsAbs);
  
  // Amiga
  w->writeS(amiga.initSample);
  for (int j=0; j<14; j++) { // reserved
    w->writeC(0);
  }

  // standard
  w->writeI(std.volMacroLen);
  w->writeI(std.arpMacroLen);
  w->writeI(std.dutyMacroLen);
  w->writeI(std.waveMacroLen);
  w->writeI(std.pitchMacroLen);
  w->writeI(std.ex1MacroLen);
  w->writeI(std.ex2MacroLen);
  w->writeI(std.ex3MacroLen);
  w->writeI(std.volMacroLoop);
  w->writeI(std.arpMacroLoop);
  w->writeI(std.dutyMacroLoop);
  w->writeI(std.waveMacroLoop);
  w->writeI(std.pitchMacroLoop);
  w->writeI(std.ex1MacroLoop);
  w->writeI(std.ex2MacroLoop);
  w->writeI(std.ex3MacroLoop);
  w->writeC(std.arpMacroMode);
  w->writeC(0); // reserved
  w->writeC(0);
  w->writeC(0);
  for (int j=0; j<std.volMacroLen; j++) {
    w->writeI(std.volMacro[j]);
  }
  for (int j=0; j<std.arpMacroLen; j++) {
    w->writeI(std.arpMacro[j]);
  }
  for (int j=0; j<std.dutyMacroLen; j++) {
    w->writeI(std.dutyMacro[j]);
  }
  for (int j=0; j<std.waveMacroLen; j++) {
    w->writeI(std.waveMacro[j]);
  }
  for (int j=0; j<std.pitchMacroLen; j++) {
    w->writeI(std.pitchMacro[j]);
  }
  for (int j=0; j<std.ex1MacroLen; j++) {
    w->writeI(std.ex1Macro[j]);
  }
  for (int j=0; j<std.ex2MacroLen; j++) {
    w->writeI(std.ex2Macro[j]);
  }
  for (int j=0; j<std.ex3MacroLen; j++) {
    w->writeI(std.ex3Macro[j]);
  }

  // FM macros and open status
  w->writeI(std.algMacroLen);
  w->writeI(std.fbMacroLen);
  w->writeI(std.fmsMacroLen);
  w->writeI(std.amsMacroLen);
  w->writeI(std.algMacroLoop);
  w->writeI(std.fbMacroLoop);
  w->writeI(std.fmsMacroLoop);
  w->writeI(std.amsMacroLoop);

  w->writeC(std.volMacroOpen);
  w->writeC(std.arpMacroOpen);
  w->writeC(std.dutyMacroOpen);
  w->writeC(std.waveMacroOpen);
  w->writeC(std.pitchMacroOpen);
  w->writeC(std.ex1MacroOpen);
  w->writeC(std.ex2MacroOpen);
  w->writeC(std.ex3MacroOpen);
  w->writeC(std.algMacroOpen);
  w->writeC(std.fbMacroOpen);
  w->writeC(std.fmsMacroOpen);
  w->writeC(std.amsMacroOpen);

  for (int j=0; j<std.algMacroLen; j++) {
    w->writeI(std.algMacro[j]);
  }
  for (int j=0; j<std.fbMacroLen; j++) {
    w->writeI(std.fbMacro[j]);
  }
  for (int j=0; j<std.fmsMacroLen; j++) {
    w->writeI(std.fmsMacro[j]);
  }
  for (int j=0; j<std.amsMacroLen; j++) {
    w->writeI(std.amsMacro[j]);
  }

  for (int i=0; i<4; i++) {
    DivInstrumentSTD::OpMacro& op=std.opMacros[i];

    w->writeI(op.amMacroLen);
    w->writeI(op.arMacroLen);
    w->writeI(op.drMacroLen);
    w->writeI(op.multMacroLen);
    w->writeI(op.rrMacroLen);
    w->writeI(op.slMacroLen);
    w->writeI(op.tlMacroLen);
    w->writeI(op.dt2MacroLen);
    w->writeI(op.rsMacroLen);
    w->writeI(op.dtMacroLen);
    w->writeI(op.d2rMacroLen);
    w->writeI(op.ssgMacroLen);
    w->writeI(op.amMacroLoop);
    w->writeI(op.arMacroLoop);
    w->writeI(op.drMacroLoop);
    w->writeI(op.multMacroLoop);
    w->writeI(op.rrMacroLoop);
    w->writeI(op.slMacroLoop);
    w->writeI(op.tlMacroLoop);
    w->writeI(op.dt2MacroLoop);
    w->writeI(op.rsMacroLoop);
    w->writeI(op.dtMacroLoop);
    w->writeI(op.d2rMacroLoop);
    w->writeI(op.ssgMacroLoop);
    w->writeC(op.amMacroOpen);
    w->writeC(op.arMacroOpen);
    w->writeC(op.drMacroOpen);
    w->writeC(op.multMacroOpen);
    w->writeC(op.rrMacroOpen);
    w->writeC(op.slMacroOpen);
    w->writeC(op.tlMacroOpen);
    w->writeC(op.dt2MacroOpen);
    w->writeC(op.rsMacroOpen);
    w->writeC(op.dtMacroOpen);
    w->writeC(op.d2rMacroOpen);
    w->writeC(op.ssgMacroOpen);
  }

  for (int i=0; i<4; i++) {
    DivInstrumentSTD::OpMacro& op=std.opMacros[i];
    for (int j=0; j<op.amMacroLen; j++) {
      w->writeC(op.amMacro[j]);
    }
    for (int j=0; j<op.arMacroLen; j++) {
      w->writeC(op.arMacro[j]);
    }
    for (int j=0; j<op.drMacroLen; j++) {
      w->writeC(op.drMacro[j]);
    }
    for (int j=0; j<op.multMacroLen; j++) {
      w->writeC(op.multMacro[j]);
    }
    for (int j=0; j<op.rrMacroLen; j++) {
      w->writeC(op.rrMacro[j]);
    }
    for (int j=0; j<op.slMacroLen; j++) {
      w->writeC(op.slMacro[j]);
    }
    for (int j=0; j<op.tlMacroLen; j++) {
      w->writeC(op.tlMacro[j]);
    }
    for (int j=0; j<op.dt2MacroLen; j++) {
      w->writeC(op.dt2Macro[j]);
    }
    for (int j=0; j<op.rsMacroLen; j++) {
      w->writeC(op.rsMacro[j]);
    }
    for (int j=0; j<op.dtMacroLen; j++) {
      w->writeC(op.dtMacro[j]);
    }
    for (int j=0; j<op.d2rMacroLen; j++) {
      w->writeC(op.d2rMacro[j]);
    }
    for (int j=0; j<op.ssgMacroLen; j++) {
      w->writeC(op.ssgMacro[j]);
    }
  }
}

DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
  char magic[4];
  reader.read(magic,4);
  if (memcmp(magic,"INST",4)!=0) {
    logE("invalid instrument header!\n");
    return DIV_DATA_INVALID_HEADER;
  }
  reader.readI();

  reader.readS(); // format version. ignored.
  type=(DivInstrumentType)reader.readC();
  mode=(type==DIV_INS_FM);
  reader.readC();
  name=reader.readString();

  // FM
  fm.alg=reader.readC();
  fm.fb=reader.readC();
  fm.fms=reader.readC();
  fm.ams=reader.readC();
  fm.ops=reader.readC();
  reader.readC();
  reader.readC();
  reader.readC();

  for (int j=0; j<4; j++) {
    DivInstrumentFM::Operator& op=fm.op[j];
    op.am=reader.readC();
    op.ar=reader.readC();
    op.dr=reader.readC();
    op.mult=reader.readC();
    op.rr=reader.readC();
    op.sl=reader.readC();
    op.tl=reader.readC();
    op.dt2=reader.readC();
    op.rs=reader.readC();
    op.dt=reader.readC();
    op.d2r=reader.readC();
    op.ssgEnv=reader.readC();

    op.dam=reader.readC();
    op.dvb=reader.readC();
    op.egt=reader.readC();
    op.ksl=reader.readC();
    op.sus=reader.readC();
    op.vib=reader.readC();
    op.ws=reader.readC();
    op.ksr=reader.readC();

    // reserved
    for (int k=0; k<12; k++) reader.readC();
  }

  // GB
  gb.envVol=reader.readC();
  gb.envDir=reader.readC();
  gb.envLen=reader.readC();
  gb.soundLen=reader.readC();

  // C64
  c64.triOn=reader.readC();
  c64.sawOn=reader.readC();
  c64.pulseOn=reader.readC();
  c64.noiseOn=reader.readC();
  c64.a=reader.readC();
  c64.d=reader.readC();
  c64.s=reader.readC();
  c64.r=reader.readC();
  c64.duty=reader.readS();
  c64.ringMod=reader.readC();
  c64.oscSync=reader.readC();
  c64.toFilter=reader.readC();
  c64.initFilter=reader.readC();
  c64.volIsCutoff=reader.readC();
  c64.res=reader.readC();
  c64.lp=reader.readC();
  c64.bp=reader.readC();
  c64.hp=reader.readC();
  c64.ch3off=reader.readC();
  c64.cut=reader.readS();
  c64.dutyIsAbs=reader.readC();
  c64.filterIsAbs=reader.readC();

  // Amiga
  amiga.initSample=reader.readS();
  // reserved
  for (int k=0; k<14; k++) reader.readC();

  // standard
  std.volMacroLen=reader.readI();
  std.arpMacroLen=reader.readI();
  std.dutyMacroLen=reader.readI();
  std.waveMacroLen=reader.readI();
  if (version>=17) {
    std.pitchMacroLen=reader.readI();
    std.ex1MacroLen=reader.readI();
    std.ex2MacroLen=reader.readI();
    std.ex3MacroLen=reader.readI();
  }
  std.volMacroLoop=reader.readI();
  std.arpMacroLoop=reader.readI();
  std.dutyMacroLoop=reader.readI();
  std.waveMacroLoop=reader.readI();
  if (version>=17) {
    std.pitchMacroLoop=reader.readI();
    std.ex1MacroLoop=reader.readI();
    std.ex2MacroLoop=reader.readI();
    std.ex3MacroLoop=reader.readI();
  }
  std.arpMacroMode=reader.readC();
  std.volMacroHeight=reader.readC();
  std.dutyMacroHeight=reader.readC();
  std.waveMacroHeight=reader.readC();
  if (std.volMacroHeight==0) std.volMacroHeight=15;
  if (std.dutyMacroHeight==0) std.dutyMacroHeight=3;
  if (std.waveMacroHeight==0) std.waveMacroHeight=63;
  reader.read(std.volMacro,4*std.volMacroLen);
  reader.read(std.arpMacro,4*std.arpMacroLen);
  reader.read(std.dutyMacro,4*std.dutyMacroLen);
  reader.read(std.waveMacro,4*std.waveMacroLen);
  if (version<31) {
    if (!std.arpMacroMode) for (int j=0; j<std.arpMacroLen; j++) {
      std.arpMacro[j]-=12;
    }
  }
  if (version>=17) {
    reader.read(std.pitchMacro,4*std.pitchMacroLen);
    reader.read(std.ex1Macro,4*std.ex1MacroLen);
    reader.read(std.ex2Macro,4*std.ex2MacroLen);
    reader.read(std.ex3Macro,4*std.ex3MacroLen);
  } else {
    if (type==DIV_INS_STD) {
      if (std.volMacroHeight==31) {
        type=DIV_INS_PCE;
      }
      if (std.dutyMacroHeight==31) {
        type=DIV_INS_AY;
      }
    }
  }

  // FM macros
  if (version>=29) {
    std.algMacroLen=reader.readI();
    std.fbMacroLen=reader.readI();
    std.fmsMacroLen=reader.readI();
    std.amsMacroLen=reader.readI();
    std.algMacroLoop=reader.readI();
    std.fbMacroLoop=reader.readI();
    std.fmsMacroLoop=reader.readI();
    std.amsMacroLoop=reader.readI();
    std.volMacroOpen=reader.readC();
    std.arpMacroOpen=reader.readC();
    std.dutyMacroOpen=reader.readC();
    std.waveMacroOpen=reader.readC();
    std.pitchMacroOpen=reader.readC();
    std.ex1MacroOpen=reader.readC();
    std.ex2MacroOpen=reader.readC();
    std.ex3MacroOpen=reader.readC();
    std.algMacroOpen=reader.readC();
    std.fbMacroOpen=reader.readC();
    std.fmsMacroOpen=reader.readC();
    std.amsMacroOpen=reader.readC();

    reader.read(std.algMacro,4*std.algMacroLen);
    reader.read(std.fbMacro,4*std.fbMacroLen);
    reader.read(std.fmsMacro,4*std.fmsMacroLen);
    reader.read(std.amsMacro,4*std.amsMacroLen);

    for (int i=0; i<4; i++) {
      DivInstrumentSTD::OpMacro& op=std.opMacros[i];

      op.amMacroLen=reader.readI();
      op.arMacroLen=reader.readI();
      op.drMacroLen=reader.readI();
      op.multMacroLen=reader.readI();
      op.rrMacroLen=reader.readI();
      op.slMacroLen=reader.readI();
      op.tlMacroLen=reader.readI();
      op.dt2MacroLen=reader.readI();
      op.rsMacroLen=reader.readI();
      op.dtMacroLen=reader.readI();
      op.d2rMacroLen=reader.readI();
      op.ssgMacroLen=reader.readI();

      op.amMacroLoop=reader.readI();
      op.arMacroLoop=reader.readI();
      op.drMacroLoop=reader.readI();
      op.multMacroLoop=reader.readI();
      op.rrMacroLoop=reader.readI();
      op.slMacroLoop=reader.readI();
      op.tlMacroLoop=reader.readI();
      op.dt2MacroLoop=reader.readI();
      op.rsMacroLoop=reader.readI();
      op.dtMacroLoop=reader.readI();
      op.d2rMacroLoop=reader.readI();
      op.ssgMacroLoop=reader.readI();

      op.amMacroOpen=reader.readC();
      op.arMacroOpen=reader.readC();
      op.drMacroOpen=reader.readC();
      op.multMacroOpen=reader.readC();
      op.rrMacroOpen=reader.readC();
      op.slMacroOpen=reader.readC();
      op.tlMacroOpen=reader.readC();
      op.dt2MacroOpen=reader.readC();
      op.rsMacroOpen=reader.readC();
      op.dtMacroOpen=reader.readC();
      op.d2rMacroOpen=reader.readC();
      op.ssgMacroOpen=reader.readC();
    }

    for (int i=0; i<4; i++) {
      DivInstrumentSTD::OpMacro& op=std.opMacros[i];
      reader.read(op.amMacro,op.amMacroLen);
      reader.read(op.arMacro,op.arMacroLen);
      reader.read(op.drMacro,op.drMacroLen);
      reader.read(op.multMacro,op.multMacroLen);
      reader.read(op.rrMacro,op.rrMacroLen);
      reader.read(op.slMacro,op.slMacroLen);
      reader.read(op.tlMacro,op.tlMacroLen);
      reader.read(op.dt2Macro,op.dt2MacroLen);
      reader.read(op.rsMacro,op.rsMacroLen);
      reader.read(op.dtMacro,op.dtMacroLen);
      reader.read(op.d2rMacro,op.d2rMacroLen);
      reader.read(op.ssgMacro,op.ssgMacroLen);
    }
  }

  return DIV_DATA_SUCCESS;
}

bool DivInstrument::save(const char* path) {
  SafeWriter* w=new SafeWriter();
  w->init();

  // write magic
  w->write("-Furnace instr.-",16);

  // write version
  w->writeS(DIV_ENGINE_VERSION);

  // reserved
  w->writeS(0);

  // pointer to data
  w->writeI(32);

  // currently reserved (TODO; wavetable and sample here)
  w->writeS(0);
  w->writeS(0);
  w->writeI(0);

  putInsData(w);

  FILE* outFile=ps_fopen(path,"wb");
  if (outFile==NULL) {
    logE("could not save instrument: %s!\n",strerror(errno));
    w->finish();
    return false;
  }
  if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) {
    logW("did not write entire instrument!\n");
  }
  fclose(outFile);
  w->finish();
  return true;
}
