///|
/// MIDI File structure
pub(all) struct MidiFile {
  format : UInt16
  division : UInt16
  tracks : Array[Track]
}

///|
pub impl Show for MidiFile with to_string(self) -> String {
  let track_strs = self.tracks.map(fn(t) { t.to_string() })
  let tracks_info = track_strs.join("\n------\n")
  "format=\{self.format}\ndivision=\{self.division}\ntracks:\{self.tracks.length()}\n\{tracks_info}"
}

///|
pub impl Show for MidiFile with output(self, logger) -> Unit {
  logger.write_string(self.to_string())
}

///|
/// Track containing MIDI events
pub(all) struct Track {
  events : Array[Event]
}

///|
impl Show for Track with to_string(self) -> String {
  let event_strs = self.events.map(fn(e) { e.to_string() })
  event_strs.join("\n")
}

///|
impl Show for Track with output(self, logger) -> Unit {
  logger.write_string(self.to_string())
}

///|
/// MIDI Event types
pub(all) enum Event {
  NoteOn(delta~ : UInt, channel~ : Byte, note~ : Byte, velocity~ : Byte)
  NoteOff(delta~ : UInt, channel~ : Byte, note~ : Byte, velocity~ : Byte)
  ControlChange(
    delta~ : UInt,
    channel~ : Byte,
    controller~ : Byte,
    value~ : Byte
  )
  ProgramChange(delta~ : UInt, channel~ : Byte, program~ : Byte)
  Aftertouch(delta~ : UInt, channel~ : Byte, value~ : Byte)
  PolyTouch(delta~ : UInt, channel~ : Byte, note~ : Byte, value~ : Byte)
  PitchWheel(delta~ : UInt, channel~ : Byte, pitch~ : Int)
  Tempo(delta~ : UInt, microseconds~ : UInt) // 0x51
  TimeSignature(
    delta~ : UInt,
    numerator~ : Byte,
    denominator~ : Byte,
    clocks_per_beat~ : Byte,
    thirty_seconds_per_quarter~ : Byte
  ) // 0x58
  KeySignature(delta~ : UInt, sf~ : Byte, mi~ : Byte) // 0x59
  Meta(delta~ : UInt, meta_type~ : Byte, data~ : Array[Byte])
  SysEx(delta~ : UInt, data~ : Array[Byte])
  Clock(delta~ : UInt)
  Start(delta~ : UInt)
  Continue(delta~ : UInt)
  Stop(delta~ : UInt)
  ActiveSensing(delta~ : UInt)
  Reset(delta~ : UInt)
}

///|
impl Show for Event with to_string(self) -> String {
  match self {
    NoteOn(delta~, channel~, note~, velocity~) =>
      "NoteOn[\{delta}][ch \{channel.to_int()}] note=\{note.to_int()}(\{note_to_pitch(note)}) vel=\{velocity.to_int()}"
    NoteOff(delta~, channel~, note~, velocity~) =>
      "NoteOff[\{delta}][ch \{channel.to_int()}] note=\{note.to_int()}(\{note_to_pitch(note)}) vel=\{velocity.to_int()}"
    ControlChange(delta~, channel~, controller~, value~) =>
      "ControlChange[\{delta}][ch \{channel.to_int()}] ctrl=\{controller.to_int()} val=\{value.to_int()}"
    ProgramChange(delta~, channel~, program~) =>
      "ProgramChange[\{delta}][ch \{channel.to_int()}] program=\{program.to_int()}: \{program_to_instrument(program)}"
    Aftertouch(delta~, channel~, value~) =>
      "Aftertouch[\{delta}][ch \{channel.to_int()}] value=\{value.to_int()}"
    PolyTouch(delta~, channel~, note~, value~) =>
      "PolyTouch[\{delta}][ch \{channel.to_int()}] note=\{note.to_int()} value=\{value.to_int()}"
    PitchWheel(delta~, channel~, pitch~) =>
      "PitchWheel[\{delta}][ch \{channel.to_int()}] pitch=\{pitch}"
    Tempo(delta~, microseconds~) =>
      "Tempo[\{delta}] microseconds=\{microseconds} (\{microseconds_to_bpm(microseconds)} BPM)"
    TimeSignature(
      delta~,
      numerator~,
      denominator~,
      clocks_per_beat~,
      thirty_seconds_per_quarter~
    ) =>
      "TimeSignature[\{delta}] numerator=\{numerator.to_int()} denominator=\{denominator.to_int()} clocks_per_tick=\{clocks_per_beat.to_int()} thirty_seconds_per_quarter=\{thirty_seconds_per_quarter.to_int()}"
    KeySignature(delta~, sf~, mi~) =>
      "KeySignature[\{delta}] sharp_flat=\{sf.to_int()} mi=\{mi.to_int()}"
    Meta(delta~, meta_type~, data~) => {
      let bytes_str = data.map(fn(b) { b.to_int().to_string() }).join(" ")
      "Meta[\{delta}] type=\{meta_type.to_int()} len=\{data.length()} [\{bytes_str}]"
    }
    SysEx(delta~, data~) => {
      let bytes_str = data.map(fn(b) { b.to_int().to_string() }).join(" ")
      "SysEx[\{delta}] len=\{data.length()} [\{bytes_str}]"
    }
    Clock(delta~) => "Clock[\{delta}]"
    Start(delta~) => "Start[\{delta}]"
    Continue(delta~) => "Continue[\{delta}]"
    Stop(delta~) => "Stop[\{delta}]"
    ActiveSensing(delta~) => "ActiveSensing[\{delta}]"
    Reset(delta~) => "Reset[\{delta}]"
  }
}

///|
impl Show for Event with output(self, logger) -> Unit {
  logger.write_string(self.to_string())
}