/**********************************************************************************************
    Copyright (C) 2007 Frank Seidel <frank@f-seidel.de>

    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., 59 Temple Place - Suite 330, Boston, MA 02111 USA

  Garmin and MapSource are registered trademarks or trademarks of Garmin Ltd.
  or one of its subsidiaries.

**********************************************************************************************/
#include "CDevice.h"

#include "../Platform.h"

#include <Garmin.h>

#include <cstdio>
#include <iostream>
#include <sstream>

using namespace EtrexH;
using namespace Garmin;
using namespace std;

#define GARMIN_EH_DFLTBITRATE 9600
#define GARMIN_EH_HIBITRATE 57600

#define CMD_TRANSFER_SCREEN 32
#define PID_SCREENDATA 69

#define PROGR_CALLBACK(state,message) \
callback ( state,0,0,0,message )

#define PROGR_CANCEL_CALLBACK(state,message,cancel) \
callback ( state,0,cancel,0,message )

namespace EtrexH
{
    static const char _clrtbl[1024]= {
        -127,-127,-127,0,-60,-60,-60,0,90,90,90,0,0,0,0,0,-117,0,0,0,
        -76,0,0,0,-43,0,0,0,-1,0,0,0,0,48,0,0,32,48,0,0,
        65,48,0,0,106,48,0,0,-117,48,0,0,-76,48,0,0,-43,48,0,0,
        -1,48,0,0,0,101,0,0,32,101,0,0,65,101,0,0,106,101,0,0,
        -117,101,0,0,-76,101,0,0,-43,101,0,0,-1,101,0,0,0,-107,0,0,
        32,-107,0,0,65,-107,0,0,106,-107,0,0,-117,-107,0,0,-76,-107,0,0,
        -43,-107,0,0,-1,-107,0,0,0,-54,0,0,32,-54,0,0,65,-54,0,0,
        106,-54,0,0,-117,-54,0,0,-76,-54,0,0,-43,-54,0,0,-1,-54,0,0,
        0,-1,0,0,32,-1,0,0,65,-1,0,0,106,-1,0,0,-117,-1,0,0,
        -76,-1,0,0,-43,-1,0,0,-1,-1,0,0,0,0,57,0,32,0,57,0,
        65,0,57,0,106,0,57,0,-117,0,57,0,-76,0,57,0,-43,0,57,0,
        -1,0,57,0,0,48,57,0,32,48,57,0,65,48,57,0,106,48,57,0,
        -117,48,57,0,-76,48,57,0,-43,48,57,0,-1,48,57,0,0,101,57,0,
        32,101,57,0,65,101,57,0,106,101,57,0,-117,101,57,0,-76,101,57,0,
        -43,101,57,0,-1,101,57,0,0,-107,57,0,32,-107,57,0,65,-107,57,0,
        106,-107,57,0,-117,-107,57,0,-76,-107,57,0,-43,-107,57,0,-1,-107,57,0,
        0,-54,57,0,32,-54,57,0,65,-54,57,0,106,-54,57,0,-117,-54,57,0,
        -76,-54,57,0,-43,-54,57,0,-1,-54,57,0,0,-1,57,0,32,-1,57,0,
        65,-1,57,0,106,-1,57,0,-117,-1,57,0,-76,-1,57,0,-43,-1,57,0,
        -1,-1,57,0,0,0,123,0,32,0,123,0,65,0,123,0,106,0,123,0,
        -117,0,123,0,-76,0,123,0,-43,0,123,0,-1,0,123,0,0,48,123,0,
        32,48,123,0,65,48,123,0,106,48,123,0,-117,48,123,0,-76,48,123,0,
        -43,48,123,0,-1,48,123,0,0,101,123,0,32,101,123,0,65,101,123,0,
        106,101,123,0,-117,101,123,0,-76,101,123,0,-43,101,123,0,-1,101,123,0,
        0,-107,123,0,32,-107,123,0,65,-107,123,0,106,-107,123,0,-117,-107,123,0,
        -76,-107,123,0,-43,-107,123,0,-1,-107,123,0,0,-54,123,0,32,-54,123,0,
        65,-54,123,0,106,-54,123,0,-117,-54,123,0,-76,-54,123,0,-43,-54,123,0,
        -1,-54,123,0,0,-1,123,0,32,-1,123,0,65,-1,123,0,106,-1,123,0,
        -117,-1,123,0,-76,-1,123,0,-43,-1,123,0,-1,-1,123,0,0,0,-67,0,
        32,0,-67,0,65,0,-67,0,106,0,-67,0,-117,0,-67,0,-76,0,-67,0,
        -43,0,-67,0,-1,0,-67,0,0,48,-67,0,32,48,-67,0,65,48,-67,0,
        106,48,-67,0,-117,48,-67,0,-76,48,-67,0,-43,48,-67,0,-1,48,-67,0,
        0,101,-67,0,32,101,-67,0,65,101,-67,0,106,101,-67,0,-117,101,-67,0,
        -76,101,-67,0,-43,101,-67,0,-1,101,-67,0,0,-107,-67,0,32,-107,-67,0,
        65,-107,-67,0,106,-107,-67,0,-117,-107,-67,0,-76,-107,-67,0,-43,-107,-67,0,
        -1,-107,-67,0,0,-54,-67,0,32,-54,-67,0,65,-54,-67,0,106,-54,-67,0,
        -117,-54,-67,0,-76,-54,-67,0,-43,-54,-67,0,-1,-54,-67,0,0,-1,-67,0,
        32,-1,-67,0,65,-1,-67,0,106,-1,-67,0,-117,-1,-67,0,-76,-1,-67,0,
        -43,-1,-67,0,-1,-1,-67,0,0,0,-1,0,32,0,-1,0,65,0,-1,0,
        106,0,-1,0,-117,0,-1,0,-76,0,-1,0,-43,0,-1,0,-1,0,-1,0,
        0,48,-1,0,32,48,-1,0,65,48,-1,0,106,48,-1,0,-117,48,-1,0,
        -76,48,-1,0,-43,48,-1,0,-1,48,-1,0,0,101,-1,0,32,101,-1,0,
        65,101,-1,0,106,101,-1,0,-117,101,-1,0,-76,101,-1,0,-43,101,-1,0,
        -1,101,-1,0,0,-107,-1,0,32,-107,-1,0,65,-107,-1,0,106,-107,-1,0,
        -117,-107,-1,0,-76,-107,-1,0,-43,-107,-1,0,-1,-107,-1,0,0,-54,-1,0,
        32,-54,-1,0,65,-54,-1,0,106,-54,-1,0,-117,-54,-1,0,-76,-54,-1,0,
        -43,-54,-1,0,-1,-54,-1,0,0,-1,-1,0,32,-1,-1,0,65,-1,-1,0,
        106,-1,-1,0,-117,-1,-1,0,-76,-1,-1,0,-43,-1,-1,0,-1,-1,-1,0,
        -1,-1,-1,0,-26,-26,-26,0,-43,-43,-43,0,-59,-59,-59,0,-76,-76,-76,0,
        -92,-92,-92,0,-108,-108,-108,0,-125,-125,-125,0,115,115,115,0,98,98,98,0,
        82,82,82,0,65,65,65,0,49,49,49,0,32,32,32,0,16,16,16,0,
        0,0,0,0
    };
}


CDevice::CDevice(uint16_t id)
: serial ( 0 ),
pScreen ( 0 ),
devId ( 0 )
{
    if ( id == GARMIN_ETREXEURO ) {
        copyright = "<h1>QLandkarte Device Driver for Etrex Euro</h1>"
            "<h2>Driver I/F Ver. " INTERFACE_VERSION "</h2>"
            "<p>&#169; 2007 by Frank Seidel (frank@f-seidel.de)</p>"
            "<p>Info for Etrex Euro support by Martin Ereth (martin.ereth@arcor.de)</p>"
            "<p>This driver 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. </p>";
    }
    else {
        copyright = "<h1>QLandkarte Device Driver for Etrex H</h1>"
            "<h2>Driver I/F Ver. " INTERFACE_VERSION "</h2>"
            "<p>&#169; 2007 by Frank Seidel (frank@f-seidel.de)</p>"
            "<p>This driver 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. </p>";
    }

    //set ProductId
    devId = id;
}


CDevice::~CDevice()
{
    if ( pScreen )
        delete[] pScreen;
}


void CDevice::_acquire()
{
    int foundProducts = 0;
    uint16_t myid = 0;

#if defined(HAVE_BIGENDIAN) || !defined(CAN_UNALIGNED)
    throw exce_t(errSync, "This device has not yet been ported to your platform.");
#endif
    PROGR_CALLBACK ( 0,"acquiring" );

    serial = new EHSerial ( port );

    PROGR_CALLBACK ( 1,"acquiring ...");

    serial->open();
    serial->syncup();

    serial->setBitrate(GARMIN_EH_DFLTBITRATE);

    //ProductID
    myid = serial->getProductId();

    //Etrex H product string
    if ( strncmp ( serial->getProductString().c_str(), "eTrex H Software", 16 ) == 0 ) {
        if ( myid == GARMIN_ETREXH && devId == GARMIN_ETREXH ) {
            foundProducts++;
        }
    }
    //Etrex Euro product string
    if ( strncmp ( serial->getProductString().c_str(), "eTrex Euro Software", 19 ) == 0 ) {
        if ( myid == GARMIN_ETREXEURO && devId == GARMIN_ETREXEURO ) {
            foundProducts++;
        }
    }
    //check
    if ( foundProducts != 1 ) {
        PROGR_CALLBACK ( 100,"error occured" );

        throw exce_t ( errSync,"Error while probing for eTrex H and eTrex Euro unit detected, according to ProductString and Id. Please retry to select other device driver." );
    }
}


void CDevice::_downloadWaypoints ( list<Garmin::Wpt_t>& waypoints )
{
    waypoints.clear();
    if ( serial == 0 )
        return;

    PROGR_CALLBACK ( 2,"Downloading waypoints ..." );

    Packet_t command;
    Packet_t response;

    unsigned int nwpts = 0;
    unsigned int cnt = 0;

    command.id   = Pid_Command_Data;
    command.size = 2;
    command.payload[0] = Cmnd_Transfer_Wpt;
    command.payload[1] = 0x00;
    serial->write ( command );

    PROGR_CALLBACK ( 5,"Downloading waypoints ..." );

    while ( 1 ) {
        if ( !serial->read ( response ) ) {
            cout << "No response from Garmin eTrex H unit. repeating..." << endl;
            continue;
        }

        if ( response.id == Pid_Records )
            nwpts = *(uint16_t*)response.payload;

        if ( response.id == Pid_Wpt_Data ) {
            D108_Wpt_t * srcWpt = ( D108_Wpt_t* ) response.payload;
            waypoints.push_back ( Wpt_t() );
            Wpt_t& tarWpt = waypoints.back();

            tarWpt << *srcWpt;

            ++cnt;
            if (nwpts)
                PROGR_CALLBACK ( 5 + ( cnt * 94 / nwpts ),"Downloading waypoints ..." );
        }

        if ( response.id == Pid_Xfer_Cmplt )
            break;
    }

    PROGR_CALLBACK( 100,"Download complete" );
}


void CDevice::_uploadWaypoints ( std::list<Garmin::Wpt_t>& waypoints )
{
    if ( serial == 0 )
        return;

    PROGR_CALLBACK ( 2,"Uploading waypoints ..." );

    list<Wpt_t>::const_iterator wpt = waypoints.begin();

    Packet_t command;
    Packet_t response;

    unsigned int packcntr = 0;
    unsigned int npacks = waypoints.size();

    //transmit waypoints
    //announce number of records
    command.id   = Pid_Records;
    command.size = 2;
    * ( uint16_t* ) command.payload = waypoints.size();
    serial->write ( command );

    PROGR_CALLBACK ( 5,"Uploading waypoints ..." );

    wpt = waypoints.begin();
    while ( wpt != waypoints.end() ) {
        ++packcntr;

        command.id   = Pid_Wpt_Data;

        D108_Wpt_t * p = ( D108_Wpt_t * ) command.payload;
        command.size = *wpt >> *p;

        serial->write ( command );

        ++wpt;

        if ( npacks )
            PROGR_CALLBACK ( 5 + (packcntr * 94 / npacks),"Uploading waypoints ..." );
    }

    //announce number of records
    command.id   = Pid_Xfer_Cmplt;
    command.size = 2;
    command.payload[0] = Cmnd_Transfer_Wpt;
    command.payload[1] = 0x00;
    serial->write ( command );

    PROGR_CALLBACK ( 100,"Upload complete" );
}


void CDevice::_downloadTracks ( std::list<Garmin::Track_t>& tracks )
{
    tracks.clear();
    if ( serial == 0 )
        return;

    PROGR_CALLBACK ( 2,"Downloading tracks ..." );

    serial->setBitrate(GARMIN_EH_HIBITRATE);

    Packet_t command;
    Packet_t response;

    unsigned int npacks = 0;
    unsigned int packcntr = 0;

    command.id   = Pid_Command_Data;
    command.size = 2;
    command.payload[0] = Cmnd_Transfer_Trk;
    command.payload[1] = 0x00;
    serial->write ( command );

    PROGR_CALLBACK ( 3,"Downloading tracks ..." );

    int         trackidx = 0;
    string      name;
    Track_t *   track = 0;

    while ( 1 ) {
        if ( !serial->read ( response ) ) {
            cout << "No response from Garmin eTrex H unit. repeating..." << endl;
            continue;
        }

        if ( response.id == Pid_Records )
            npacks = *(uint16_t*)response.payload;

        if ( response.id == Pid_Trk_Hdr ) {
            ++packcntr;
            trackidx = 0;
            D310_Trk_Hdr_t * hdr = ( D310_Trk_Hdr_t* ) response.payload;
            tracks.push_back ( Track_t() );
            track = &tracks.back();

            *track << *hdr;
            name  = hdr->ident;

        }

        if ( response.id == Pid_Trk_Data ) {
            ++packcntr;
            D301_Trk_t * data = ( D301_Trk_t* ) response.payload;
            TrkPt_t pt;
            if ( data->new_trk ) {
                if ( trackidx ) {
                    tracks.push_back ( Track_t() );
                    Track_t& t = tracks.back();
                    t.color = track->color;
                    t.dspl = track->dspl;
                    char str[512];
                    sprintf ( str,"%s_%d",name.c_str(),trackidx++ );
                    t.ident = str;
                    track = &t;
                }
                else {
                    ++trackidx;
                }
            }

            pt << *data;
            track->track.push_back ( pt );
        }

        if ( npacks )
            PROGR_CALLBACK ( 3 + (packcntr * 96 / npacks),"Downloading tracks ..." );

        if ( response.id == Pid_Xfer_Cmplt )
            break;
    }

    serial->setBitrate(GARMIN_EH_DFLTBITRATE);

    PROGR_CALLBACK ( 100,"Download complete" );
}


void CDevice::_uploadRoutes(list<Garmin::Route_t>& routes)
{
    if (serial == 0)
        return;

    int canceled = 0;

    PROGR_CANCEL_CALLBACK ( 0,"Uploading Routes ...",&canceled );

    Packet_t command;
    Packet_t response;

    list<Garmin::Route_t>::const_iterator route = routes.begin();

    unsigned int nroutes = routes.size();
    unsigned int ncntr = 0;

    PROGR_CANCEL_CALLBACK ( 1,"Uploading Routes ...",&canceled );

    while(route != routes.end() && !canceled) {
        ++ncntr;

        //announce number of records
        uint16_t nrec = route->route.size() * 2;
        uint16_t recsent = 0;
        command.id   = Pid_Records;
        command.size = 2;
        *(uint16_t*)command.payload = nrec;
        serial->write(command);

        // write route header
        command.id   = Pid_Rte_Hdr;
        D202_Rte_Hdr_t * r = (D202_Rte_Hdr_t *)command.payload;
        command.size = *route >> *r;
        serial->write(command);
        ++recsent;

        vector<RtePt_t>::const_iterator rtept = route->route.begin();

        command.id   = Pid_Rte_Wpt_Data;
        D108_Wpt_t * p = (D108_Wpt_t *)command.payload;
        command.size = *rtept >> *p;
        serial->write(command);
        ++recsent;

        ++rtept;

        if ( nroutes && nrec )
            PROGR_CANCEL_CALLBACK ( 2+(ncntr-1)*97/nroutes+(recsent*97)/(nrec*nroutes),
                "Uploading Routes ...", &canceled );

        while (rtept != route->route.end() && !canceled) {
            command.id   = Pid_Rte_Link_Data;
            D210_Rte_Link_t * l = (D210_Rte_Link_t *)command.payload;
            command.size = *rtept >> *l;
            serial->write(command);
            ++recsent;

            command.id   = Pid_Rte_Wpt_Data;
            D108_Wpt_t * p = (D108_Wpt_t *)command.payload;
            command.size = *rtept >> *p;
            serial->write(command);
            ++recsent;

            ++rtept;

            if ( nroutes && nrec )
                PROGR_CANCEL_CALLBACK ( 2+(ncntr-1)*97/nroutes+(recsent*97)/(nrec*nroutes),
                    "Uploading Routes ...",&canceled );
        }

        // finish block
        command.id   = Pid_Xfer_Cmplt;
        command.size = 2;
        *(uint16_t*)command.payload = Cmnd_Transfer_Rte;
        serial->write(command);
        ++route;

        if ( nroutes )
            PROGR_CALLBACK ( 2 + ncntr * 97 / nroutes,"Uploading routes ..." );
    }

    PROGR_CANCEL_CALLBACK ( 100,"Uploading routes ...",&canceled );
}


void CDevice::_screenshot ( char *& clrtbl, char *& data, int& width, int& height )
{
    if ( serial == 0 )
        return;

    PROGR_CALLBACK ( 2,"Downloading screenshot ..." );

    Packet_t command;
    Packet_t response;

    int bperpixel = 0;
    int pixperpacket = 0;
    int slines = 0;
    int swidth = 0;
    int spackets = 0;
    long screenbytes = 0;
    int fetchedpackets = 0;
    unsigned long rawoffset = 0;
    char *rawbuffer = NULL;

    memcpy ( aClrtbl,_clrtbl,sizeof ( aClrtbl ) );

    command.id   = Pid_Command_Data;
    command.size = 2;
    command.payload[0] = CMD_TRANSFER_SCREEN;
    command.payload[1] = 0x00;
    serial->write ( command );

    PROGR_CALLBACK ( 3,"Downloading screenshot ..." );

    //collect data from unit
    while ( serial->read ( response ) ) {

        if ( response.id == PID_SCREENDATA ) {
            if ( response.payload[0] == 0 ) {
                //get meta-data with payload size 40
                bperpixel = response.payload[12];
                slines = response.payload[20];
                swidth = response.payload[16];
                pixperpacket = response.payload[8];
                screenbytes = ( bperpixel * swidth * slines ) / 8L;
                spackets = ( swidth * slines ) / ( pixperpacket * ( 8 / bperpixel ) );

                rawbuffer = new char[screenbytes];
                if ( rawbuffer == NULL ) {
                    PROGR_CALLBACK ( 100,"error occured" );
                    throw exce_t ( errSync,"Could not allocate memory for raw display data from unit." );
                    return;
                }

                PROGR_CALLBACK ( 5,"Downloading screenshot ..." );
            }
            else {
                //get image-data
                ++fetchedpackets;

                rawoffset = response.payload[4] + ( response.payload[5] << 8 )
                    + ( response.payload[6] << 16 ) + ( response.payload[7] << 24 );

                memcpy ( rawbuffer + rawoffset, response.payload + 8, pixperpacket );

                PROGR_CALLBACK ( 5 + (fetchedpackets * 85 / spackets),"Downloading screenshot ..." );

                if ( fetchedpackets == spackets )
                    break;
            }
        }
    }

    if ( pScreen )
        delete[] pScreen;
    pScreen = new char[swidth * slines];

    //reorder and extract pixel data
    unsigned long mask = 0x03L;
    char pcolor = 0;
    int pX = 63;
    int pY = 127;

    for ( int bnum=0; bnum < screenbytes; bnum += ( pixperpacket / 8 ) ) {
        unsigned long curdat = * ( ( unsigned long* ) ( rawbuffer + bnum ) );

        for ( int bitshift = 0; bitshift < pixperpacket; bitshift += bperpixel ) {
            pcolor = ( ( curdat & mask ) >> bitshift ) & 3;

            pScreen[pX + ( pY * slines ) ] = pcolor;

            --pY;
            if ( pY < 0 ) {
                --pX;
                pY = 127;

                PROGR_CALLBACK ( 90 + ((63 - pX) * 9 / 63),"Processing data ..." );
            }

            mask <<= 2;
        }

        mask = 0x03L;
    }

    clrtbl  = aClrtbl;
    data    = pScreen;
    width   = slines;
    height  = swidth;

    if ( rawbuffer )
        delete[] rawbuffer;

    PROGR_CALLBACK ( 100,"Completed screenshot" );
}


void CDevice::_release()
{
    if ( serial == 0 )
        return;

    serial->close();
    delete serial;
    serial = 0;
}
