package jp.crestmuse.cmx.bayesband.inference;

import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.Sequence;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Track;
import javax.sound.midi.Receiver;

import jp.crestmuse.cmx.inference.MusicRepresentation;
import jp.crestmuse.cmx.inference.MusicRepresentation.MusicElement; //import jp.crestmuse.cmx.inference.MusicRepresentation.Type;
import jp.crestmuse.cmx.sound.SequenceGeneratable;
import jp.crestmuse.cmx.sound.SequencerManager;

public class AccompanimentGenerator implements SequenceGeneratable {

  public int VELOCITY = 127;
  private MusicRepresentation musicRepresentation;
  private List<ShortMessageEvent> messages;
  private List<ShortMessageEvent> channelTen;
  private List<ShortMessageEvent> programChanges;

  public AccompanimentGenerator(MusicRepresentation mr, String smf)
      throws InvalidMidiDataException, IOException {
    this.musicRepresentation = mr;
    this.messages = new LinkedList<ShortMessageEvent>();
    this.channelTen = new LinkedList<ShortMessageEvent>();
    this.programChanges = new LinkedList<ShortMessageEvent>();
    Sequence seq = MidiSystem.getSequence(new File(smf));
    for (Track track : seq.getTracks()) {
      for (int i = 0; i < track.size(); i++) {
        MidiEvent me = track.get(i);
        MidiMessage mm = me.getMessage();
        int status = mm.getStatus() & 0xff;
        // 2chでノートオンかオフのとき
        if (status == 129 || status == 145) {
          ShortMessage sm = new ShortMessage();
          sm.setMessage(status, mm.getMessage()[1], mm.getMessage()[2]);
          messages.add(new ShortMessageEvent(sm, me.getTick()));
        } else if (status == 137 || status == 153) {
          ShortMessage sm = new ShortMessage();
          sm.setMessage(status, mm.getMessage()[1], mm.getMessage()[2]);
          channelTen.add(new ShortMessageEvent(sm, me.getTick()));
        } else if ((status & 0xF0) == ShortMessage.PROGRAM_CHANGE) {
          programChanges.add(new ShortMessageEvent((ShortMessage) mm, me
              .getTick()));
        }
      }
    }
  }

  public void sendInitializingMessages(Receiver r) {
    try {
      ShortMessage reset = new ShortMessage();
      reset.setMessage(ShortMessage.SYSTEM_RESET);
      r.send(reset, 0);
    } catch (InvalidMidiDataException e) {
      e.printStackTrace();
    }
    for (ShortMessageEvent pc : programChanges)
      r.send(pc.sm, pc.tick);
  }

  public boolean changeMeasure(Track track, long measureTick) {
    int nextMeasureIndex = (int) measureTick
        / (SequencerManager.TICKS_PER_BEAT * 4)
        * musicRepresentation.getDivision();
    if (nextMeasureIndex >= musicRepresentation.getMeasureNum()
        * musicRepresentation.getDivision())
      return false;
    /*
    MusicElement currentElem = musicRepresentation.getChordElement(Math.max(
        nextMeasureIndex - musicRepresentation.getDivision(), 0));
    MusicElement nextElem = musicRepresentation
        .getChordElement(nextMeasureIndex);
    */
    MusicElement currentElem = musicRepresentation.getMusicElement("chord",
        Math.max(nextMeasureIndex - musicRepresentation.getDivision(), 0));
    MusicElement nextElem = musicRepresentation.getMusicElement("chord",
        nextMeasureIndex);
    if (!nextElem.set()) {
      nextElem.setEvidence(currentElem.getHighestProbIndex());
      // musicRepresentation.update(Type.Chord, nextElem, nextMeasureIndex);
      musicRepresentation.update("chord", nextMeasureIndex);
    } else if (!musicRepresentation.getMusicElement("voicingHigh",
        nextMeasureIndex).set()) {
      // voicingがまだ予測されてなかったら予測する
      // musicRepresentation.update(Type.Chord, nextElem, nextMeasureIndex);
      musicRepresentation.update("chord", nextMeasureIndex);
    }
    nextElem.setEvidence(nextElem.getHighestProbIndex());
    // GUI.getInstance().changeMeasure(nextElem.getHighestProbIndex());
    System.err.println(" <<< "
        + nextElem.getLabel(nextElem.getHighestProbIndex()));

    // 伴奏データをシーケンサに追加
    int[] map = new int[4];
    /*
    map[0] = musicRepresentation.getVoicingHighElement(nextMeasureIndex)
        .getHighestProbIndex();
    map[1] = musicRepresentation.getVoicingMidHighElement(nextMeasureIndex)
        .getHighestProbIndex();
    map[2] = musicRepresentation.getVoicingMidLowElement(nextMeasureIndex)
        .getHighestProbIndex();
    map[3] = musicRepresentation.getVoicingLowElement(nextMeasureIndex)
        .getHighestProbIndex();
    */
    map[0] = musicRepresentation.getMusicElement("voicingHigh",
        nextMeasureIndex).getHighestProbIndex();
    map[1] = musicRepresentation.getMusicElement("voicingMidHigh",
        nextMeasureIndex).getHighestProbIndex();
    map[2] = musicRepresentation.getMusicElement("voicingMidLow",
        nextMeasureIndex).getHighestProbIndex();
    map[3] = musicRepresentation
        .getMusicElement("voicingLow", nextMeasureIndex).getHighestProbIndex();
    Iterator<ShortMessageEvent> it = messages.iterator();
    while (it.hasNext()) {
      for (int notenum : map) {
        ShortMessageEvent sme = it.next();
        try {
          ShortMessage newSm = new ShortMessage();
          newSm.setMessage(sme.sm.getStatus(), notenum, sme.sm.getData2());
          // newSm.setMessage(sme.sm.getStatus(), sme.sm.getData1() + diff,
          // sme.sm.getData2());
          track.add(new MidiEvent(newSm, sme.tick + measureTick));
        } catch (InvalidMidiDataException e) {
          e.printStackTrace();
        }
      }
    }

    for (ShortMessageEvent sme : channelTen) {
      try {
        ShortMessage sm = new ShortMessage();
        sm.setMessage(sme.sm.getStatus(), sme.sm.getData1(), sme.sm.getData2());
        track.add(new MidiEvent(sm, sme.tick + measureTick));
      } catch (Exception e) {
        e.printStackTrace();
      }
    }

    // ベースデータをシーケンサに追加
    for (int i = 0; i < 4; i++) {
      // int num = musicRepresentation.getBassElement(nextMeasureIndex + i
      // 2).getHighestProbIndex();
      int num = musicRepresentation.getMusicElement("bass",
          nextMeasureIndex + i * 2).getHighestProbIndex();
      try {
        ShortMessage sm = new ShortMessage();
        sm.setMessage(ShortMessage.NOTE_ON, 2, num, VELOCITY);
        track.add(new MidiEvent(sm, measureTick + i
            * SequencerManager.TICKS_PER_BEAT));
        sm = new ShortMessage();
        sm.setMessage(ShortMessage.NOTE_OFF, 2, num, VELOCITY);
        track.add(new MidiEvent(sm, measureTick + (i + 1)
            * SequencerManager.TICKS_PER_BEAT));
      } catch (InvalidMidiDataException e) {
        e.printStackTrace();
      }
    }
    return true;
  }

  private class ShortMessageEvent {
    ShortMessage sm;
    long tick;

    ShortMessageEvent(ShortMessage sm, long tick) {
      this.sm = sm;
      this.tick = tick;
    }
  }

}
