#include <iostream>
#include <fstream>

#include <boost/lexical_cast.hpp>
#include <boost/thread.hpp>

#include "xhexprog.h"
#include "programmer.h"
#include "xhex.h"


typedef std::map<uint32_t, std::basic_string<uint8_t> > chunkMap_t;

uint32_t hex_to_uint32 (const std::string & val) {
  if (val.substr(0,2)!="0x") {
    return boost::lexical_cast<int>(val);
  }
  uint32_t res=0;
  for (uint8_t pos=2; pos<val.size(); ++pos) {
    char c = val[pos] | 0x20;
    res <<= 4;
    res |= (c>'9')?(c+10-'a'):(c-'0');
  }
  return res;
}

int appendChunkData(std::basic_string<uint8_t> & data, uint32_t offset, uint32_t segmentsize, uint8_t fill, std::istream & is) {
  int count=0;
  data.resize(segmentsize, fill);
  while((offset<segmentsize) && is.peek()!=EOF) {
    uint8_t hi = is.get()| 0x20;
    uint8_t lo = is.get()| 0x20;
    if (!is) {
      throw std::runtime_error("error: invalid hexdata");
    }
    hi = (hi>'9')?(hi+10-'a'):(hi-'0');
    lo = (lo>'9')?(lo+10-'a'):(lo-'0');
    data[offset] = (hi<<4)|(lo);
    count++;
    offset++;
  }
  return count;
}


chunkMap_t buildChunkMap(segment & s) {
  chunkMap_t chunkMap;
  uint32_t address = s.start.empty()?0:hex_to_uint32(s.start.c_str());
  uint32_t segmentsize = 1;
  if (s.id=="flash") segmentsize = 128;
  if (s.id=="eeprom") segmentsize = 8;

  for(unsigned int i=0; i<s.dataVec.size(); ++i) {
    data & d (s.dataVec[i]);
    //std::cout << d.address << std::endl;
    address = d.address.empty()?address:hex_to_uint32(d.address.c_str());
    std::cout << "data at " << std::hex <<  address << std::endl;
    uint32_t chunkAddress = (address/segmentsize)*segmentsize;
    uint32_t offset = address-chunkAddress;
    std::istringstream is(d.content);
    while (is && (is.peek()!=EOF)) {
      std::cout << "chunk at " << std::hex << chunkAddress << std::endl;
      int count = appendChunkData(chunkMap[chunkAddress], offset, segmentsize, 0xff, is);
      chunkAddress += segmentsize;
      offset = 0;
    }
  }
  return chunkMap;
}

void xhexprog::callStatusChange(const std::string & status) {
  if (onStatusChange) {
    onStatusChange(progName, portName, partName, status);
  }
}

void xhexprog::callInfo(const std::string & info){
  if (onInfo) {
    onInfo(info);
  }
}

void xhexprog::callProgress(int pos, int max){
  if (onProgress) {
    onProgress(pos, max);
  }
}



xhexprog::xhexprog() {
  progName="ISP";
}


xhexprog::~xhexprog() {
  if (prog) {
    prog->leaveProgramMode();
  }
}


void xhexprog::setProgrammer(std::string name) {
  progName=name;
  callStatusChange("");
}


void xhexprog::connect(std::string port) {
  delete prog;
  prog = 0;
  portName = "";
  partName = "";
  if (port=="") {
    callInfo("Disconnected.");
    callStatusChange("Programmer disconnected");
    return;
  }

  callInfo("Connecting " + progName + " to port " + port + "...");
  prog = Programmer::create(progName, port);
  callInfo("Connected.");
  int retries=5;
  while(retries--) {
    try {
      prog->enterProgramMode();
      callInfo("Programming mode entered.");
      portName = port;
      updateChipInfo();
      callStatusChange("Programmer connected");
      return;
    } catch (...) {
      callInfo("Retrying...");
      boost::this_thread::sleep(boost::posix_time::milliseconds(200));
    }
  }
  delete prog;
  prog=0;
  callInfo("Unable to enter programming mode!");
  callStatusChange("Programmer disconnected");
}


void xhexprog::loadFile(std::string filename) {
  delete input_xhex;
  input_xhex = 0;

  callInfo("Loading file \""+filename+"\"...");

  std::ifstream in(filename.c_str());
  nicai::xml::ixmlstream xin(in);
  if (!in) {
    throw std::runtime_error("unable to open file: "+filename);
  }
  xin.ignoreComments(true);
  xin.collapseWhitespace(true);
  std::cout << "loading xhex file " << filename << std::endl;

  if (xin.expectNode(nicai::xml::document())) {
    std::cout << "detected xml document" << std::endl;
    if (xin.expectNode(nicai::xml::tag("xhex"))) {
      callInfo("detected xhex document.");
      std::cout << "detected xhex document" << std::endl;
      input_xhex = new xhex(xin);
      xin.expectNode(nicai::xml::endtag());
    }
    xin.expectNode(nicai::xml::enddocument());
  } else {
    throw std::runtime_error("no xml document detected");
  }
  callInfo("File loaded successfully.");
  callStatusChange("File loaded successfully.");
}


void xhexprog::saveFile(std::string filename) {
}


void xhexprog::eraseChip() {
  if (!prog) {
    throw std::runtime_error("no programmer connected");
  }
  callProgress(50, 100);
  callInfo("Erasing chip...");
  prog->eraseDevice();
  callProgress(100, 100);
  callInfo("Chip was erased.");
  callStatusChange("Chip was erased.");
  callProgress(0, 100);

}


void xhexprog::writeChip() {
  if (!input_xhex) {
    throw std::runtime_error("no xhex file loaded");
  }
  if (!prog) {
    throw std::runtime_error("no programmer connected");
  }

  // count data
  int max_progress=0;
  for (unsigned int i=0; i<input_xhex->segmentVec.size(); ++i) {
    segment & s(input_xhex->segmentVec[i]);
    chunkMap_t cm = buildChunkMap(s);
    max_progress+=cm.size();
  }

  callStatusChange("Programming...");
  callProgress(0, max_progress);
  callInfo("Programming started...");

  int progress=0;
  for (unsigned int i=0; i<input_xhex->segmentVec.size(); ++i) {
    segment & s(input_xhex->segmentVec[i]);

    callInfo("Programming " + s.id + "...");
    chunkMap_t cm = buildChunkMap(s);

    for (chunkMap_t::iterator it = cm.begin(); it!=cm.end(); it++) {
      std::ostringstream os;
      os << "writing Block 0x" << std::hex << it->first;
      //dohexdump(it->second.data(), it->second.size());
      std::cout << "PROG:" << os.str() << std::endl;
      callProgress(progress, max_progress);
      callStatusChange(os.str());
      prog->program(s.id, it->first, it->second.data(), it->second.size());
      progress++;
    }
  }
  callStatusChange("Programming finished.");
  callInfo("Programming finished.");
  callProgress(0, 100);
}


void xhexprog::readChip() {
  if (!prog) {
    throw std::runtime_error("no programmer connected");
  }

}


void xhexprog::verifyChip() {
  if (!input_xhex) {
    throw std::runtime_error("no xhex file loaded");
  }
  if (!prog) {
    throw std::runtime_error("no programmer connected");
  }

  int mismatches=0;

  // count data
  int max_progress=0;
  for (unsigned int i=0; i<input_xhex->segmentVec.size(); ++i) {
    segment & s(input_xhex->segmentVec[i]);
    chunkMap_t cm = buildChunkMap(s);
    max_progress+=cm.size();
  }

  callStatusChange("Verifying...");
  callProgress(0, max_progress);
  callInfo("Verification started...");

  int progress=0;
  for (unsigned int i=0; i<input_xhex->segmentVec.size(); ++i) {
    segment & s(input_xhex->segmentVec[i]);

    callInfo("Verifying " + s.id + "...");
    chunkMap_t cm = buildChunkMap(s);

    for (chunkMap_t::iterator it = cm.begin(); it!=cm.end(); it++) {
      std::ostringstream os;
      os << "comparing Block 0x" << std::hex << it->first;
      //dohexdump(it->second.data(), it->second.size());
      //std::cout << "PROG:" << std::endl;
      callProgress(progress, max_progress);
      callStatusChange(os.str());
      int errors = prog->verify(s.id, it->first, it->second.data(), it->second.size());
      if (errors) {
        std::ostringstream os;
        os << "Mismatch in Block 0x" << std::hex << it->first << ": " << std::dec << errors << ((errors==1)?" byte.":" bytes.");
        callInfo(os.str());
        mismatches+=errors;
      }
      progress++;
    }
  }
  if (mismatches==0) {
    callStatusChange("Verification succesful.");
    callInfo("Device content does match xhexfile content.");
    callInfo("Verification succesful.");
  } else {
    std::ostringstream os;
    callStatusChange("Verification unsuccessful!");
    os << mismatches << ((mismatches==1)?" mismatching byte found.":" mismatching bytes found!");
    callInfo(os.str());
    callInfo("Verification unsuccessful!");
  }
  callProgress(0, 100);
}


void xhexprog::updateChipInfo() {
  if (!prog) {
    throw std::runtime_error("no programmer connected");
  }
  std::string signature = prog->getDeviceId();
  if (signature=="0x1e9702") {
    partName = "ATmega128";
  } else {
    partName = "id="+signature;
  }


}

void xhexprog::setParameter(std::string parameter, std::string value) {
  if (!prog) {
    throw std::runtime_error("no programmer connected");
  }
  prog->setParameter(parameter, value);
}

std::string xhexprog::getParameter(std::string parameter) {
  if (!prog) {
    throw std::runtime_error("no programmer connected");
  }
  return prog->getParameter(parameter);
}

bool xhexprog::isConnected() {
  return prog!=0;
}

bool xhexprog::hasXHex(){
  return input_xhex!=0;
}

