/**
 * Furnace Tracker - multi-system chiptune tracker
 * Copyright (C) 2021-2022 tildearrow and contributors
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#ifndef _INSTRUMENT_H
#define _INSTRUMENT_H
#include "safeWriter.h"
#include "dataErrors.h"
#include "../ta-utils.h"

// NOTICE!
// before adding new instrument types to this struct, please ask me first.
// absolutely zero support granted to conflicting formats.
enum DivInstrumentType {
  DIV_INS_STD=0,
  DIV_INS_FM=1,
  DIV_INS_GB=2,
  DIV_INS_C64=3,
  DIV_INS_AMIGA=4,
  DIV_INS_PCE=5,
  DIV_INS_AY=6,
  DIV_INS_AY8930=7,
  DIV_INS_TIA=8,
  DIV_INS_SAA1099=9,
  DIV_INS_VIC=10,
  DIV_INS_PET=11,
  DIV_INS_VRC6=12,
  DIV_INS_OPLL=13,
  DIV_INS_OPL=14,
  DIV_INS_FDS=15,
  DIV_INS_VBOY=16,
  DIV_INS_N163=17,
  DIV_INS_SCC=18,
  DIV_INS_OPZ=19,
  DIV_INS_POKEY=20,
  DIV_INS_BEEPER=21,
  DIV_INS_SWAN=22,
  DIV_INS_MIKEY=23,
  DIV_INS_VERA=24,
  DIV_INS_X1_010=25,
  DIV_INS_MAX,
};

// FM operator structure:
// - OPN:
//   - AM, AR, DR, MULT, RR, SL, TL, RS, DT, D2R, SSG-EG
// - OPM:
//   - AM, AR, DR, MULT, RR, SL, TL, DT2, RS, DT, D2R
// - OPLL:
//   - AM, AR, DR, MULT, RR, SL, TL, SSG-EG&8 = EG-S
//   - KSL, VIB, KSR
// - OPL:
//   - AM, AR, DR, MULT, RR, SL, TL, SSG-EG&8 = EG-S
//   - KSL, VIB, WS (OPL2/3), KSR
// - OPZ: NOT FINAL!
//   - AM, AR, DR, MULT (CRS), RR, SL, TL, DT2, RS, DT, D2R
//   - KSL = LS, WS, DVB = MULT (FINE), DAM = REV, EGT = EGShift

struct DivInstrumentFM {
  unsigned char alg, fb, fms, ams, ops, opllPreset;
  bool fixedDrums;
  unsigned short kickFreq, snareHatFreq, tomTopFreq;
  struct Operator {
    unsigned char am, ar, dr, mult, rr, sl, tl, dt2, rs, dt, d2r, ssgEnv;
    unsigned char dam, dvb, egt, ksl, sus, vib, ws, ksr; // YMU759/OPL/OPZ
    Operator():
      am(0),
      ar(0),
      dr(0),
      mult(0),
      rr(0),
      sl(0),
      tl(0),
      dt2(0),
      rs(0),
      dt(0),
      d2r(0),
      ssgEnv(0),
      dam(0),
      dvb(0),
      egt(0),
      ksl(0),
      sus(0),
      vib(0),
      ws(0),
      ksr(0) {}
  } op[4];
  DivInstrumentFM():
    alg(0),
    fb(0),
    fms(0),
    ams(0),
    ops(2),
    opllPreset(0),
    fixedDrums(false),
    kickFreq(0x520),
    snareHatFreq(0x550),
    tomTopFreq(0x1c0) {
    // default instrument
    fb=4;
    op[0].tl=42;
    op[0].ar=31;
    op[0].dr=8;
    op[0].sl=15;
    op[0].rr=3;
    op[0].mult=5;
    op[0].dt=5;

    op[2].tl=18;
    op[2].ar=31;
    op[2].dr=10;
    op[2].sl=15;
    op[2].rr=4;
    op[2].mult=1;
    op[2].dt=0;

    op[1].tl=48;
    op[1].ar=31;
    op[1].dr=4;
    op[1].sl=11;
    op[1].rr=1;
    op[1].mult=1;
    op[1].dt=5;

    op[3].tl=2;
    op[3].ar=31;
    op[3].dr=9;
    op[3].sl=15;
    op[3].rr=9;
    op[3].mult=1;
    op[3].dt=0;
  }
};

struct DivInstrumentSTD {
  int volMacro[256];
  int arpMacro[256];
  int dutyMacro[256];
  int waveMacro[256];
  int pitchMacro[256];
  int ex1Macro[256];
  int ex2Macro[256];
  int ex3Macro[256];
  int algMacro[256];
  int fbMacro[256];
  int fmsMacro[256];
  int amsMacro[256];
  bool arpMacroMode;
  unsigned char volMacroHeight, dutyMacroHeight, waveMacroHeight;
  bool volMacroOpen, arpMacroOpen, dutyMacroOpen, waveMacroOpen;
  bool pitchMacroOpen, ex1MacroOpen, ex2MacroOpen, ex3MacroOpen;
  bool algMacroOpen, fbMacroOpen, fmsMacroOpen, amsMacroOpen;
  unsigned char volMacroLen, arpMacroLen, dutyMacroLen, waveMacroLen;
  unsigned char pitchMacroLen, ex1MacroLen, ex2MacroLen, ex3MacroLen;
  unsigned char algMacroLen, fbMacroLen, fmsMacroLen, amsMacroLen;
  signed char volMacroLoop, arpMacroLoop, dutyMacroLoop, waveMacroLoop;
  signed char pitchMacroLoop, ex1MacroLoop, ex2MacroLoop, ex3MacroLoop;
  signed char algMacroLoop, fbMacroLoop, fmsMacroLoop, amsMacroLoop;
  signed char volMacroRel, arpMacroRel, dutyMacroRel, waveMacroRel;
  signed char pitchMacroRel, ex1MacroRel, ex2MacroRel, ex3MacroRel;
  signed char algMacroRel, fbMacroRel, fmsMacroRel, amsMacroRel;
  struct OpMacro {
    // ar, dr, mult, rr, sl, tl, dt2, rs, dt, d2r, ssgEnv;
    unsigned char amMacro[256];
    unsigned char arMacro[256];
    unsigned char drMacro[256];
    unsigned char multMacro[256];
    unsigned char rrMacro[256];
    unsigned char slMacro[256];
    unsigned char tlMacro[256];
    unsigned char dt2Macro[256];
    unsigned char rsMacro[256];
    unsigned char dtMacro[256];
    unsigned char d2rMacro[256];
    unsigned char ssgMacro[256];
    unsigned char damMacro[256];
    unsigned char dvbMacro[256];
    unsigned char egtMacro[256];
    unsigned char kslMacro[256];
    unsigned char susMacro[256];
    unsigned char vibMacro[256];
    unsigned char wsMacro[256];
    unsigned char ksrMacro[256];
    bool amMacroOpen, arMacroOpen, drMacroOpen, multMacroOpen;
    bool rrMacroOpen, slMacroOpen, tlMacroOpen, dt2MacroOpen;
    bool rsMacroOpen, dtMacroOpen, d2rMacroOpen, ssgMacroOpen;
    bool damMacroOpen, dvbMacroOpen, egtMacroOpen, kslMacroOpen;
    bool susMacroOpen, vibMacroOpen, wsMacroOpen, ksrMacroOpen;
    unsigned char amMacroLen, arMacroLen, drMacroLen, multMacroLen;
    unsigned char rrMacroLen, slMacroLen, tlMacroLen, dt2MacroLen;
    unsigned char rsMacroLen, dtMacroLen, d2rMacroLen, ssgMacroLen;
    unsigned char damMacroLen, dvbMacroLen, egtMacroLen, kslMacroLen;
    unsigned char susMacroLen, vibMacroLen, wsMacroLen, ksrMacroLen;
    signed char amMacroLoop, arMacroLoop, drMacroLoop, multMacroLoop;
    signed char rrMacroLoop, slMacroLoop, tlMacroLoop, dt2MacroLoop;
    signed char rsMacroLoop, dtMacroLoop, d2rMacroLoop, ssgMacroLoop;
    signed char damMacroLoop, dvbMacroLoop, egtMacroLoop, kslMacroLoop;
    signed char susMacroLoop, vibMacroLoop, wsMacroLoop, ksrMacroLoop;
    signed char amMacroRel, arMacroRel, drMacroRel, multMacroRel;
    signed char rrMacroRel, slMacroRel, tlMacroRel, dt2MacroRel;
    signed char rsMacroRel, dtMacroRel, d2rMacroRel, ssgMacroRel;
    signed char damMacroRel, dvbMacroRel, egtMacroRel, kslMacroRel;
    signed char susMacroRel, vibMacroRel, wsMacroRel, ksrMacroRel;
    OpMacro():
      amMacroOpen(false), arMacroOpen(false), drMacroOpen(false), multMacroOpen(false),
      rrMacroOpen(false), slMacroOpen(false), tlMacroOpen(true), dt2MacroOpen(false),
      rsMacroOpen(false), dtMacroOpen(false), d2rMacroOpen(false), ssgMacroOpen(false),
      damMacroOpen(false), dvbMacroOpen(false), egtMacroOpen(false), kslMacroOpen(false),
      susMacroOpen(false), vibMacroOpen(false), wsMacroOpen(false), ksrMacroOpen(false),
      amMacroLen(0), arMacroLen(0), drMacroLen(0), multMacroLen(0),
      rrMacroLen(0), slMacroLen(0), tlMacroLen(0), dt2MacroLen(0),
      rsMacroLen(0), dtMacroLen(0), d2rMacroLen(0), ssgMacroLen(0),
      damMacroLen(0), dvbMacroLen(0), egtMacroLen(0), kslMacroLen(0),
      susMacroLen(0), vibMacroLen(0), wsMacroLen(0), ksrMacroLen(0),
      amMacroLoop(-1), arMacroLoop(-1), drMacroLoop(-1), multMacroLoop(-1),
      rrMacroLoop(-1), slMacroLoop(-1), tlMacroLoop(-1), dt2MacroLoop(-1),
      rsMacroLoop(-1), dtMacroLoop(-1), d2rMacroLoop(-1), ssgMacroLoop(-1),
      damMacroLoop(-1), dvbMacroLoop(-1), egtMacroLoop(-1), kslMacroLoop(-1),
      susMacroLoop(-1), vibMacroLoop(-1), wsMacroLoop(-1), ksrMacroLoop(-1),
      amMacroRel(-1), arMacroRel(-1), drMacroRel(-1), multMacroRel(-1),
      rrMacroRel(-1), slMacroRel(-1), tlMacroRel(-1), dt2MacroRel(-1),
      rsMacroRel(-1), dtMacroRel(-1), d2rMacroRel(-1), ssgMacroRel(-1),
      damMacroRel(-1), dvbMacroRel(-1), egtMacroRel(-1), kslMacroRel(-1),
      susMacroRel(-1), vibMacroRel(-1), wsMacroRel(-1), ksrMacroRel(-1) {
        memset(amMacro,0,256);
        memset(arMacro,0,256);
        memset(drMacro,0,256);
        memset(multMacro,0,256);
        memset(rrMacro,0,256);
        memset(slMacro,0,256);
        memset(tlMacro,0,256);
        memset(dt2Macro,0,256);
        memset(rsMacro,0,256);
        memset(dtMacro,0,256);
        memset(d2rMacro,0,256);
        memset(ssgMacro,0,256);
        memset(damMacro,0,256);
        memset(dvbMacro,0,256);
        memset(egtMacro,0,256);
        memset(kslMacro,0,256);
        memset(susMacro,0,256);
        memset(vibMacro,0,256);
        memset(wsMacro,0,256);
        memset(ksrMacro,0,256);
      }
  } opMacros[4];
  DivInstrumentSTD():
    arpMacroMode(false),
    volMacroHeight(15),
    dutyMacroHeight(3),
    waveMacroHeight(63),
    volMacroOpen(true),
    arpMacroOpen(false),
    dutyMacroOpen(false),
    waveMacroOpen(false),
    pitchMacroOpen(false),
    ex1MacroOpen(false),
    ex2MacroOpen(false),
    ex3MacroOpen(false),
    algMacroOpen(false),
    fbMacroOpen(false),
    fmsMacroOpen(false),
    amsMacroOpen(false),
    volMacroLen(0),
    arpMacroLen(0),
    dutyMacroLen(0),
    waveMacroLen(0),
    pitchMacroLen(0),
    ex1MacroLen(0),
    ex2MacroLen(0),
    ex3MacroLen(0),
    algMacroLen(0),
    fbMacroLen(0),
    fmsMacroLen(0),
    amsMacroLen(0),
    volMacroLoop(-1),
    arpMacroLoop(-1),
    dutyMacroLoop(-1),
    waveMacroLoop(-1),
    pitchMacroLoop(-1),
    ex1MacroLoop(-1),
    ex2MacroLoop(-1),
    ex3MacroLoop(-1),
    algMacroLoop(-1),
    fbMacroLoop(-1),
    fmsMacroLoop(-1),
    amsMacroLoop(-1),
    volMacroRel(-1),
    arpMacroRel(-1),
    dutyMacroRel(-1),
    waveMacroRel(-1),
    pitchMacroRel(-1),
    ex1MacroRel(-1),
    ex2MacroRel(-1),
    ex3MacroRel(-1),
    algMacroRel(-1),
    fbMacroRel(-1),
    fmsMacroRel(-1),
    amsMacroRel(-1) {
      memset(volMacro,0,256*sizeof(int));
      memset(arpMacro,0,256*sizeof(int));
      memset(dutyMacro,0,256*sizeof(int));
      memset(waveMacro,0,256*sizeof(int));
      memset(pitchMacro,0,256*sizeof(int));
      memset(ex1Macro,0,256*sizeof(int));
      memset(ex2Macro,0,256*sizeof(int));
      memset(ex3Macro,0,256*sizeof(int));
      memset(algMacro,0,256*sizeof(int));
      memset(fbMacro,0,256*sizeof(int));
      memset(fmsMacro,0,256*sizeof(int));
      memset(amsMacro,0,256*sizeof(int));
    }
};

struct DivInstrumentGB {
  unsigned char envVol, envDir, envLen, soundLen;
  DivInstrumentGB():
    envVol(15),
    envDir(0),
    envLen(2),
    soundLen(64) {}
};

struct DivInstrumentC64 {
  bool triOn, sawOn, pulseOn, noiseOn;
  unsigned char a, d, s, r;
  unsigned short duty;
  unsigned char ringMod, oscSync;
  bool toFilter, volIsCutoff, initFilter, dutyIsAbs, filterIsAbs;
  unsigned char res;
  unsigned short cut;
  bool hp, lp, bp, ch3off;

  DivInstrumentC64():
    triOn(false),
    sawOn(true),
    pulseOn(false),
    noiseOn(false),
    a(0),
    d(8),
    s(0),
    r(0),
    duty(2048),
    ringMod(0),
    oscSync(0),
    toFilter(false),
    volIsCutoff(false),
    initFilter(false),
    dutyIsAbs(false),
    filterIsAbs(false),
    res(0),
    cut(0),
    hp(false),
    lp(false),
    bp(false),
    ch3off(false) {}
};

struct DivInstrumentAmiga {
  short initSample;
  bool useNoteMap;
  int noteFreq[120];
  short noteMap[120];

  DivInstrumentAmiga():
    initSample(0),
    useNoteMap(false) {
    memset(noteMap,-1,120*sizeof(short));
    memset(noteFreq,0,120*sizeof(int));
  }
};

struct DivInstrumentN163 {
  int wave, wavePos, waveLen;
  unsigned char waveMode;

  DivInstrumentN163():
    wave(-1),
    wavePos(0),
    waveLen(32),
    waveMode(3) {}
};

struct DivInstrument {
  String name;
  bool mode;
  DivInstrumentType type;
  DivInstrumentFM fm;
  DivInstrumentSTD std;
  DivInstrumentGB gb;
  DivInstrumentC64 c64;
  DivInstrumentAmiga amiga;
  DivInstrumentN163 n163;
  
  /**
   * save the instrument to a SafeWriter.
   * @param w the SafeWriter in question.
   */
  void putInsData(SafeWriter* w);

  /**
   * read instrument data in .fui format.
   * @param reader the reader.
   * @param version the format version.
   * @return a DivDataErrors.
   */
  DivDataErrors readInsData(SafeReader& reader, short version);

  /**
   * save this instrument to a file.
   * @param path file path.
   * @return whether it was successful.
   */
  bool save(const char* path);
  DivInstrument():
    name(""),
    mode(false),
    type(DIV_INS_STD) {
  }
};
#endif
