/*
    BCU SDK bcu development enviroment
    Copyright (C) 2005-2011 Martin Koegler <mkoegler@auto.tuwien.ac.at>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include <cassert>
#include <cstdio>
#include <cstdlib>
#include "loadctl.h"
#include "image.h"

std::string
HexDump (const CArray data)
{
  char buf[200];
  std::string s;
  unsigned int i;
  sprintf (buf, "%04X ", 0);
  s = buf;
  for (i = 0; i < data.size(); i++)
    {
      sprintf (buf, "%02x ", data[i]);
      s += buf;
      if (i % 16 == 15)
        {
          sprintf (buf, "\n%04X ", i + 1);
          s += buf;
        }
    }
  s += "\n";
  return s;
}

STR_Stream *
STR_Stream::fromArray (const CArray & c)
{
  assert (c.size() >= 4);
  assert (((c[0] << 8) | c[1]) + 2U == c.size());
  STR_Stream *i;

  switch (c[2] << 8 | c[3])
    {
    case L_STRING_PAR:
      i = new STR_StringParameter ();
      break;
    case L_INT_PAR:
      i = new STR_IntParameter ();
      break;
    case L_FLOAT_PAR:
      i = new STR_FloatParameter ();
      break;
    case L_LIST_PAR:
      i = new STR_ListParameter ();
      break;
    case L_GROUP_OBJECT:
      i = new STR_GroupObject ();
      break;
    case L_BCU_TYPE:
      i = new STR_BCUType ();
      break;
    case L_BCU2_INIT:
      i = new STR_BCU2Start ();
      break;
    case L_CODE:
      i = new STR_Code ();
      break;
    case L_BCU1_SIZE:
      i = new STR_BCU1Size ();
      break;
    case L_BCU2_SIZE:
      i = new STR_BCU2Size ();
      break;
    case L_BCU2_KEY:
      i = new STR_BCU2Key ();
      break;
    default:
      i = new STR_Unknown ();
      break;
    }
  if (i->init (c))
    return i;
  delete i;
  i = new STR_Invalid ();
  i->init (c);
  return i;
}

bool
STR_Invalid::init (const CArray & c)
{
  data = c;
  return true;
}

CArray
STR_Invalid::toArray () const
{
  return data;
}

std::string
STR_Invalid::decode () const
{
  char buf[200];
  std::string s;
  sprintf (buf, "Invalid:\n");
  s = buf;
  return s + HexDump (data);
}

bool
STR_Unknown::init (const CArray & c)
{
  data.assign (c.begin() + 4, c.end());
  type = c[2] << 8 | c[3];
  return true;
}

CArray
STR_Unknown::toArray () const
{
  CArray d;
  uint16_t len = 2 + data.size();
  d.resize (2 + len);
  d[0] = (len >> 8) & 0xff;
  d[1] = (len) & 0xff;
  d[2] = (type >> 8) & 0xff;
  d[3] = (type) & 0xff;
  d.setpart (data, 4);
  return d;
}

std::string
STR_Unknown::decode () const
{
  char buf[200];
  std::string s;
  sprintf (buf, "Unknown %04x:\n", type);
  s = buf;
  return s + HexDump (data);
}

bool
STR_BCUType::init (const CArray & c)
{
  if (c.size() != 6)
    return false;
  bcutype = c[4] << 8 | c[5];
  return true;
}

CArray
STR_BCUType::toArray () const
{
  CArray d;
  uint16_t len = 4;
  d.resize (2 + len);
  d[0] = (len >> 8) & 0xff;
  d[1] = (len) & 0xff;
  d[2] = (L_BCU_TYPE >> 8) & 0xff;
  d[3] = (L_BCU_TYPE) & 0xff;
  d[4] = (bcutype >> 8) & 0xff;
  d[5] = (bcutype) & 0xff;
  return d;
}

std::string
STR_BCUType::decode () const
{
  char buf[200];
  sprintf (buf, "Maskversion: %04x\n", bcutype);
  return buf;
}

bool
STR_Code::init (const CArray & c)
{
  code.set (c.data() + 4, c.size() - 4);
  return true;
}

CArray
STR_Code::toArray () const
{
  CArray d;
  uint16_t len = 2 + code.size();
  d.resize (2 + len);
  d[0] = (len >> 8) & 0xff;
  d[1] = (len) & 0xff;
  d[2] = (L_CODE >> 8) & 0xff;
  d[3] = (L_CODE) & 0xff;
  d.setpart (code, 4);
  return d;
}

std::string
STR_Code::decode () const
{
  char buf[200];
  std::string s;
  sprintf (buf, "Code:\n");
  s = buf;
  return s + HexDump (code);
}

bool
STR_StringParameter::init (const CArray & c)
{
  const uint8_t *d;
  if (c.size() < 9)
    return false;
  addr = c[4] << 8 | c[5];
  length = c[6] << 8 | c[7];
  if (c[c.size() - 1])
    return false;
  d = &c[8];
  while (*d)
    d++;
  if (d != &c[c.size() - 1])
    return false;
  name = (const char *) c.data() + 8;
  return true;
}

CArray
STR_StringParameter::toArray () const
{
  CArray d;
  uint16_t len = 7 + name.length();
  d.resize (2 + len);
  d[0] = (len >> 8) & 0xff;
  d[1] = (len) & 0xff;
  d[2] = (L_STRING_PAR >> 8) & 0xff;
  d[3] = (L_STRING_PAR) & 0xff;
  d[4] = (addr >> 8) & 0xff;
  d[5] = (addr) & 0xff;
  d[6] = (length >> 8) & 0xff;
  d[7] = (length) & 0xff;
  d.setpart (name, 8);
  return d;
}

std::string
STR_StringParameter::decode () const
{
  char buf[200];
  sprintf (buf, "StringParameter: addr=%04x id=%s length=%d\n", addr, name.c_str(),
           length);
  return buf;
}

bool
STR_IntParameter::init (const CArray & c)
{
  const uint8_t *d;
  if (c.size() < 8)
    return false;
  addr = c[4] << 8 | c[5];
  type = (int8_t) c[6];
  if (c[c.size() - 1])
    return false;
  d = &c[7];
  while (*d)
    d++;
  if (d != &c[c.size() - 1])
    return false;
  name = (const char *) c.data() + 7;
  return true;
}

CArray
STR_IntParameter::toArray () const
{
  CArray d;
  uint16_t len = 6 + name.length();
  d.resize (2 + len);
  d[0] = (len >> 8) & 0xff;
  d[1] = (len) & 0xff;
  d[2] = (L_INT_PAR >> 8) & 0xff;
  d[3] = (L_INT_PAR) & 0xff;
  d[4] = (addr >> 8) & 0xff;
  d[5] = (addr) & 0xff;
  d[6] = (type) & 0xff;
  d.setpart (name, 7);
  return d;
}

std::string
STR_IntParameter::decode () const
{
  char buf[200];
  sprintf (buf, "IntParameter: addr=%04x id=%s type=%s %d bytes\n", addr,
           name.c_str(), type < 0 ? "signed" : "unsigned", 1 << (abs (type) - 1));
  return buf;
}

bool
STR_FloatParameter::init (const CArray & c)
{
  const uint8_t *d;
  if (c.size() < 7)
    return false;
  addr = c[4] << 8 | c[5];
  if (c[c.size() - 1])
    return false;
  d = &c[6];
  while (*d)
    d++;
  if (d != &c[c.size() - 1])
    return false;
  name = (const char *) c.data() + 6;
  return true;
}

CArray
STR_FloatParameter::toArray () const
{
  CArray d;
  uint16_t len = 5 + name.length();
  d.resize (2 + len);
  d[0] = (len >> 8) & 0xff;
  d[1] = (len) & 0xff;
  d[2] = (L_FLOAT_PAR >> 8) & 0xff;
  d[3] = (L_FLOAT_PAR) & 0xff;
  d[4] = (addr >> 8) & 0xff;
  d[5] = (addr) & 0xff;
  d.setpart (name, 6);
  return d;
}

std::string
STR_FloatParameter::decode () const
{
  char buf[200];
  sprintf (buf, "FloatParameter: addr=%04x id=%s\n", addr, name.c_str());
  return buf;
}

bool
STR_ListParameter::init (const CArray & c)
{
  uint16_t el, i;
  const uint8_t *d, *d1;
  if (c.size() < 9)
    return false;
  addr = c[4] << 8 | c[5];
  el = c[6] << 8 | c[7];
  if (c[c.size() - 1])
    return false;
  d = &c[8];
  d1 = d;
  while (*d)
    d++;
  if (d > &c[c.size() - 1])
    return false;
  name = (const char *) d1;
  d1 = ++d;
  if (d > &c[c.size() - 1])
    return false;
  elements.resize (el);
  for (i = 0; i < el; i++)
    {
      while (*d)
        d++;
      if (d > &c[c.size() - 1])
        return false;
      elements[i] = (const char *) d1;
      d1 = ++d;
      if (d > &c[c.size()])
        return false;
    }
  if (d != &c[c.size()])
    return false;
  return true;
}

CArray
STR_ListParameter::toArray () const
{
  CArray d;
  uint16_t p;
  uint16_t len = 7 + name.length();
  C_ITER(i, elements)
  len += i->length() + 1;
  d.resize (2 + len);
  d[0] = (len >> 8) & 0xff;
  d[1] = (len) & 0xff;
  d[2] = (L_LIST_PAR >> 8) & 0xff;
  d[3] = (L_LIST_PAR) & 0xff;
  d[4] = (addr >> 8) & 0xff;
  d[5] = (addr) & 0xff;
  d[6] = (elements.size() >> 8) & 0xff;
  d[7] = (elements.size()) & 0xff;
  d.setpart (name, 8);
  p = 8 + name.length() + 1;
  C_ITER(i, elements)
  {
    d.setpart (*i, p);
    p += i->length() + 1;
  }
  return d;
}

std::string
STR_ListParameter::decode () const
{
  char buf[200];
  std::string s;
  sprintf (buf, "ListParameter: addr=%04x id=%s elements=", addr, name.c_str());
  s = buf;
  for (unsigned int i = 0; i < elements.size(); i++)
    {
      sprintf (buf, "%s,", elements[i].c_str());
      s += buf;
    }
  s += "\n";
  return s;
}

bool
STR_GroupObject::init (const CArray & c)
{
  const uint8_t *d;
  if (c.size() < 6)
    return false;
  no = c[4];
  if (c[c.size() - 1])
    return false;
  d = &c[5];
  while (*d)
    d++;
  if (d != &c[c.size() - 1])
    return false;
  name = (const char *) c.data() + 5;
  return true;
}

std::string
STR_GroupObject::decode () const
{
  char buf[200];
  sprintf (buf, "GROUP_OBJECT %d: id=%s\n", no, name.c_str());
  return buf;
}

CArray
STR_GroupObject::toArray () const
{
  CArray d;
  uint16_t len = 4 + name.length();
  d.resize (2 + len);
  d[0] = (len >> 8) & 0xff;
  d[1] = (len) & 0xff;
  d[2] = (L_GROUP_OBJECT >> 8) & 0xff;
  d[3] = (L_GROUP_OBJECT) & 0xff;
  d[4] = (no) & 0xff;
  d.setpart (name, 5);
  return d;
}

bool
STR_BCU1Size::init (const CArray & c)
{
  if (c.size() != 12)
    return false;
  textsize = c[4] << 8 | c[5];
  stacksize = c[6] << 8 | c[7];
  datasize = c[8] << 8 | c[9];
  bsssize = c[10] << 8 | c[11];
  return true;
}

CArray
STR_BCU1Size::toArray () const
{
  CArray d;
  uint16_t len = 10;
  d.resize (2 + len);
  d[0] = (len >> 8) & 0xff;
  d[1] = (len) & 0xff;
  d[2] = (L_BCU1_SIZE >> 8) & 0xff;
  d[3] = (L_BCU1_SIZE) & 0xff;
  d[4] = (textsize >> 8) & 0xff;
  d[5] = (textsize) & 0xff;
  d[6] = (stacksize >> 8) & 0xff;
  d[7] = (stacksize) & 0xff;
  d[8] = (datasize >> 8) & 0xff;
  d[9] = (datasize) & 0xff;
  d[10] = (bsssize >> 8) & 0xff;
  d[11] = (bsssize) & 0xff;
  return d;
}

std::string
STR_BCU1Size::decode () const
{
  char buf[200];
  sprintf (buf, "BCU1_SIZE: text:%d stack:%d data:%d bss:%d\n", textsize,
           stacksize, datasize, bsssize);
  return buf;
}

bool
STR_BCU2Size::init (const CArray & c)
{
  if (c.size() != 16)
    return false;
  textsize = c[4] << 8 | c[5];
  stacksize = c[6] << 8 | c[7];
  lo_datasize = c[8] << 8 | c[9];
  lo_bsssize = c[10] << 8 | c[11];
  hi_datasize = c[12] << 8 | c[13];
  hi_bsssize = c[14] << 8 | c[15];
  return true;
}

CArray
STR_BCU2Size::toArray () const
{
  CArray d;
  uint16_t len = 14;
  d.resize (2 + len);
  d[0] = (len >> 8) & 0xff;
  d[1] = (len) & 0xff;
  d[2] = (L_BCU2_SIZE >> 8) & 0xff;
  d[3] = (L_BCU2_SIZE) & 0xff;
  d[4] = (textsize >> 8) & 0xff;
  d[5] = (textsize) & 0xff;
  d[6] = (stacksize >> 8) & 0xff;
  d[7] = (stacksize) & 0xff;
  d[8] = (lo_datasize >> 8) & 0xff;
  d[9] = (lo_datasize) & 0xff;
  d[10] = (lo_bsssize >> 8) & 0xff;
  d[11] = (lo_bsssize) & 0xff;
  d[12] = (hi_datasize >> 8) & 0xff;
  d[13] = (hi_datasize) & 0xff;
  d[14] = (hi_bsssize >> 8) & 0xff;
  d[15] = (hi_bsssize) & 0xff;
  return d;
}

std::string
STR_BCU2Size::decode () const
{
  char buf[200];
  sprintf (buf,
           "BCU2_SIZE: text:%d stack:%d lo_data:%d lo_bss:%d hi_data:%d hi_bss:%d\n",
           textsize, stacksize, lo_datasize, lo_bsssize, hi_datasize,
           hi_bsssize);
  return buf;
}

bool
STR_BCU2Start::init (const CArray & c)
{
  if (c.size() != 47)
    return false;
  addrtab_start = c[4] << 8 | c[5];
  addrtab_size = c[6] << 8 | c[7];
  assoctab_start = c[8] << 8 | c[9];
  assoctab_size = c[10] << 8 | c[11];
  readonly_start = c[12] << 8 | c[13];
  readonly_end = c[14] << 8 | c[15];
  param_start = c[16] << 8 | c[17];
  param_end = c[18] << 8 | c[19];
  obj_ptr = c[20] << 8 | c[21];
  obj_count = c[22] << 8 | c[23];
  appcallback = c[24] << 8 | c[25];
  groupobj_ptr = c[26] << 8 | c[27];
  seg0 = c[28] << 8 | c[29];
  seg1 = c[30] << 8 | c[31];
  sphandler = c[32] << 8 | c[33];
  initaddr = c[34] << 8 | c[35];
  runaddr = c[36] << 8 | c[37];
  saveaddr = c[38] << 8 | c[39];
  eeprom_start = c[40] << 8 | c[41];
  eeprom_end = c[42] << 8 | c[43];
  poll_addr = c[44] << 8 | c[45];
  poll_slot = c[46];
  return true;
}

CArray
STR_BCU2Start::toArray () const
{
  CArray d;
  uint16_t len = 45;
  d.resize (2 + len);
  d[0] = (len >> 8) & 0xff;
  d[1] = (len) & 0xff;
  d[2] = (L_BCU2_INIT >> 8) & 0xff;
  d[3] = (L_BCU2_INIT) & 0xff;
  d[4] = (addrtab_start >> 8) & 0xff;
  d[5] = (addrtab_start) & 0xff;
  d[6] = (addrtab_size >> 8) & 0xff;
  d[7] = (addrtab_size) & 0xff;
  d[8] = (assoctab_start >> 8) & 0xff;
  d[9] = (assoctab_start) & 0xff;
  d[10] = (assoctab_size >> 8) & 0xff;
  d[11] = (assoctab_size) & 0xff;
  d[12] = (readonly_start >> 8) & 0xff;
  d[13] = (readonly_start) & 0xff;
  d[14] = (readonly_end >> 8) & 0xff;
  d[15] = (readonly_end) & 0xff;
  d[16] = (param_start >> 8) & 0xff;
  d[17] = (param_start) & 0xff;
  d[18] = (param_end >> 8) & 0xff;
  d[19] = (param_end) & 0xff;
  d[20] = (obj_ptr >> 8) & 0xff;
  d[21] = (obj_ptr) & 0xff;
  d[22] = (obj_count >> 8) & 0xff;
  d[23] = (obj_count) & 0xff;
  d[24] = (appcallback >> 8) & 0xff;
  d[25] = (appcallback) & 0xff;
  d[26] = (groupobj_ptr >> 8) & 0xff;
  d[27] = (groupobj_ptr) & 0xff;
  d[28] = (seg0 >> 8) & 0xff;
  d[29] = (seg0) & 0xff;
  d[30] = (seg1 >> 8) & 0xff;
  d[31] = (seg1) & 0xff;
  d[32] = (sphandler >> 8) & 0xff;
  d[33] = (sphandler) & 0xff;
  d[34] = (initaddr >> 8) & 0xff;
  d[35] = (initaddr) & 0xff;
  d[36] = (runaddr >> 8) & 0xff;
  d[37] = (runaddr) & 0xff;
  d[38] = (saveaddr >> 8) & 0xff;
  d[39] = (saveaddr) & 0xff;
  d[40] = (eeprom_start >> 8) & 0xff;
  d[41] = (eeprom_start) & 0xff;
  d[42] = (eeprom_end >> 8) & 0xff;
  d[43] = (eeprom_end) & 0xff;
  d[44] = (poll_addr >> 8) & 0xff;
  d[45] = (poll_addr) & 0xff;
  d[46] = (poll_slot) & 0xff;
  return d;
}

std::string
STR_BCU2Start::decode () const
{
  char buf[600];
  sprintf (buf,
           "BCU2_INIT: init:%04X run:%04X save:%04X addr_tab:%04X(%d) assoc_tab:%04X(%d) text:%04X-%04X\n"
           " param:%04X-%04X obj:%04X(%d) appcallback:%04X\n"
           " groupobj:%04X seg0:%04X seg1:%04X sphandler:%04X eeprom:%04X-%04X poll_addr:%04X poll_slot:%d\n",
           initaddr, runaddr, saveaddr, addrtab_start, addrtab_size,
           assoctab_start, assoctab_size, readonly_start, readonly_end,
           param_start, param_end, obj_ptr, obj_count, appcallback,
           groupobj_ptr, seg0, seg1, sphandler, eeprom_start, eeprom_end,
           poll_addr, poll_slot);
  return buf;
}

bool
STR_BCU2Key::init (const CArray & c)
{
  unsigned i;
  if (c.size() % 4)
    return false;
  if (c.size() < 8)
    return false;
  installkey = (c[4] << 24) | (c[5] << 16) | (c[6] << 8) | (c[7]);
  for (i = 8; i < c.size(); i += 4)
    keys.push_back ((c[i] << 24) | (c[i + 1] << 16) | (c[i + 2] << 8) | (c[i + 3]));
  return true;
}

CArray
STR_BCU2Key::toArray () const
{
  CArray d;
  unsigned int i;
  uint16_t len = keys.size() * 4 + 4 + 2;
  d.resize (2 + len);
  d[0] = (len >> 8) & 0xff;
  d[1] = (len) & 0xff;
  d[2] = (L_BCU2_KEY >> 8) & 0xff;
  d[3] = (L_BCU2_KEY) & 0xff;
  d[4] = (installkey >> 24) & 0xff;
  d[5] = (installkey >> 16) & 0xff;
  d[6] = (installkey >> 8) & 0xff;
  d[7] = (installkey >> 0) & 0xff;
  for (i = 0; i < keys.size(); i++)
    {
      d[8 + 4 * i] = (keys[i] >> 24) & 0xff;
      d[9 + 4 * i] = (keys[i] >> 16) & 0xff;
      d[10 + 4 * i] = (keys[i] >> 8) & 0xff;
      d[11 + 4 * i] = (keys[i] >> 0) & 0xff;
    }
  return d;
}


std::string
STR_BCU2Key::decode () const
{
  char buf[200];
  std::string s;
  unsigned int i;
  sprintf (buf, "BCU2_KEY: install:%08X ", installkey);
  s = buf;
  for (i = 0; i < keys.size(); i++)
    {
      sprintf (buf, "level%d: %08X ", i, keys[i]);
      s += buf;
    }
  return s;
}

Image::~Image ()
{
  for (unsigned int i = 0; i < str.size(); i++)
    if (str[i])
      delete str[i];
}

std::string
Image::decode () const
{
  std::string s = "BCU Memory Image\n";
  for (unsigned int i = 0; i < str.size(); i++)
    s += str[i]->decode ();
  return s;
}

CArray
Image::toArray () const
{
  CArray data;
  data.resize (10);
  data[0] = 0xbc;
  data[1] = 0x68;
  data[2] = 0x0c;
  data[3] = 0x05;
  data[4] = 0xbc;
  data[5] = 0x68;
  data[6] = 0x0c;
  data[7] = 0x05;
  data[8] = (data.size() >> 8) & 0xff;
  data[9] = (data.size()) & 0xff;
  C_ITER(i, str)
  data.setpart ((*i)->toArray (), data.size());
  return data;
}

int
Image::findStreamNumber (const STR_Type t) const
{
  C_ITER(i, str)
  if ((*i)->getType () == t)
    return i-str.begin();
  return -1;
}

STR_Stream *
Image::findStream (const STR_Type t) const
{
  int i = findStreamNumber (t);
  if (i == -1)
    return 0;
  else
    return str[i];
}

Image *
Image::fromArray (CArray c)
{
  CArray::size_type pos = 10;
  CArray::size_type len;
  if (c.size() < 10)
    return 0;
  if (c[0] != 0xbc)
    return 0;
  if (c[1] != 0x68)
    return 0;
  if (c[2] != 0x0c)
    return 0;
  if (c[3] != 0x05)
    return 0;
  if (c[4] != 0xbc)
    return 0;
  if (c[5] != 0x68)
    return 0;
  if (c[6] != 0x0c)
    return 0;
  if (c[7] != 0x05)
    return 0;
  if (c[8] != ((c.size() >> 8) & 0xff))
    return 0;
  if (c[9] != ((c.size()) & 0xff))
    return 0;
  Image *i = new Image;
  while (pos < c.size())
    {
      if (pos + 4U >= c.size())
        {
          delete i;
          return 0;
        }
      len = c[pos] << 8 | c[pos + 1];
      if (pos + 2U + len > c.size() || len < 2U)
        {
          delete i;
          return 0;
        }

      len += 2;
      i->str.push_back (STR_Stream::fromArray (CArray (c.data(),pos, len)));
      pos += len;
    }
  return i;
}

bool
Image::isValid () const
{
  return findStreamNumber (S_Invalid) == -1;
}
