/*
 * Copyright (c) 2011 Damien Grauser (Damien.Grauser@pev-geneve.ch)
 *
 * 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 "SyncRideFile.h"
#include <QSharedPointer>
#include <QMap>
#include <QSet>
#include <stdio.h>
#include <stdint.h>
#include <cmath>


static int syncFileReaderRegistered =
    RideFileFactory::instance().registerReader(
        "osyn", "Macro GoldenCheetah Sync File", new SyncFileReader());


#define TRAINING_DETAIL 0x89 //Packet with details about a specific training


struct SyncFileReaderState
{
    QFile &file;
    QStringList &errors;
    RideFile *rideFile;

    double secs, km;

    int interval;
    double last_interval_secs;

    bool stopped;


    SyncFileReaderState(QFile &file, QStringList &errors) :
        file(file), errors(errors), rideFile(NULL), secs(0), km(0),
        interval(0), last_interval_secs(0.0),  stopped(true)
    {

    }

    struct TruncatedRead {};

    int read_bsd_byte(int *count = NULL, int *sum = NULL) {
        char c;
        if (file.read(&c, 1) != 1)
            throw TruncatedRead();
        if (sum)
            *sum += (0xff & c);
        if (count)
            *count += 1;

        return (0xff & c) - (int(c/16)*6);
    }

    int read_bytes(int len, int *count = NULL, int *sum = NULL, bool issigned = false) {
        char c;
        int res = 0;

        for (int i = 0; i < len; ++i) {
            if (file.read(&c, 1) != 1)
                throw TruncatedRead();
            if (sum)
                *sum += (0xff & c);
            if (count)
                *count += 1;

            res += pow(256,i) * (0xff & (unsigned) c) ;

            if (issigned && i == len-1)
                res = res - (0x80 & (unsigned) c) * 2 ;
        }

        return res;
    }


    QString
    cEscape(char *buf, int len)
    {
        char *result = new char[4 * len + 1];
        char *tmp = result;
        for (int i = 0; i < len; ++i) {
            if (buf[i] == '"')
                tmp += sprintf(tmp, "\\\"");
            else if (isprint(buf[i]))
                *(tmp++) = buf[i];
            else
                tmp += sprintf(tmp, "\\x%02x", 0xff & (unsigned) buf[i]);
        }
        return result;
    }

    void read_graph_data(double *secs, int *count = NULL, int *sum = NULL)
    {
        double alt = 0, cad = 0, grade = 0, hr = 0, kph = 0, watts = 0;

        double nm = 0, headwind = 0.0, temp = 0.0;
        int lng = 0, lat = 0;

        // Graph data (12 bytes)

        temp = read_bytes(2, count, sum, true) / 100; // Temperature * 100 (in degree C) (signed)

        alt = read_bytes(2, count, sum); //Alti (in m)
        grade = read_bytes(1, count, sum, true); // Gradient % (signed)
        cad = read_bytes(1, count, sum); // Cadence
        kph = read_bytes(2, count, sum)/10.0; // Speed * 10 (in Km/h)
        hr = read_bytes(1, count, sum); // Heart Rate
        watts = read_bytes(2, count, sum); // Power (in Watt)

        int record_data_flag = read_bytes(1, count, sum); // Data Flag

        int  intSecs = 1;
        if ((record_data_flag & 0x03) == 0)
            intSecs = 5;
        else if ((record_data_flag & 0x03) == 1)
            intSecs = 10;
        else if ((record_data_flag & 0x03) == 2)
            intSecs = 20;

        km += intSecs * kph / 3600.0;
        rideFile->setRecIntSecs(intSecs);
        rideFile->appendPoint(*secs, cad, hr, km, kph, nm, watts, alt, lng, lat, headwind, grade, temp, 0.0, 
                              0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
                              0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, interval);

        *secs = *secs + intSecs;

    }

    int read_page()
    {
        int sum = 0;
        int bytes_read = 0;

        char record_command = read_bytes(1, &bytes_read, &sum); // Always 0x89

        if ((0xff & record_command) == 0x89)
        {
            // 1. page # data
            int page_number = read_bytes(2, &bytes_read, &sum); // Page #
            int data_number = read_bytes(1, &bytes_read, &sum); // # of data in page



            if (page_number == 1 || (page_number == 64010 && secs == 0.0)) {
                // 2. Training Summary data (60 bytes)";
                read_bytes(39, &bytes_read, &sum);

                int record_training_flag = read_bytes(1, &bytes_read, &sum); // Training Flag

                if ((record_training_flag & 0x01) == 0) {
                    // Only new lap
                    rideFile->addInterval(RideFileInterval::DEVICE, last_interval_secs, secs, QString("%1").arg(interval));
                    last_interval_secs = secs;
                    interval ++;
                }

                read_bytes(20, &bytes_read, &sum); // Don't care
            }

            if (page_number == 1 || (page_number == 64010 && secs == 0.0)) {
                // Section Start time and date data (12 byte)

                int sec = read_bsd_byte(&bytes_read, &sum); // Section start time sec
                int min = read_bsd_byte(&bytes_read, &sum); // Section start time min
                int hour = read_bsd_byte(&bytes_read, &sum); // Section start time hour
                int day = read_bsd_byte(&bytes_read, &sum); // Section start time day
                int month = read_bytes(1, &bytes_read, &sum); // Section start time month
                int year = read_bsd_byte(&bytes_read, &sum); // Section start time year

                QDateTime t = QDateTime(QDate(2000+year,month,day), QTime(hour,min,sec));

                if (secs == 0.0) { // || rideFile->startTime().toSecsSinceEpoch()<0
                    rideFile->setStartTime(t);
                }
                else {
                    int gap = (t.toSecsSinceEpoch() - rideFile->startTime().toSecsSinceEpoch()) - secs;
                    secs += gap;
                }

                read_bytes(5, &bytes_read, &sum); // Don't care

                read_bytes(1, &bytes_read, &sum); // Data Flag
                data_number--;
            }

            for (int i = 0; i < data_number; ++i) {
                read_graph_data(&secs, &bytes_read, &sum);
            }

            int finish = 259-bytes_read;

            for (int i = 0; i < finish; i++) {
                read_bytes(1, &bytes_read, &sum); // to finish
            }

            read_bytes(1, &bytes_read, &sum); // Checksum
        }

        return bytes_read;
    }

    RideFile * run() {
        errors.clear();
        rideFile = new RideFile;
        rideFile->setDeviceType("o_synce macro/macrox");
        rideFile->setFileFormat("Macro GoldenCheetah Sync File (osyn)");

        if (!file.open(QIODevice::ReadOnly)) {
            delete rideFile;
            return NULL;
        }
        bool stop = false;

        qint64 data_size = file.size();
        qint64 bytes_read = 0;

        while (!stop && (bytes_read < data_size)) {
            bytes_read += read_page(); // read_page(stop, errors);
        }

        if (stop) {
            file.close();
            delete rideFile;
            return NULL;

        } else {
            file.close();
            return rideFile;
        }
    }
};


RideFile *SyncFileReader::openRideFile(QFile &file, QStringList &errors, QList<RideFile*>*) const
{
    QSharedPointer<SyncFileReaderState> state(new SyncFileReaderState(file, errors));
    return state->run();
}
