/*!
  \file
  \brief S-Format ファイルの送信処理

  \author Satofumi KAMIMURA

  $Id: SformatSender.cpp 1489 2009-11-02 08:25:58Z satofumi $
*/

#include "SformatSender.h"
#include "SformatHandler.h"
#include "Connection.h"
#include "delay.h"
#include <map>
#include <fstream>
#include <cerrno>
#include <cstring>

using namespace qrk;
using namespace std;

enum {
  WaitPackets = 1,              // マイコン側のパケットのバッファ数
  Timeout = 100,                // [msec]
};


namespace
{
  typedef map<unsigned long, string> AddressData;


  unsigned long alignedAddress(unsigned long address)
  {
    return address & ~0x1f;
  }


  void setData(string& data, SformatHandler::sformat_t& sformat)
  {
    vector<char>& sformat_data = sformat.data;
    size_t n = sformat_data.size();

    size_t offset = sformat.address & 0x1f;
    for (size_t i = 0; i < n; ++i) {
      data[offset + i] = sformat_data[i];
    }
  }


  // データを送信する必要がある場合、true を返す
  bool setAddress(AddressData& address_data,
                  unsigned long aligned_address,
                  SformatHandler::sformat_t& sformat)
  {
    AddressData::iterator it = address_data.begin();
    if ((! address_data.empty()) && it->first == aligned_address) {
      // 既存のデータに追加
      setData(it->second, sformat);
      return false;
    }

    bool send_request = false;
    if (! address_data.empty()) {
      // データが２つ以上になったら、データ送信を行わせる
      send_request = true;
    }

    // 新規アドレスに追加
    string data;
    data.resize(32);
    memset(&data[0], 0, 32);

    setData(data, sformat);
    address_data[aligned_address] = data;

    return send_request;
  }


  unsigned long firstAddress(const AddressData& address_data)
  {
    AddressData::const_iterator it = address_data.begin();
    return it ->first;
  }


  void setChecksum(char* buffer, size_t n)
  {
    char sum = 0x00;
    for (size_t i = 0; i < n; ++i) {
      sum += buffer[i];
    }
    buffer[n] = sum;
  }
}


struct SformatSender::pImpl
{
  string error_message_;
  Connection* connection_;
  long transfer_baudrate_;
  int wait_replies_;
  bool is_dump_;
  ofstream dump_fout_;


  pImpl(Connection* connection, long baudrate)
    : error_message_("no error."),
      connection_(connection), transfer_baudrate_(baudrate), wait_replies_(0),
      is_dump_(false)
  {
  }


  ~pImpl(void)
  {
    if (is_dump_) {
      dump_fout_ << ";" << endl;
    }
  }


  void sendAddress(unsigned long address)
  {
    fprintf(stderr, "sendAddress: %08lx\n", address);

    // チェックサムを付加して送信する
    enum { BufferSize = 1 + 4 + 1 };
    char buffer[] = "Axxxxy";
    buffer[1] = address >> 24;
    buffer[2] = address >> 16;
    buffer[3] = address >> 8;
    buffer[4] = address;
    setChecksum(buffer, BufferSize - 1);

    connection_->send(buffer, BufferSize);

    // 応答待ちの数をインクリメント
    ++wait_replies_;
  }


  void sendData(AddressData& address_data)
  {
    unsigned long first_address = firstAddress(address_data);

    fprintf(stderr, "sendData: %08lx: ", first_address);

    // チェックサムを付加して送信する
    enum { BufferSize = 1 + 32 + 1 };
    char buffer[BufferSize];
    buffer[0] = 'D';

    string data = address_data[first_address];
    size_t n = data.size();
    for (size_t i = 0; i < n; ++i) {
      buffer[1 + i] = data[i];
      fprintf(stderr, "%02x ", static_cast<unsigned char>(data[i]));
    }
    fprintf(stderr, "\n");
    setChecksum(buffer, BufferSize - 1);
    connection_->send(buffer, BufferSize);

    if (is_dump_) {
      dump_fout_ << "  \"";
      for (size_t i = 0; i < 32; ++i) {
        char output_buffer[] = "xxxx";
        snprintf(output_buffer, 5, "\\x%02x", data[i]);
        dump_fout_.write(output_buffer, 4);

        if (i == 15) {
          dump_fout_ << "\"" << endl << "  \"";
        }
      }
      dump_fout_ << "\"" << endl;
    }

    // 送信した項目を削除する
    address_data.erase(first_address);

    // 応答待ちの数をインクリメント
    ++wait_replies_;
  }


  bool waitResponse(void)
  {
    fprintf(stderr, "waitResponse(): ");

    char ch;
    int n = connection_->receive(&ch, 1, Timeout);
    if (n <= 0) {
      error_message_ = "host: response timeout.";
      return false;
    }

    if (ch != 'A') {
      error_message_ = "host: invalid response.";
      return false;
    }

    fprintf(stderr, "O.K.\n");
    return true;
  }


  void changeBaudrate(long baudrate)
  {
    fprintf(stderr, "changeBaudrate: %ld\n", baudrate);

    enum { BufferSize = 1 + 4 + 1 };
    long send_baudrate = baudrate;
    char buffer[BufferSize];
    buffer[0] = 'B';
    for (size_t i = 0; i < 4; ++i) {
      buffer[1 + (4 - 1) - i] = send_baudrate & 0xff;
      send_baudrate >>= 8;
    }
    setChecksum(buffer, BufferSize - 1);
    connection_->send(buffer, BufferSize);

    // 応答を受け取った後、ボーレートを変更する
    char recv_ch;
    int n = connection_->receive(&recv_ch, 1, Timeout);
    if ((n != 1) || (recv_ch != 'A')) {
      return;
    }
    delay(1);
    connection_->setBaudrate(baudrate);
    fprintf(stderr, "changed.\n");
  }
};


SformatSender::SformatSender(Connection* connection, long baudrate)
  : pimpl(new pImpl(connection, baudrate))
{
}


SformatSender::~SformatSender(void)
{
}


const char* SformatSender::what(void) const
{
  return pimpl->error_message_.c_str();
}


void SformatSender::setDumpFile(const char* file)
{
  pimpl->dump_fout_.open(file);
  if (pimpl->dump_fout_.is_open()) {
    pimpl->dump_fout_ << "static char writer_program[] =" << endl;
    pimpl->is_dump_ = true;
  }
}


bool SformatSender::send(const char* mot_file)
{
  ifstream fin(mot_file);
  if (! fin.is_open()) {
    pimpl->error_message_ = strerror(errno);
    return false;
  }

  // ボーレートの変更
  pimpl->changeBaudrate(pimpl->transfer_baudrate_);

  AddressData address_data;

  unsigned long address = 0x00000000;
  string line;
  while (getline(fin, line) && (! line.empty())) {
    fprintf(stderr, "%s\n", line.c_str());
    SformatHandler::sformat_t sformat;
    if (! SformatHandler::parseSformat(&sformat, line)) {
      // 事前の mot ファイルのチェックを通過していれば、ここには分岐しないはず
      pimpl->error_message_ = string("invalid mot file: ") + mot_file;
      return false;
    }

    size_t type = sformat.type - '0';
    if ((type >= 1) && (type <= 3)) {
      unsigned long aligned_address = alignedAddress(sformat.address);
      if (setAddress(address_data, aligned_address, sformat)) {

        // 必要ならば、応答を待つ
        if (pimpl->wait_replies_ >= WaitPackets) {
          if (! pimpl->waitResponse()) {
            return false;
          }
          --pimpl->wait_replies_;
        }

        // 書き込み先のアドレスを変更する必要がある場合のみ、アドレスを送信する
        unsigned long first_address = firstAddress(address_data);
        if (first_address != address) {
          pimpl->sendAddress(first_address);
          address = first_address;
        }

        // データの送信
        pimpl->sendData(address_data);
        address += 32;
      }
    }
  }

  // 残っているデータを送信する
  pimpl->sendData(address_data);

  while (pimpl->wait_replies_ > 0) {
    if (! pimpl->waitResponse()) {
      return false;
    }
    --pimpl->wait_replies_;
  }

  return true;
}


bool SformatSender::jump(const char* mot_file)
{
  // !!! send() と重複が多いのをなんとかする

  ifstream fin(mot_file);
  if (! fin.is_open()) {
    pimpl->error_message_ = strerror(errno);
    return false;
  }

  string line;
  SformatHandler::sformat_t sformat;
  while (getline(fin, line) && (! line.empty())) {
    if (! SformatHandler::parseSformat(&sformat, line)) {
      pimpl->error_message_ = "invalid mot file: + mot_file";
      return false;
    } else if ((sformat.type < '7') || (sformat.type > '9')) {
      continue;
    }

    // 指定アドレスからのプログラム実行を指示
    enum { BufferSize = 10 + 1 };
    char buffer[BufferSize];
    snprintf(buffer, BufferSize, "J%08lx", sformat.address);
    pimpl->connection_->send(buffer, BufferSize);
    return true;
  }

  return false;
}
