#include "xhex.h"
#include <iostream>
#include <fstream>
#include <algorithm>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string/case_conv.hpp>
#include "stdplatform.h"

static uint8_t charhex(char c) {
    unsigned char c2 = c;
    if ((c2>='0')&&(c2<='9')) return (c2-'0');
    if ((c2>='A')&&(c2<='F')) return (c2-'A'+10);
    if ((c2>='a')&&(c2<='f')) return (c2-'a'+10);
    return 0xff;
}

int checkIntelHex(const std::string s, std::vector<uint8_t> & memory, uint32_t baseAddress) {
    int byteSize=0;
    int state = 0;
    int line = 1;
    int pos_bs = 0;
    int act_bs = 0;
    uint8_t byte_val = 0;
    uint8_t rec_count = 0;
    uint16_t rec_address = 0;
    uint8_t rec_type = 0;
    uint8_t checksum = 0;
    uint32_t address_base = 0;

    std::cout << "checkIntelHex()" << std::endl;
    
    std::string::const_iterator it(s.begin()), end(s.end());
    while (it!=end) {
        char c = *it;
        uint8_t nibble = charhex(c);
        
        if (state==0) {
            if (c==':') {
                state++;
                pos_bs = 0;
                checksum = 0;
            } else if (c=='\n') {
                line++;
            } else if (c=='\r') {
                state=0;
            } else {
                throw std::runtime_error("Syntax error in Intel hex data. Colon expected at begin of line!");
            }
        } else if (state==1) {
            state++;
            if (nibble > 0xf) throw std::runtime_error("Syntax error in Intel hex data. Hexadecimal digit expected in line!");
            byte_val = nibble<<4;
            
        } else if (state==2) {
            state--;
            if (nibble > 0xf) throw std::runtime_error("Syntax error in Intel hex data. Hexadecimal digit expected in line!");
            byte_val |= nibble;
            pos_bs++;
            
            if (pos_bs==1) {
                rec_count = byte_val;
            } else if (pos_bs==2) {
                rec_address = byte_val;
                rec_address <<= 8;
            
            } else if (pos_bs==3) {
                rec_address |= byte_val;
            
            } else if (pos_bs==4) {
                rec_type = byte_val;
                if (rec_type==0) {
                    int size = address_base+rec_address+rec_count;
                    if (memory.size()<size) memory.resize(size);
                } else if (rec_type==2) {
                    if (rec_count!=2) throw std::runtime_error("Syntax error in Intel hex data. Wrong count (02 record)!");
                } else if (rec_type==4) {
                    if (rec_count!=2) throw std::runtime_error("Syntax error in Intel hex data. Wrong count (04 record)!");
                }
            
            } else if (pos_bs==rec_count+5) {
                if (checksum!=0) throw std::runtime_error("Syntax error in Intel hex data. Wrong checksum!");
                if (rec_type==0) {
                    byteSize += rec_count;
                }
                /*
                std::cout << "* XHEX line:" << line
                          << " count=" << (int)rec_count
                          << " address=" << (int)rec_address
                          << " type=" << (int)rec_type << std::endl;
                */
                state = 0;
                
            } else if (pos_bs>rec_count+5) {
                std::runtime_error("Syntax error in Intel hex data. Too much data entries in line!");
                
            } else {
                // data
                if (rec_type==0) {
                    memory[address_base+rec_address+pos_bs-5] = byte_val;
                } else if (rec_type==2) {
                    if (pos_bs==5) {
                        address_base = byte_val;
                        address_base <<= 8;
                    } else if (pos_bs==6) {
                        address_base |= byte_val;
                        address_base <<= 4;
                        std::cout << "* new address_base=" << address_base << std::endl;
                    }
                } else if (rec_type==4) {
                    if (pos_bs==5) {
                        address_base = byte_val;
                        address_base <<= 8;
                    } else if (pos_bs==6) {
                        address_base |= byte_val;
                        address_base <<= 16;
                        std::cout << "* new address_base=" << address_base << std::endl;
                    }
                }

            }

        }
        it++;
    }
    std::cout << "* XHEX byteSize=" << byteSize
              << " memory.size()=" <<  memory.size() << std::endl;
    
    return byteSize;
}

int checkSRec(const std::string s, std::vector<uint8_t> & memory, uint32_t baseAddress) {
    int byteSize=0;
    int state = 0;
    int line = 1;
    std::string::const_iterator it(s.begin()), end(s.end());
    while (it!=end) {
        char c = *it;
        if (state==0) {
            if (c=='S') {
                state=1;
            } else if (c=='\n') {
                state=0;
                line++;
            } else if (c=='\r') {
                state=0;
            } else {
                throw std::runtime_error("Syntax error in Motorola S-record data. 'S' expected at begin of line!");
            }
        } else if (state==1) {
            if (((c>='0')&&(c<='9')) || ((c>='a')&&(c<='f')) || ((c>='A')&&(c<='F'))) {
                state=1;
            } else if (c=='\n') {
                state=0;
                line++;
            } else if (c=='\r') {
                state=0;
            } else {
                throw std::runtime_error("Syntax error in Motorola S-record data. Hexadecimal digit expected in line!");
            }
        }
        it++;
    }
    return byteSize;
}

using namespace nicai;


// class xhex

xhex * xhex::create(const std::string & filename, const std::string & defPlatform) {
    if (filename.substr(filename.size()-5, 5) == ".srec") {
        return createFromSRec(filename, defPlatform);
    }
    if (filename.substr(filename.size()-4, 4) == ".hex") {
        return createFromHex(filename, defPlatform);
    }
    if (filename.substr(filename.size()-5, 5) == ".xhex") {
        return createFromXHex(filename);
    }
    if (filename.substr(filename.size()-5, 5) == ".bob3") {
        return createFromXHex(filename);
    }
    return 0;
}

xhex * xhex::createFromXHex(const std::string & filename) {
    std::ifstream in(filename.c_str());
    if (!in) {
        throw std::runtime_error("unable to open file: "+filename);
    }
    return createFromStream(in);
}

xhex * xhex::createFromStream(std::istream & in) {
    std::cout << "createFromStream()" << std::endl;
    nicai::xml::ixmlstream xin(in);
    xhex * input_xhex = 0;
    xin.ignoreComments(true);
    xin.collapseWhitespace(true);

    if (xin.expectNode(nicai::xml::document())) {
        if (xin.expectNode(nicai::xml::tag("xhex"))) {
            input_xhex = new xhex(xin);
            xin.expectNode(nicai::xml::endtag());
        }
        xin.expectNode(nicai::xml::enddocument());
    } else {
        throw std::runtime_error("no xml document detected");
    }
    return input_xhex;
}

xhex * xhex::createFromHex(const std::string & filename, const std::string & defPlatform) {
    std::ifstream in(filename.c_str());
    if (!in) {
        throw std::runtime_error("unable to open file: "+filename);
    }


    std::string dummy ("<?xml version=\"1.0\"?>"
                        "<xhex version=\"1.0\">"
                        "<platform>Custom Intel-HEX</platform>"
                        //"<programmer type=\"usbasp\"/>"
                        "<device part=\"_\" erase=\"yes\">"
                        "<segment id=\"flash\" format=\"ihex\">"
                        "</segment>"
                        "</device>"
                        "</xhex>"
                        );
    std::istringstream sin(getStdPlatformXML(defPlatform));

    xhex * result = createFromStream(sin);

    // insert hex file into dummy xhex
    segment & seg = result->devicePtr->segmentVec[0];
    std::ostringstream s;
    s << in.rdbuf();
    seg.content = s.str();
    seg.byteSize = checkIntelHex(seg.content, seg.memory, 0);
    return result;
}

xhex * xhex::createFromSRec(const std::string & filename, const std::string & defPlatform) {
    std::ifstream in(filename.c_str());
    if (!in) {
        throw std::runtime_error("unable to open file: "+filename);
    }

    std::string dummy ("<?xml version=\"1.0\"?>"
                        "<xhex version=\"1.0\">"
                        "<platform>Custom S-record</platform>"
                        //"<programmer type=\"usbasp\"/>"
                        "<device part=\"_\" erase=\"yes\">"
                        "<segment id=\"flash\" format=\"srec\">"
                        "</segment>"
                        "</device>"
                        "</xhex>"
                        );
    std::istringstream sin(getStdPlatformXML(defPlatform));

    xhex * result = createFromStream(sin);

    // insert hex file into dummy xhex
    segment & seg = result->devicePtr->segmentVec[0];
    std::ostringstream s;
    s << in.rdbuf();
    seg.content = s.str();
    checkSRec(seg.content, seg.memory, 0);
    return result;
}


xhex::xhex(xml::ixmlstream & xin) {
    xml::attributemap attributes;
    xin.readAttributes(attributes);
    if (attributes["version"]!="1.0") {
        throw std::runtime_error("wrong file version");
    }

    while(!xin.expectNode(xml::endtag())) {
        if (xin.expectNode(xml::tag("platform"))) {
            platformPtr.reset(new platform(xin));
        } else if (xin.expectNode(xml::tag("programmer"))) {
            programmerPtr.reset(new programmer(xin));
        } else if (xin.expectNode(xml::tag("info"))) {
            infoVec.push_back(new info(xin));
        } else if (xin.expectNode(xml::tag("device"))) {
            devicePtr.reset(new device(xin));
        } else {
            xin.ignoreContent();
        }
    }
}



info * xhex::getInfo(const std::string & id) {
    for (unsigned int i=0; i<infoVec.size(); i++) {
        if (infoVec[i].id==id) {
            return &(infoVec[i]);
        }
    }
    return 0;
}

// class info

info::info(xml::ixmlstream & xin) {
    xml::attributemap attributes;
    xin.readAttributes(attributes);
    id = attributes["id"];
    xin.readText(content);
    xin.expectNode(xml::endtag());
}


// class platform

platform::platform(xml::ixmlstream & xin) {
    xml::attributemap attributes;
    xin.readAttributes(attributes);
    xin.readText(content);
    xin.expectNode(xml::endtag());
}


// class programmer

programmer::programmer(xml::ixmlstream & xin) {
    xml::attributemap attributes;
    xin.readAttributes(attributes);
    type = attributes["type"];
    baudrate = attributes["baudrate"];
    bitclock = attributes["bitclock"];
    xin.expectNode(xml::endtag());
}


// class device

device::device(xml::ixmlstream & xin) {
    xml::attributemap attributes;
    xin.readAttributes(attributes);
    part = attributes["part"];
    erase = attributes["erase"]=="yes";

    while(!xin.expectNode(xml::endtag())) {
        if (xin.expectNode(xml::tag("segment"))) {
            segmentVec.push_back(new segment(xin));
        } else if (xin.expectNode(xml::tag("checksum"))) {
            //checksumVec.push_back(new checksum(xin));
        } else {
            xin.ignoreContent();
        }
    }
}

segment * device::getSegment(const std::string & id) {
    for (unsigned int i=0; i<segmentVec.size(); i++) {
        if (segmentVec[i].id==id) {
            return &(segmentVec[i]);
        }
    }
    return 0;
}


// class segment

segment::segment(xml::ixmlstream & xin) {
    std::cout << "segment::segment()" << std::endl;
    byteSize = 0;
    xml::attributemap attributes;
    xin.readAttributes(attributes);
    id = attributes["id"];
    format = attributes["format"];
    std::cout << "segment::segment() pre baseAddress" << std::endl;
    baseAddress = attributes.get("baseAddress", (unsigned int)0);

    std::cout << "segment::segment() attributes loaded" << std::endl;

    //std::cout << "Segment detected: " << id << std::endl;

    if (format=="") {
        while(!xin.expectNode(xml::endtag())) {
            if (xin.expectNode(xml::tag("data"))) {
                dataVec.push_back(new data(xin));
            } else if (xin.expectNode(xml::tag("checksum"))) {
                //checksumVec.push_back(new checksum(xin));
            } else {
                xin.ignoreContent();
            }
        }
    } else {
        xin.readText(content);
        std::replace(content.begin(), content.end(), ' ', '\n');
        content+='\n';
        if (format=="ihex") {
            byteSize=checkIntelHex(content, memory, baseAddress);
        } else if (format=="srec") {
            byteSize=checkSRec(content, memory, baseAddress);
        }
        xin.expectNode(xml::endtag());
    }
}


// class data

data::data(xml::ixmlstream & xin) {
    xml::attributemap attributes;
    xin.readAttributes(attributes);
    address = attributes["address"];
    xin.readText(content);
    xin.expectNode(xml::endtag());
}





