package jp.crestmuse.cmx.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 = 100;
  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) {
	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);
    if (nextElem == null) {
      nextElem = musicRepresentation.addChordElement(nextMeasureIndex);
      nextElem.setEvidence(currentElem.getHighestProbIndex());
      musicRepresentation.update(Type.Chord, nextElem, nextMeasureIndex);
    }
    else if(musicRepresentation.getVoicingHighElement(nextMeasureIndex) == null){
      // voicingがまだ予測されてなかったら予測する
      musicRepresentation.update(Type.Chord, nextElem, nextMeasureIndex);
    }
    nextElem.setEvidence(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();
    Iterator<ShortMessageEvent> it = messages.iterator();
    while (it.hasNext()) {
      for (int diff : map) {
        ShortMessageEvent sme = it.next();
        try {
          ShortMessage newSm = new ShortMessage();
          newSm.setMessage(sme.sm.getStatus(), diff, 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();
      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;
    }
  }

}
