package jp.crestmuse.cmx.bayesband;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;

import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Sequencer;

import jp.crestmuse.cmx.amusaj.filewrappers.*;
import jp.crestmuse.cmx.bayesband.filewrappers.*;
import jp.crestmuse.cmx.amusaj.sp.*;
import jp.crestmuse.cmx.bayesband.sp.*;
import jp.crestmuse.cmx.bayesband.inference.*;
import jp.crestmuse.cmx.inference.*;
import jp.crestmuse.cmx.inference.MusicRepresentation.MusicElement;
import jp.crestmuse.cmx.sound.SequencerManager;
import jp.crestmuse.cmx.sound.VirtualKeyboard;

public class BayesianBand {

  static MusicRepresentation mr;

  public static void main(String[] args) {
    try {
      if (args.length < 2) {
	  System.err.println("Usage: ");
	  System.err.println("$java -jar BayesianBand.jar <options> <bif_file> <mid_file>");
	  System.err.println("Available options are: -start, -smf, -v, -vkb");
	  System.exit(1);
      }
      int length = 8;
      String modelFile = args[0];
      String chordMIDIFile = args[1];
      String firstChord = "C";
      String inputSMF = null;
      boolean writeLog = false;
      boolean useVirtualKeyboard = false;
      for (int i = 2; i < args.length; i++) {
        if (args[i].equals("-length")) {
          length = Integer.parseInt(args[i + 1]);
          i++;
        } else if (args[i].equals("-start")) {
          firstChord = args[i + 1];
          i++;
        } else if (args[i].equals("-smf")) {
          inputSMF = args[i + 1];
          i++;
        } else if (args[i].equals("-v")) {
          writeLog = true;
        } else if (args[i].equals("-vkb")) {
          useVirtualKeyboard = true;
        }
      }

      // MusicRepresentation周り
      mr = new MusicRepresentation(length, 8);
      mr.addMusicLayer("melody", 12);
      mr.addMusicLayer("chord", new String[]{ "C", "Dm", "Em", "F", "G", "Am", "Bm(b5)" }, 8);
      mr.addMusicLayer("bass", 128, 2);
      mr.addMusicLayer("voicingHigh", 128, 8);
      mr.addMusicLayer("voicingMidHigh", 128, 8);
      mr.addMusicLayer("voicingMidLow", 128, 8);
      mr.addMusicLayer("voicingLow", 128, 8);

      BayesianCalculator ac 
	  = new BayesianCalculator(new BayesNetWrapper(modelFile));
      ac.addReadMapping(
        new BayesianMapping("melody", -1, BayesianMapping.SET_ONLY, 0));
      ac.addReadMapping
	  (new BayesianMapping("chord", -1, BayesianMapping.BY_TIED_LENGTH, 1));
      ac.addReadMapping(
        new BayesianMapping("melody", 0, BayesianMapping.NORMAL, 2));
      ac.addReadMapping(
        new BayesianMapping("chord", 0, BayesianMapping.NORMAL, 3));
      ac.addWriteMapping(
        new BayesianMapping("melody", 1, BayesianMapping.NORMAL, 4));
      ac.addWriteMapping(
        new BayesianMapping("chord", 1, BayesianMapping.BY_TIED_LENGTH, 5));

      NaiveVoicingCalculator vc = new NaiveVoicingCalculator();
      mr.addCalculator("melody", ac);
      mr.addCalculator("chord", vc);
      mr.addCalculator("melody", new ChordPrintCalculator());
      MusicElement chord = mr.getMusicElement("chord", 0);
      chord.setProb(chord.indexOf(firstChord), 1.0);
      mr.update("chord", 0);

      // SP周り
      MidiInputModule mi;
      Sequencer seqr = null;
      if (inputSMF != null) {
        seqr = MidiSystem.getSequencer(false);
        seqr.setSequence(MidiSystem.getSequence(new File(inputSMF)));
        mi = new MidiInputModule(seqr);
      } else if (useVirtualKeyboard) {
        mi = new MidiInputModule(new VirtualKeyboard());
      } else {
        mi = new MidiInputModule(getMidiDevice(true));
      }
      SPExecutor sp = new SPExecutor();
      MelodyWriter mrm = new MelodyWriter(mr, writeLog);
      MidiDevice device = getMidiDevice(false);
      device.open();
      MidiOutputModule mo = new MidiOutputModule(device.getReceiver());

      // SequencerManager周り
      SequencerManager sm = new SequencerManager(device.getReceiver());
      if (inputSMF != null)
        seqr.setMasterSyncMode(sm.getSequencer().getMasterSyncMode());
      AccompanimentGenerator mrg = new AccompanimentGenerator(mr, chordMIDIFile);
      sm.addGeneratable(mrg);

      mi.setTickTimer(sm);
      SPSpreadModule towway =
	  new SPSpreadModule(MidiEventWithTicktime.class, 2);
      sp.addSPModule(mi);
      sp.addSPModule(towway);
      sp.addSPModule(mo);
      sp.addSPModule(mrm);
      // sp.addSPModule(mrv);
      sp.connect(mi, 0, towway, 0);
      sp.connect(towway, 0, mo, 0);
      sp.connect(towway, 1, mrm, 0);
      // sp.connect(mrm, 0, mrv, 0);

      // 起動
      sp.start();
      sm.start();
      if (inputSMF != null)
        seqr.start();

      // 終了
      System.err.println("press button to exit...");
      System.in.read();
      sm.stop();
      sp.stop();
      device.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
    System.exit(0);
  }

  public static MidiDevice getMidiDevice(boolean trans) {
    MidiDevice.Info[] info = MidiSystem.getMidiDeviceInfo();
    MidiDevice device = null;

    for (int i = 0; i < info.length; i++) {
      try {
        device = MidiSystem.getMidiDevice(info[i]);
        if (trans && device.getMaxTransmitters() == 0)
          continue;
        if (!trans && device.getMaxReceivers() == 0)
          continue;
        System.err.println("*** " + i + " ***");
        System.err.println("  Description:" + info[i].getDescription());
        System.err.println("  Name:" + info[i].getName());
        System.err.println("  Vendor:" + info[i].getVendor());
        System.err.println();
      } catch (MidiUnavailableException e) {
        e.printStackTrace();
      }
    }

    try {
      BufferedReader r = new BufferedReader(new InputStreamReader(System.in), 1);
      if (trans)
        System.err.print("Using Input Device Number: ");
      else
        System.err.print("Using Output Device Number: ");
      String s = r.readLine();
      device = MidiSystem.getMidiDevice(info[Integer.parseInt(s)]);
    } catch (Exception e) {
      e.printStackTrace();
    }
    return device;
  }
}
