// Copyright 2013 Olivier Gillet.
//
// Author: Olivier Gillet (ol.gillet@gmail.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// 
// See http://creativecommons.org/licenses/MIT/ for more information.
//
// -----------------------------------------------------------------------------
//
// MIDI event handler.

#include "yarns/midi_handler.h"

#include <algorithm>

#include "yarns/multi.h"
#include "yarns/storage_manager.h"

namespace yarns {

using namespace std;

/* static */
MidiHandler::MidiBuffer MidiHandler::input_buffer_; 

/* static */
MidiHandler::MidiBuffer MidiHandler::output_buffer_;

/* static */
MidiHandler::SmallMidiBuffer MidiHandler::high_priority_output_buffer_;

/* static */
stmlib_midi::MidiStreamParser<MidiHandler> MidiHandler::parser_;

/* static */
const MidiHandler::SysExDescription MidiHandler::accepted_sysex_[] = {
  { { 0xf0, 0x00, 0x21, 0x02, 0x00, 0x0b }, 6, 0xff,
      &MidiHandler::HandleYarnsSpecificMessage },
  { { 0xf0, 0x7f, 0xff, 0x08, 0x08 }, 5, 21,
      &MidiHandler::HandleScaleOctaveTuning1ByteForm },
  { { 0xf0, 0x7e, 0xff, 0x08, 0x08 }, 5, 21,
      &MidiHandler::HandleScaleOctaveTuning1ByteForm },
  { { 0xf0, 0x7f, 0xff, 0x08, 0x09 }, 5, 33,
      &MidiHandler::HandleScaleOctaveTuning2ByteForm },
  { { 0xf0, 0x7e, 0xff, 0x08, 0x09 }, 5, 33,
      &MidiHandler::HandleScaleOctaveTuning2ByteForm }
};

/* static */
uint8_t MidiHandler::sysex_rx_write_ptr_;

/* static */
uint8_t MidiHandler::previous_packet_index_;

/* static */
uint8_t MidiHandler::sysex_rx_buffer_[kSysexRxBufferSize];

/* static */
uint8_t MidiHandler::calibration_voice_;

/* static */
uint8_t MidiHandler::calibration_note_;

/* static */
bool MidiHandler::factory_testing_requested_;

/* static */
void MidiHandler::Init() {
  input_buffer_.Init();
  output_buffer_.Init();
  high_priority_output_buffer_.Init();
  sysex_rx_write_ptr_ = 0;
  previous_packet_index_ = 0;
  calibration_voice_ = 0xff;
  calibration_note_ = 0xff;
  factory_testing_requested_ = false;
}

/* static */
void MidiHandler::DecodeSysExMessage() {
  uint8_t length = sysex_rx_write_ptr_;

  if (sysex_rx_buffer_[length - 1] != 0xf7) {
    // Discard long messages that have been truncated.
    return;
  }
  
  for (uint8_t i = 0;
       i < sizeof(accepted_sysex_) / sizeof(SysExDescription);
       ++i) {
    const SysExDescription& description = accepted_sysex_[i];
    if (description.expected_size == length ||
        description.expected_size == 0xff) {
      bool match = true;
      for (uint8_t j = 0; j < description.prefix_length; ++j) {
        if (description.prefix[j] != sysex_rx_buffer_[j] &&
            description.prefix[j] != 0xff) {
          match = false;
          break;
        }
      }
      if (match) {
        (*description.handler)();
        break;
      }
    }
  }
}

/* static */
void MidiHandler::HandleScaleOctaveTuning1ByteForm() {
  for (uint8_t pitch_class = 0; pitch_class < 12; ++pitch_class) {
    int16_t correction = sysex_rx_buffer_[8 + pitch_class];
    correction -= 64;
    correction = (correction * 128 + (correction > 0 ? 64 : -64)) / 100;
    multi.set_custom_pitch(pitch_class, correction);
  }
}

/* static */
void MidiHandler::HandleScaleOctaveTuning2ByteForm() {
  for (uint8_t pitch_class = 0; pitch_class < 12; ++pitch_class) {
    int16_t correction = sysex_rx_buffer_[8 + pitch_class * 2] << 7;
    correction += sysex_rx_buffer_[9 + pitch_class * 2];
    correction -= 8192;
    correction >>= 6;
    multi.set_custom_pitch(pitch_class, correction);
  }
}


enum SysExCommand {
  SYSEX_COMMAND_DUMP_PACKET = 1,
  SYSEX_COMMAND_REQUEST_PACKETS = 17,
  SYSEX_COMMAND_FACTORY_TESTING_MODE = 32,
  SYSEX_COMMAND_CALIBRATE = 33,
};

/* static */
void MidiHandler::HandleYarnsSpecificMessage() {
  uint8_t command = sysex_rx_buffer_[6];
  if (command == SYSEX_COMMAND_DUMP_PACKET) {
    uint8_t packet_index = sysex_rx_buffer_[7];
    
    // Handle packet reception.
    if (packet_index != 0 && packet_index != previous_packet_index_ + 1) {
      // Packet not in sequence!
      return;
    }
    previous_packet_index_ = packet_index;
    
    // Denibblize.
    uint8_t* data = &sysex_rx_buffer_[8];
    uint8_t* byte_ptr = data;
    uint8_t* nibble_ptr = data;
    uint8_t checksum = 0;
    while (*nibble_ptr != 0xf7 &&
           static_cast<size_t>(byte_ptr - data) <= kSysexMaxChunkSize + 1) {
      *byte_ptr = (*nibble_ptr++) << 4;
      *byte_ptr |= (*nibble_ptr++);
      // Warning! The last byte of the block, which is the checksum
      // is summed here!
      checksum += *byte_ptr++;
    }
    size_t size = byte_ptr - data - 1;
    checksum -= data[size];
    if (checksum != data[size]) {
      previous_packet_index_ = 0xff;
      return;
    }
    if (size != 0) {
      storage_manager.AppendData(data, size, packet_index == 0);
    } else if (packet_index) {
      storage_manager.DeserializeMulti();
    }
  } else if (command == SYSEX_COMMAND_REQUEST_PACKETS) {
    if (sysex_rx_buffer_[7] == 0 &&
        sysex_rx_buffer_[8] == 0 && 
        sysex_rx_buffer_[9] == 0 &&
        sysex_rx_buffer_[10] == 0xf7) {
      storage_manager.SysExSendMulti();
    }
  } else if (command == SYSEX_COMMAND_FACTORY_TESTING_MODE) {
    if (sysex_rx_buffer_[7] == 0 &&
        sysex_rx_buffer_[8] == 0 && 
        sysex_rx_buffer_[9] == 0 &&
        sysex_rx_buffer_[10] == 0xf7) {
      factory_testing_requested_ = true;
    }
  } else if (command == SYSEX_COMMAND_CALIBRATE) {
    calibration_voice_ = sysex_rx_buffer_[7] >> 4;
    calibration_note_ = sysex_rx_buffer_[7] & 0xf;
    if (calibrating()) {
      uint16_t dac_code = (sysex_rx_buffer_[8] << 12) | (sysex_rx_buffer_[9] << 8) |
        (sysex_rx_buffer_[10] << 4) | (sysex_rx_buffer_[11] << 0);
      Voice* voice = multi.mutable_voice(calibration_voice_);
      voice->set_calibration_dac_code(calibration_note_, dac_code);
    } else {
      storage_manager.SaveCalibration();
    }
  }
}

/* static */
void MidiHandler::SysExSendPacket(
    uint8_t packet_index,
    const uint8_t* data,
    size_t size) {
  Flush();
  
  for (uint8_t i = 0; i < 6; ++i) {
    SendBlocking(accepted_sysex_[0].prefix[i]);
  }
  SendBlocking(SYSEX_COMMAND_DUMP_PACKET);
  SendBlocking(packet_index);
  
  // Outputs the data.
  uint8_t checksum = 0;
  for (uint8_t i = 0; i < size; ++i) {
    checksum += data[i];
    SendBlocking(data[i] >> 4);
    SendBlocking(data[i] & 0x0f);
  }
  // Outputs a checksum.
  SendBlocking(checksum >> 4);
  SendBlocking(checksum & 0x0f);

  // End of SysEx block.
  SendBlocking(0xf7);
  Flush();
}

/* static */
void MidiHandler::SysExSendPackets(const uint8_t* data, size_t size) {
  uint8_t block_index = 0;
  while (size) {
    size_t chunk_size = min(size, kSysexMaxChunkSize);
    SysExSendPacket(block_index, data, chunk_size);
    size -= chunk_size;
    data += chunk_size;
    ++block_index;
  }
  // Send a NULL packet to indicate end of transmission.
  SysExSendPacket(block_index, NULL, 0);
}

/* extern */
MidiHandler midi_handler;

}  // namespace yarns