#pragma once

namespace sf {
  struct Synthesizer
  {
    // エンベロープパラメータ
    struct Envelope 
    {
      Envelope() : 
        enable(true),
        releaseNoteOff(true),
        attackTime(0.0f),
        decayTime(0.5f),
        sustainLevel(0.5f),
        releaseTime(3.0f),
        gain(1.0f)
      {}

      Envelope(const Envelope& src) :
        enable(src.enable),
        releaseNoteOff(src.releaseNoteOff),
        attackTime(src.attackTime),
        decayTime(src.decayTime),
        sustainLevel(src.sustainLevel),
        releaseTime(src.releaseTime),
        gain(src.gain)
      {}

      Envelope(Envelope&& src) :
        enable(std::move(src.enable)),
        releaseNoteOff(std::move(src.releaseNoteOff)),
        attackTime(std::move(src.attackTime)),
        decayTime(std::move(src.decayTime)),
        sustainLevel(std::move(src.sustainLevel)),
        releaseTime(std::move(src.releaseTime)),
        gain(std::move(src.gain))
      {}

      Envelope& operator=(const Envelope& src)
      {
        if(this != &src)
        {
          enable = src.enable;
          releaseNoteOff = src.releaseNoteOff;
          attackTime = src.attackTime;
          decayTime = src.decayTime;
          sustainLevel = src.sustainLevel;
          releaseTime = src.releaseTime;
          gain = src.gain;
        }
        return *this;
      }

      Envelope& operator=(Envelope&& src)
      {
        if(this != &src)
        {
          enable = std::move(src.enable);
          releaseNoteOff = std::move(src.releaseNoteOff);
          attackTime = std::move(src.attackTime);
          decayTime = std::move(src.decayTime);
          sustainLevel = std::move(src.sustainLevel);
          releaseTime = std::move(src.releaseTime);
          gain = std::move(src.gain);
        }
        return *this;
      }

      bool enable;
      bool releaseNoteOff;
      float attackTime;
      float decayTime;
      float sustainLevel;
      float releaseTime;
      float gain;
    };

    struct Timber;
    enum struct OscillatorCategory
    {
      WaveTable,SquwareWave,SinWave
    };
    // オシレータインターフェース
    struct Oscillator 
    {
      typedef  std::unique_ptr<Oscillator> PtrType; 
      virtual ~Oscillator(){}
      virtual void Process(float * data,float delta,float offset) = 0;
      virtual float SampleRate() const = 0;
      virtual float CalcPitchDelta(float sampleRate) = 0;
//      virtual void Init(Timber& timber) = 0;
      virtual OscillatorCategory Category() = 0;
      virtual PtrType Clone() = 0;
    };

    // ウェーブデータ
    typedef std::vector<float> WaveData;

    // ウェーブテーブル
    struct WaveTable 
    {
      WaveTable() :
        stereo(false),
        sampleRate(44100.0f),//
        basePitch(0.0f)
      {};

      WaveTable(const WaveTable& src) :
        stereo(src.stereo),
        sampleRate(src.sampleRate),//
        basePitch(src.basePitch)
      {waveData = src.waveData;};

      WaveTable(WaveTable&& src) :
        stereo(std::move(src.stereo)),
        sampleRate(std::move(src.sampleRate)),//
        basePitch(std::move(src.basePitch))
      {waveData = std::move(src.waveData);};

      WaveTable& operator=(const WaveTable& src)
      {
        if(this != &src)
        {
          stereo = src.stereo;
          sampleRate = src.sampleRate;//
          basePitch = src.basePitch;
          waveData = src.waveData;
        }
        return *this;
      }

      WaveTable& operator=(WaveTable&& src)
      {
        if(this != &src)
        {
          stereo = std::move(src.stereo);
          sampleRate = std::move(src.sampleRate);//
          basePitch = std::move(src.basePitch);
          waveData = std::move(src.waveData);
        }
        return *this;
      }

      bool stereo;
      float sampleRate;//
      float basePitch;
      WaveData waveData;
      static std::vector<WaveTable> WaveTables;
    };

    // LFO
    struct LFO
    {
      LFO() : 
        waveForm(nullptr),
        startNoteOn(true),
        freq(20.0f),
        gain(1.0f)
      {}
      LFO(const LFO &src)
        :
        waveForm(src.waveForm),
        envelope(src.envelope),
        startNoteOn(src.startNoteOn),
        freq(src.freq),
        gain(src.gain) 
      {}

      LFO(LFO &&src) :
        waveForm(std::move(src.waveForm)),
        envelope(std::move(src.envelope)),
        startNoteOn(std::move(src.startNoteOn)),
        freq(std::move(src.freq)),
        gain(std::move(src.gain)) 
      {}

      LFO& operator=(const LFO &src)
      {
        if(this !=  &src)
        {
          waveForm = src.waveForm;
          envelope = src.envelope;
          startNoteOn = src.startNoteOn;
          freq = src.freq;
          gain = src.gain; 

        }
        return *this;
      }
      LFO& operator=(LFO &&src)
      {
        if(this !=  &src)
        {
          waveForm = std::move(src.waveForm);
          envelope = std::move(src.envelope);
          startNoteOn = std::move(src.startNoteOn);
          freq = std::move(src.freq);
          gain = std::move(src.gain); 

        }
        return *this;
      }

      bool startNoteOn;
      WaveTable* waveForm;
      Envelope envelope;
      float freq;
      float gain;
    };

    // フィルターカテゴリー
    enum struct FilterCategory
    {
      LPF,HPF,BPF
    };

    // フィルター
    struct Filter
    {
      Filter() : category(FilterCategory::LPF),level(0.0f),resonance(0.0f),cutoff(0.0f){};
      Filter(const Filter& src) :
        category(src.category),
        level(src.level),
        cutoff(src.cutoff),
        resonance(src.resonance),
        envelope(src.envelope),
        lfo(src.lfo)
      {
      }

      Filter( Filter&& src) :
        category(std::move(src.category)),
        level(std::move(src.level)),
        cutoff(std::move(src.cutoff)),
        resonance(std::move(src.resonance)),
        envelope(std::move(src.envelope)),
        lfo(std::move(src.lfo))
      {
      }

      Filter& operator= (const Filter& src)
      {
        if(this != &src)
        {
          category = src.category;
          level = src.level;
          cutoff = src.cutoff;
          resonance = src.resonance;
          envelope = src.envelope;
          lfo = src.lfo;
        }
        return *this;
      }

      Filter& operator= (Filter&& src)
      {
        if(this != &src)
        {
          category = std::move(src.category);
          level = std::move(src.level);
          cutoff = std::move(src.cutoff);
          resonance = std::move(src.resonance);
          envelope = std::move(src.envelope);
          lfo = std::move(src.lfo);
        }
        return *this;
      }

      FilterCategory category;
      float level;
      float cutoff;
      float resonance;
      Envelope envelope;
      LFO  lfo;
    };

    // 音量パラメータ
    struct Amplitude
    {
      Amplitude() : gain(1.0f) {}
      Amplitude(const Amplitude& src) :
        gain(src.gain),
        envelope(src.envelope),
        lfo(src.lfo)
      {}

      Amplitude(Amplitude&& src) :
        gain(std::move(src.gain)),
        envelope(std::move(src.envelope)),
        lfo(std::move(src.lfo))
      {}
      Amplitude& operator= (const Amplitude& src)
      {
        if(this != &src)
        {
          gain = src.gain;
          envelope = src.envelope;
          lfo = src.lfo;
        }
        return *this;
      }

      Amplitude& operator= (Amplitude&& src)
      {
        if(this != &src)
        {
          gain = std::move(src.gain);
          envelope = std::move(src.envelope);
          lfo = std::move(src.lfo);
        }
        return *this;
      }

      float gain;
      Envelope envelope;
      LFO lfo;
    };

    // 周波数パラメータ
    struct Pitch {
      Pitch() : pitch(1.0f){}
      Pitch(const Pitch& src) :
        pitch(src.pitch),
        envelope(src.envelope),
        lfo(src.lfo)
      {}

      Pitch(Pitch&& src) :
        pitch(std::move(src.pitch)),
        envelope(std::move(src.envelope)),
        lfo(std::move(src.lfo))
      {}
      Pitch& operator= (const Pitch& src)
      {
        if(this != &src)
        {
          pitch = src.pitch;
          envelope = src.envelope;
          lfo = src.lfo;
        }
        return *this;
      }

      Pitch& operator= (Pitch&& src)
      {
        if(this != &src)
        {
          pitch = std::move(src.pitch);
          envelope = std::move(src.envelope);
          lfo = std::move(src.lfo);
        }
        return *this;
      }

      float pitch;
      Envelope envelope;
      LFO lfo;
    };

    // 定位パラメータ
    struct Pan {
      Pan() : pan(0.5f) {};
      Pan(const Pan& src) :
        pan(src.pan),
        envelope(src.envelope),
        lfo(src.lfo)
      {}

      Pan(Pan&& src) :
        pan(std::move(src.pan)),
        envelope(std::move(src.envelope)),
        lfo(std::move(src.lfo))
      {}

      Pan& operator= (const Pan& src)
      {
        if(this != &src)
        {
          pan = src.pan;
          envelope = src.envelope;
          lfo = src.lfo;

        }
        return *this;
      }

      Pan& operator= (Pan&& src)
      {
        if(this != &src)
        {
          pan = std::move(src.pan);
          envelope = std::move(src.envelope);
          lfo = std::move(src.lfo);
        }
        return *this;
      }

      float pan;
      Envelope envelope;
      LFO lfo;
    };

    typedef std::vector<WaveTable> WaveTablesType;

    struct WaveTableOscillator : public Oscillator
    {
      typedef std::function<void (float * ,float,float)> ProcessorType;

      WaveTableOscillator() : waveTable_(nullptr),waveCounter_(0.0f)
      {

      };

      explicit WaveTableOscillator(WaveTable* waveTable) : waveTable_(nullptr),waveCounter_(0.0f)
      {
        WaveTable(waveTable);
      }

      WaveTableOscillator(const WaveTableOscillator& src) : waveTable_(nullptr),
        waveCounter_(src.waveCounter_)
      {
        if(src.waveTable_ != nullptr)
        {
          WaveTable(src.waveTable_);
        }

      }

      WaveTableOscillator(WaveTableOscillator&& src) : 
        waveTable_(std::move(nullptr)),
        waveCounter_(std::move(src.waveCounter_))
      {
        if(src.waveTable_ != nullptr)
        {
          WaveTable(src.waveTable_);
        }

      }

      WaveTableOscillator& operator= (const WaveTableOscillator& src)
      {
        if(this != &src)
        {
          this->waveCounter_ = src.waveCounter_;
          WaveTable(src.waveTable_);
        }
        return *this;
      }

      WaveTableOscillator& operator= (WaveTableOscillator&& src)
      {
        if(this != &src)
        {
          this->waveCounter_ = std::move(src.waveCounter_);
          WaveTable(src.waveTable_);
        }
        return *this;
      }

      void Process(float * data,float delta,float offset)
      {

        //if(waveTable_->stereo)
        //{
        //  ProcessStereo(data,delta);
        //} else {
        //  ProcessMono(data,delta);
        //}
        processor_(data,delta,offset);
      }

      void ProcessStereo(float * data,float delta,float offset)
      {
        waveCounter_ += delta * (offset + waveTable_->basePitch);

        if(waveTable_->waveData.size() <= (int)(waveCounter_ * 2.0f))
        {
          waveCounter_ -= (float)waveTable_->waveData.size() / 2.0f;
        }

        int index = (float) waveCounter_;
        float *src = &waveTable_->waveData[index * 2];
        *data = *src;
        ++data;++src;
        *data = *src;

      }

      void ProcessMono(float * data,float delta,float offset)
      {
        waveCounter_ += delta * (offset + waveTable_->basePitch);

        if(waveTable_->waveData.size() <= (int)(waveCounter_))
        {
          waveCounter_ -= (float)waveTable_->waveData.size();
        }
        int index = (int) waveCounter_;
        float d = waveTable_->waveData[index];
        *data++ = d;
        *data = d;

      }

      float SampleRate() const {return waveTable_->sampleRate;}
      float CalcPitchDelta(float sampleRate)
      {
        return SampleRate() * ((float)WaveTable().waveData.size() / sampleRate);         
      }

      //void Init()
      //{
      //    //WaveTable(&(WaveTable::WaveTables[timber.waveTableNo]));
      //}

      OscillatorCategory Category() {return OscillatorCategory::WaveTable;};

      PtrType Clone() {return PtrType(new WaveTableOscillator(*this));}

      Synthesizer::WaveTable& WaveTableOscillator::WaveTable() {return *waveTable_;}
      void Synthesizer::WaveTableOscillator::WaveTable(Synthesizer::WaveTable *p);

    private:
      Synthesizer::WaveTable *waveTable_;
      float waveCounter_;
      ProcessorType processor_;
    };

    // 音色パラメータ
    struct Timber 
    {
      Timber() {};
      Timber(const Timber& src) :
        amplitude(src.amplitude),
        filter(src.filter),
        pitch(src.pitch),
        pan(src.pan)
      {
        if(src.oscillator.get() != nullptr){
          oscillator = src.oscillator->Clone();
        }
      }

      Timber(Timber&& src) :
//        waveTableNo(std::move(src.waveTableNo)),
        amplitude(std::move(src.amplitude)),
        filter(std::move(src.filter)),
        pitch(std::move(src.pitch)),
        pan(std::move(src.pan))
      {
        if(src.oscillator.get() != nullptr){
          oscillator.reset(src.oscillator.release());
        }
      }

      Timber& operator= (const Timber& src)
      {
        if(this != &src)
        {
   //       waveTableNo = src.waveTableNo;
        if(src.oscillator.get() != nullptr){
          oscillator  = src.oscillator->Clone();
        }
          amplitude = src.amplitude;
          filter = src.filter;
          pitch = src.pitch;
          pan = src.pan;

        }
        return *this;
      }

      Timber& operator= (Timber&& src)
      {
        if(this != &src)
        {
 //         waveTableNo = std::move(src.waveTableNo);
        if(src.oscillator.get() != nullptr){
          oscillator.reset(src.oscillator.release());
        }
          amplitude = std::move(src.amplitude);
          filter = std::move(src.filter);
          pitch = std::move(src.pitch);
          pan = std::move(src.pan);
        }
        return *this;
      }

      Oscillator::PtrType oscillator;
//      int waveTableNo;
      Amplitude amplitude;
      Filter filter;
      Pitch pitch;
      Pan pan;
    };

    // 音色プログラムパラメータ
    struct Program {
      Program(){}
      Program(const std::wstring& name,const Timber& timber) :
        name(name),timber(timber)
      {}

      Program(const Program& src) 
        :
        name(src.name),timber(src.timber)
      {

      }

      Program(const Program&& src) :
        name(std::move(src.name)),timber(std::move(src.timber))
      {

      }

      Program& operator= (const Program& src)
      {
        if(this != &src)
        {
          name = src.name;
          timber = src.timber;
        }
        return *this;
      }

      Program& operator= (const Program&& src)
      {
        if(this != &src)
        {
          name = std::move(src.name);
          timber = std::move(src.timber);
        }
        return *this;
      }

      std::wstring name;
      Timber timber;
    };
    typedef std::vector<Program> ProgramsType;
    // コンストラクタ
    Synthesizer();
    // コンストラクタ
    explicit Synthesizer(WAVEFORMATEXTENSIBLE& format,int channel = 4);
    // デストラクタ
    ~Synthesizer();
    // リスタート
    void Restart();
    // 波形生成処理
    void Process(float* buffer);
    // ノートオン処理
    void NoteOn(int index,float pitch,float velocity);
    // ノートオフ処理
    void NoteOff(int index);

    // 音色プログラム
    void AddProgram(Program&& program);
    Program& GetProgram(int index);

    void UpdateProgram(int index,Program&& Program);
    size_t ProgramsSize();

    void AssignProgramToVoice(int programNo,int voiceChannelNo);

    size_t Voices() const;

    // ウェーブテーブル
    static WaveTablesType& WaveTables();

    bool isEnable() const ;
    void isEnable(bool v);

    void Clear();

  private:
    struct impl;
    std::unique_ptr<impl> impl_;
  };
}
