/***************************************************************************
 * Copyright (C) 2006 Nicolas Hadacek <hadacek@kde.org>                    *
 *                                                                         *
 *   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.                                   *
 ***************************************************************************/
#include "pickit.h"

#include "devices/base/device_group.h"
#include "progs/base/prog_group.h"

//-----------------------------------------------------------------------------
Pickit::Array::Array(uint length, uchar fillChar, PrintMode mode)
  : _fillChar(fillChar), _mode(mode), _data(length)
{
  _data.fill(fillChar);
}

TQString Pickit::Array::pretty() const
{
  int end = _data.count() - 1;
  for (; end>=0; end--)
    if ( _data[end]!=_fillChar ) break;
  TQString s;
  for (int i=0; i<=end; i++) s += toPrintable(_data[i], _mode);
  return s;
}

//-----------------------------------------------------------------------------
Pickit::USBPort::USBPort(uint deviceId, Log::Base &log)
  : Port::USB(log, Microchip::VENDOR_ID, deviceId, CONFIG_VENDOR, 0)
{}

bool Pickit::USBPort::command(uchar c)
{
  Array a = array();
  a._data[0] = c;
  return command(a);
}

bool Pickit::USBPort::command(const char *s)
{
  Array a = array();
  if (s) {
    Q_ASSERT( strlen(s)<=a.length() );
    for (uint i=0; i<strlen(s); i++) a._data[i] = s[i];
  }
  return command(a);
}

bool Pickit::USBPort::command(const Array &cmd)
{
  log(Log::DebugLevel::Extra, TQString("send command: \"%1\"").arg(cmd.pretty()));
  return write(writeEndPoint(), (const char *)cmd._data.data(), cmd.length());
}

bool Pickit::USBPort::receive(Pickit::Array &array)
{
  if ( !read(readEndPoint(), (char *)array._data.data(), array.length()) ) return false;
  log(Log::DebugLevel::Max, TQString("received: \"%1\"").arg(array.pretty()));
  return true;
}

bool Pickit::USBPort::getMode(VersionData &version, ::Programmer::Mode &mode)
{
  if ( !command('v') ) return false;
  Array a = array();
  if ( !receive(a) ) return false;
  if ( a[5]=='B' ) {
    version = VersionData(a[6], a[7], 0);
    mode = ::Programmer::BootloadMode;
  } else {
    version = VersionData(a[0], a[1], a[2]);
    mode = ::Programmer::NormalMode;
  }
  return true;
}

bool Pickit::USBPort::receiveWords(uint nbBytesWord, uint nbRead, TQValueVector<uint> &words, uint offset)
{
  log(Log::DebugLevel::Max, TQString("receive words nbBytesWord=%1 nbRead=%2 offset=%3").arg(nbBytesWord).arg(nbRead).arg(offset));
  Array a = array();
  TQMemArray<uchar> data(nbRead*a.length());
  uint l = 0;
  for (uint i=0; i<nbRead; i++) {
    if ( !receive(a) ) return false;
    for (uint k=offset; k<a.length(); k++) {
      data[l] = a[k];
      l++;
    }
  }
  words.resize(data.count()/nbBytesWord);
  for (uint i=0; i<uint(words.count()); i++) {
    words[i] = 0;
    for (uint k=0; k<nbBytesWord; k++) words[i] |= data[nbBytesWord*i + k] << (8*k);
  }
  return true;
}

//-----------------------------------------------------------------------------
Pickit::Hardware::Hardware(::Programmer::Base &base, USBPort *port)
  : ::Programmer::PicHardware(base, port, TQString())
{}

bool Pickit::Hardware::internalConnectHardware()
{
  return port().open();
}

bool Pickit::Hardware::setTargetPower(uint v)
{
  Array cmd = port().array();
  cmd[0] = 'V';
  cmd[1] = v;
  return port().command(cmd);
}

bool Pickit::Hardware::writeWords(uint max, char c, uint nbBytesWord,
                                  uint &i, const Device::Array &data)
{
  Q_ASSERT( i<data.count() );
  Q_ASSERT( nbBytesWord==1 || nbBytesWord==2 );
  Array cmd = port().array();
  uint n = (nbBytesWord==1 ? 2 : 3);
  Q_ASSERT( n*max<=cmd.length() );
  for (uint k=0; k<max; k++) {
    cmd[n*k] = c;
    cmd[n*k+1] = data[i].byte(0);
    if ( nbBytesWord==2 ) cmd[n*k+2] = data[i].byte(1);
    i++;
    if ( i>=data.count() ) break;
  }
  return port().command(cmd);
}

bool Pickit::Hardware::regenerateOsccal(BitValue &newValue)
{
  if ( !setTargetPower(Osc2_5kHz) ) return false;
  if ( !setTargetPower(PowerOn + Osc2_5kHz) ) return false;
  Port::usleep(400000); // 400 ms
  if ( !setTargetPower(PowerOff) ) return false;
  Pickit::Array cmd = port().array();
  cmd[0] = 'P';
  cmd[1] = 'I';
  cmd[2] = 120;
  cmd[3] = 0;
  cmd[4] = 'r';
  cmd[5] = 'p';
  if ( !port().command(cmd) ) return false;
  TQValueVector<uint> words;
  if ( !port().receiveWords(1, 1, words) ) return false;
  newValue = words[7] | 0x3400;
  return true;
}

//----------------------------------------------------------------------------
bool Pickit::DeviceSpecific::setPowerOn()
{
  return hardware().port().command(entryMode());
}

bool Pickit::DeviceSpecific::setPowerOff()
{
  return hardware().port().command('p');
}

bool Pickit::DeviceSpecific::setTargetPowerOn(bool on)
{
  return hardware().setTargetPower(on ? PowerOn : PowerOff);
}

//----------------------------------------------------------------------------
bool Pickit::BMDeviceSpecific::doRead(Pic::MemoryRangeType type, Device::Array &data, const ::Programmer::VerifyData *vdata)
{
  data.resize(device().nbWords(type));
  gotoMemory(type);
  TQValueVector<uint> words;
  switch (type.type()) {
    case Pic::MemoryRangeType::Config:
    case Pic::MemoryRangeType::Code:
    case Pic::MemoryRangeType::Cal:
    case Pic::MemoryRangeType::CalBackup:
    case Pic::MemoryRangeType::UserId:
    case Pic::MemoryRangeType::DeviceId:
      for (uint i=0; i<data.count();) {
        if ( !hardware().port().command('R') ) return false;
        if ( !hardware().port().receiveWords(2, 1, words) ) return false;
        for (uint k=0; k<uint(words.count()); k++) {
          data[i] = words[k];
          if ( vdata && !hardware().verifyWord(i, data[i], type, *vdata) ) return false;
          i++;
          if ( i>=data.count() ) break;
        }
      }
      break;
    case Pic::MemoryRangeType::Eeprom:
      for (uint i=0; i<data.count();) {
        if ( !hardware().port().command('r') ) return false; // #### not sure this is correct for Pickit1: "rrrrrrrr" used by usb_pickit
        if ( !hardware().port().receiveWords(1, 1, words) ) return false;
        for (uint k=0; k<uint(words.count()); k++) {
          data[i] = words[k];
          if ( vdata && !hardware().verifyWord(i, data[i], type, *vdata) ) return false;
          i++;
          if ( i>=data.count() ) break;
        }
      }
      break;
    case Pic::MemoryRangeType::DebugVector:
    case Pic::MemoryRangeType::HardwareStack:
    case Pic::MemoryRangeType::ProgramExecutive:
    case Pic::MemoryRangeType::Nb_Types: Q_ASSERT(false); return false;
  }
  if ( type==Pic::MemoryRangeType::Code || type==Pic::MemoryRangeType::Eeprom )
    _base.progressMonitor().addTaskProgress(data.count());
  return true;
}

bool Pickit::BMDeviceSpecific::incrementPC(uint nb)
{
  Pickit::Array cmd = hardware().port().array();
  cmd[0] = 'I';
  cmd[1] = nb & 0xFF;
  cmd[2] = (nb >> 8) & 0xFF;
  return hardware().port().command(cmd);
}

bool Pickit::BMDeviceSpecific::doEraseRange(Pic::MemoryRangeType type)
{
  Q_ASSERT( type==Pic::MemoryRangeType::Code );
  return hardware().port().command('E');
}

bool Pickit::BMDeviceSpecific::doWrite(Pic::MemoryRangeType type, const Device::Array &data, bool force)
{
  // #### TODO: speed optimize...
  Q_UNUSED(force);
  gotoMemory(type);
  uint nb = nbWrites(type);
  switch (type.type()) {
    case Pic::MemoryRangeType::Config:
    case Pic::MemoryRangeType::Code:
    case Pic::MemoryRangeType::Cal:
    case Pic::MemoryRangeType::UserId:
      for (uint i=0; i<data.count(); )
        hardware().writeWords(nb, writeCode(), 2, i, data);
      break;
    case Pic::MemoryRangeType::Eeprom:
      for (uint i=0; i<data.count(); )
        hardware().writeWords(nb, writeData(), 1, i, data);
      break;
    case Pic::MemoryRangeType::CalBackup:
    case Pic::MemoryRangeType::DeviceId:
    case Pic::MemoryRangeType::DebugVector:
    case Pic::MemoryRangeType::HardwareStack:
    case Pic::MemoryRangeType::ProgramExecutive:
    case Pic::MemoryRangeType::Nb_Types: Q_ASSERT(false); return false;
  }
  if ( type==Pic::MemoryRangeType::Code || type==Pic::MemoryRangeType::Eeprom )
    _base.progressMonitor().addTaskProgress(data.count());
  return true;
}

//----------------------------------------------------------------------------
bool Pickit::Baseline::gotoMemory(Pic::MemoryRangeType type)
{
  switch (type.type()) {
    case Pic::MemoryRangeType::Config:
    case Pic::MemoryRangeType::Eeprom: return true;
    case Pic::MemoryRangeType::Code:
    case Pic::MemoryRangeType::Cal:
    case Pic::MemoryRangeType::UserId:
    case Pic::MemoryRangeType::CalBackup: return incrementPC(1+device().range(type).start.toUInt());
    case Pic::MemoryRangeType::DeviceId:
    case Pic::MemoryRangeType::DebugVector:
    case Pic::MemoryRangeType::HardwareStack:
    case Pic::MemoryRangeType::ProgramExecutive:
    case Pic::MemoryRangeType::Nb_Types: break;
  }
  Q_ASSERT(false);
  return false;
}

//----------------------------------------------------------------------------
bool Pickit::P16F::gotoMemory(Pic::MemoryRangeType type)
{
  Pickit::Array cmd = hardware().port().array();
  cmd[0] = 'C';
  switch (type.type()) {
    case Pic::MemoryRangeType::Code: return true;
    case Pic::MemoryRangeType::Eeprom: return true;
    case Pic::MemoryRangeType::UserId: return hardware().port().command(cmd);
    case Pic::MemoryRangeType::DeviceId:
      cmd[1] = 'I';
      cmd[2] = 0x06;
      cmd[3] = 0x00;
      return hardware().port().command(cmd);
    case Pic::MemoryRangeType::Config:
      cmd[1] = 'I';
      cmd[2] = 0x07;
      cmd[3] = 0x00;
      return hardware().port().command(cmd);
    case Pic::MemoryRangeType::Cal:
      if ( device().range(type).start==device().range(Pic::MemoryRangeType::Code).end+1 )
        return incrementPC(device().range(type).start.toUInt());
      cmd[1] = 'I';
      cmd[2] = 0x08;
      cmd[3] = 0x00;
      return hardware().port().command(cmd);
    case Pic::MemoryRangeType::CalBackup:
    case Pic::MemoryRangeType::DebugVector:
    case Pic::MemoryRangeType::HardwareStack:
    case Pic::MemoryRangeType::ProgramExecutive:
    case Pic::MemoryRangeType::Nb_Types: break;
  }
  Q_ASSERT(false);
  return false;
}

bool Pickit::P16F::doErase(bool)
{
  Pickit::Array cmd = hardware().port().array();
  cmd[0] = 'C';
  cmd[1] = writeCode();
  cmd[2] = 0xFF;
  cmd[3] = 0x3F;
  cmd[4] = 'E';
  cmd[5] = 'e';
  return hardware().port().command(cmd);
}

bool Pickit::P16F::doEraseRange(Pic::MemoryRangeType type)
{
  if ( type==Pic::MemoryRangeType::Eeprom ) return hardware().port().command('e');
  return BMDeviceSpecific::doEraseRange(type);
}