/*!
  \file
  \brief S-Format ファイルの解析

  http://www.geocities.jp/chako_ratta/micon/s_format.html を参照のこと

  \author Satofumi KAMIMURA

  $Id: SformatHandler.cpp 1157 2009-07-18 11:18:40Z satofumi $
*/

#include "SformatHandler.h"
#include <fstream>
#include <cerrno>
#include <cstdlib>
#include <cstring>

using namespace qrk;
using namespace std;


namespace
{
  bool isValidChecksum(const string& line)
  {
    // １行のチェックサムを評価する
    size_t n = line.size();
    if (n < 10) {
      // S5 サイズより小さい場合は、エラー扱いとする
      return false;
    }
    const char* c_line = line.c_str();
    char expected_checksum = strtol(&c_line[n - 2], NULL, 16);

    char actual_checksum = 0x00;
    for (size_t i = 2; i < n - 2; i += 2) {
      char bytes[] = "xx";
      bytes[0] = line[i];
      bytes[1] = line[i + 1];
      actual_checksum += strtol(bytes, NULL, 16);
    }

    return (~actual_checksum == expected_checksum) ? true : false;
  }


  bool isLF(const char ch)
  {
    return ((ch == 0x0a) || (ch == 0x0d)) ? true : false;
  }


  string chomp(const string& line)
  {
    size_t n = line.size();
    return (isLF(line[n - 1])) ? line.substr(0, n - 1) : line;
  }
}


struct SformatHandler::pImpl
{
  string error_message_;
  string file_;
  bool invalid_;


  pImpl(const char* file) : error_message_("no error."), file_(file)
  {
    invalid_ = ! checkFile();
  }


  bool checkFile(void)
  {
    // ファイルを読み出し、何行目が不正かを検出する
    ifstream fin(file_.c_str());
    if (! fin.is_open()) {
      error_message_ = file_ + ": " + strerror(errno);
      return false;
    }

    string line;
    for (size_t line_index = 0; getline(fin, line); ++line_index) {
      if (line.empty()) {
        break;
      }

      line = chomp(line);
      if (! isValidChecksum(line)) {
        enum { BufferSize = 12 };
        char buffer[BufferSize];
        snprintf(buffer, BufferSize, "%d", line_index);
        error_message_ = file_ + ": checksum error " + buffer + " lines";
        return false;
      }
    }

    return true;
  }
};


SformatHandler::SformatHandler(const char* file) : pimpl(new pImpl(file))
{
}


SformatHandler::~SformatHandler(void)
{
}


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


bool SformatHandler::invalid(void) const
{
  return pimpl->invalid_;
}


bool SformatHandler::parseSformat(sformat_t* sformat, const string& line)
{
  string adjusted_line = chomp(line);
  if (! isValidChecksum(adjusted_line)) {
    return false;
  }

  if (line[0] != 'S') {
    return false;
  }
  sformat->type = line[1];
  size_t type = sformat->type - '0';
  if (type == 0) {
    return true;
  }

  char size_bytes[] = "xx";
  size_bytes[0] = line[2];
  size_bytes[1] = line[3];
  size_t byte_size = strtol(size_bytes, NULL, 16) * 2;

  // アドレスの格納
  char address_bytes[] = "\0\0\0\0\0\0\0\0";
  size_t address_size = 2 + (type * 2);
  for (size_t i = 0; i < address_size; ++i) {
    address_bytes[i] = line[4 + i];
  }
  sformat->address = strtoll(address_bytes, NULL, 16);

  if (type >= 7) {
    return true;
  }

  // データの格納
  size_t data_size = byte_size - address_size - 2;
  for (size_t i = 0; i < data_size; i += 2) {
    char bytes[] = "xx";
    size_t index = 4 + address_size + i;
    bytes[0] = line[index];
    bytes[1] = line[index + 1];

    sformat->data.push_back(strtol(bytes, NULL, 16));
  }

  return true;
}
