/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */

/*
    Sonic Visualiser
    An audio file viewer and annotation editor.
    Centre for Digital Music, Queen Mary, University of London.
    
    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.  See the file
    COPYING included with this distribution for more information.
*/

/*
   This is a modified version of a source file from the 
   Rosegarden MIDI and audio sequencer and notation editor.
   This file copyright 2000-2006 Chris Cannam.
*/

#include <iostream>

#include <cstdlib>
#include <sstream>

using std::cerr;
using std::endl;

#include "RealTime.h"
#include "sys/time.h"

#include "Preferences.h"

// A RealTime consists of two ints that must be at least 32 bits each.
// A signed 32-bit int can store values exceeding +/- 2 billion.  This
// means we can safely use our lower int for nanoseconds, as there are
// 1 billion nanoseconds in a second and we need to handle double that
// because of the implementations of addition etc that we use.
//
// The maximum valid RealTime on a 32-bit system is somewhere around
// 68 years: 999999999 nanoseconds longer than the classic Unix epoch.

#define ONE_BILLION 1000000000

RealTime::RealTime(int s, int n) :
    sec(s), nsec(n)
{
    if (sec == 0) {
	while (nsec <= -ONE_BILLION) { nsec += ONE_BILLION; --sec; }
	while (nsec >=  ONE_BILLION) { nsec -= ONE_BILLION; ++sec; }
    } else if (sec < 0) {
	while (nsec <= -ONE_BILLION) { nsec += ONE_BILLION; --sec; }
	while (nsec > 0)             { nsec -= ONE_BILLION; ++sec; }
    } else { 
	while (nsec >=  ONE_BILLION) { nsec -= ONE_BILLION; ++sec; }
	while (nsec < 0)             { nsec += ONE_BILLION; --sec; }
    }
}

RealTime
RealTime::fromSeconds(double sec)
{
    return RealTime(int(sec), int((sec - int(sec)) * ONE_BILLION + 0.5));
}

RealTime
RealTime::fromMilliseconds(int msec)
{
    return RealTime(msec / 1000, (msec % 1000) * 1000000);
}

RealTime
RealTime::fromTimeval(const struct timeval &tv)
{
    return RealTime(tv.tv_sec, tv.tv_usec * 1000);
}

RealTime
RealTime::fromXsdDuration(std::string xsdd)
{
    RealTime t;

    int year = 0, month = 0, day = 0, hour = 0, minute = 0;
    double second = 0.0;

    int i = 0;

    const char *s = xsdd.c_str();
    int len = xsdd.length();

    bool negative = false, afterT = false;

    int valstart = 0;

    while (i < len) {

        if (s[i] == '-') {
            if (i == 0) negative = true;
            ++i;
            continue;
        }

        double value = 0.0;
        char *eptr = 0;

        if (isdigit(s[i]) || s[i] == '.') {
            valstart = i;
            value = strtod(&s[i], &eptr);
            i = eptr - s;
        }

        if (i == len) break;

        switch (s[i]) {
        case 'Y': year = int(value + 0.1); break;
        case 'D': day  = int(value + 0.1); break;
        case 'H': hour = int(value + 0.1); break;
        case 'M':
            if (afterT) minute = int(value + 0.1);
            else month = int(value + 0.1);
            break;
        case 'S':
            second = value;
            break;
        case 'T': afterT = true; break;
        };

        ++i;
    }

    if (year > 0) {
        std::cerr << "WARNING: This xsd:duration (\"" << xsdd << "\") contains a non-zero year.\nWith no origin and a limited data size, I will treat a year as exactly 31556952\nseconds and you should expect overflow and/or poor results." << std::endl;
        t = t + RealTime(year * 31556952, 0);
    }

    if (month > 0) {
        std::cerr << "WARNING: This xsd:duration (\"" << xsdd << "\") contains a non-zero month.\nWith no origin and a limited data size, I will treat a month as exactly 2629746\nseconds and you should expect overflow and/or poor results." << std::endl;
        t = t + RealTime(month * 2629746, 0);
    }

    if (day > 0) {
        t = t + RealTime(day * 86400, 0);
    }

    if (hour > 0) {
        t = t + RealTime(hour * 3600, 0);
    }

    if (minute > 0) {
        t = t + RealTime(minute * 60, 0);
    }

    t = t + fromSeconds(second);

    return t;
}

double
RealTime::toDouble() const
{
    double d = sec;
    d += double(nsec) / double(ONE_BILLION);
    return d;
}

std::ostream &operator<<(std::ostream &out, const RealTime &rt)
{
    if (rt < RealTime::zeroTime) {
	out << "-";
    } else {
	out << " ";
    }

    int s = (rt.sec < 0 ? -rt.sec : rt.sec);
    int n = (rt.nsec < 0 ? -rt.nsec : rt.nsec);

    out << s << ".";

    int nn(n);
    if (nn == 0) out << "00000000";
    else while (nn < (ONE_BILLION / 10)) {
	out << "0";
	nn *= 10;
    }
    
    out << n << "R";
    return out;
}

std::string
RealTime::toString(bool align) const
{
    std::stringstream out;
    out << *this;
    
    std::string s = out.str();

    if (!align && *this >= RealTime::zeroTime) {
        // remove leading " "
        s = s.substr(1, s.length() - 1);
    }

    // remove trailing R
    return s.substr(0, s.length() - 1);
}

RealTime
RealTime::fromString(std::string s)
{
    bool negative = false;
    bool section = 0;
    std::string ssec, snsec;

    for (size_t i = 0; i < s.length(); ++i) {

        char c = s[i];
        if (isspace(c)) continue;

        if (section == 0) {

            if (c == '-') negative = true;
            else if (isdigit(c)) { section = 1; ssec += c; }
            else if (c == '.') section = 2;
            else break;

        } else if (section == 1) {

            if (c == '.') section = 2;
            else if (isdigit(c)) ssec += c;
            else break;

        } else if (section == 2) {

            if (isdigit(c)) snsec += c;
            else break;
        }
    }

    while (snsec.length() < 8) snsec += '0';

    int sec = atoi(ssec.c_str());
    int nsec = atoi(snsec.c_str());
    if (negative) sec = -sec;

//    std::cerr << "RealTime::fromString: string " << s << " -> "
//              << sec << " sec, " << nsec << " nsec" << std::endl;

    return RealTime(sec, nsec);
}

std::string
RealTime::toText(bool fixedDp) const
{
    if (*this < RealTime::zeroTime) return "-" + (-*this).toText(fixedDp);

    Preferences *p = Preferences::getInstance();
    if (p) {
        int fps = 0;
        switch (p->getTimeToTextMode()) {
        case Preferences::TimeToTextMs: break;
        case Preferences::TimeToTextUs: fps = 1000000; break;
        case Preferences::TimeToText24Frame: fps = 24; break;
        case Preferences::TimeToText25Frame: fps = 25; break;
        case Preferences::TimeToText30Frame: fps = 30; break;
        case Preferences::TimeToText50Frame: fps = 50; break;
        case Preferences::TimeToText60Frame: fps = 60; break;
        }
        if (fps != 0) return toFrameText(fps);
    }

    std::stringstream out;

    if (sec >= 3600) {
	out << (sec / 3600) << ":";
    }

    if (sec >= 60) {
	out << (sec % 3600) / 60 << ":";
    }

    if (sec >= 10) {
	out << ((sec % 60) / 10);
    }

    out << (sec % 10);
    
    int ms = msec();

    if (ms != 0) {
	out << ".";
	out << (ms / 100);
	ms = ms % 100;
	if (ms != 0) {
	    out << (ms / 10);
	    ms = ms % 10;
	} else if (fixedDp) {
	    out << "0";
	}
	if (ms != 0) {
	    out << ms;
	} else if (fixedDp) {
	    out << "0";
	}
    } else if (fixedDp) {
	out << ".000";
    }
	
    std::string s = out.str();

    return s;
}

std::string
RealTime::toFrameText(int fps) const
{
    if (*this < RealTime::zeroTime) return "-" + (-*this).toFrameText(fps);

    std::stringstream out;

    if (sec >= 3600) {
	out << (sec / 3600) << ":";
    }

    if (sec >= 60) {
	out << (sec % 3600) / 60 << ":";
    }

    if (sec >= 10) {
	out << ((sec % 60) / 10);
    }

    out << (sec % 10);
    
    int f = nsec / (ONE_BILLION / fps);

    int div = 1;
    int n = fps - 1;
    while ((n = n / 10)) {
        div *= 10;
    }

    out << ":";

    std::cerr << "div = " << div << ", f =  "<< f << std::endl;

    while (div) {
        int d = (f / div) % 10;
        out << d;
        div /= 10;
    }
	
    std::string s = out.str();

    std::cerr << "converted " << toString() << " to " << s << std::endl;

    return s;
}

std::string
RealTime::toSecText() const
{
    if (*this < RealTime::zeroTime) return "-" + (-*this).toSecText();

    std::stringstream out;

    if (sec >= 3600) {
	out << (sec / 3600) << ":";
    }

    if (sec >= 60) {
	out << (sec % 3600) / 60 << ":";
    }

    if (sec >= 10) {
	out << ((sec % 60) / 10);
    }

    out << (sec % 10);
    
    if (sec < 60) {
        out << "s";
    }

    std::string s = out.str();

    return s;
}

std::string
RealTime::toXsdDuration() const
{
    std::string s = "PT" + toString(false) + "S";
    return s;
}

RealTime
RealTime::operator*(int m) const
{
    double t = (double(nsec) / ONE_BILLION) * m;
    t += sec * m;
    return fromSeconds(t);
}

RealTime
RealTime::operator/(int d) const
{
    int secdiv = sec / d;
    int secrem = sec % d;

    double nsecdiv = (double(nsec) + ONE_BILLION * double(secrem)) / d;
    
    return RealTime(secdiv, int(nsecdiv + 0.5));
}

RealTime
RealTime::operator*(double m) const
{
    double t = (double(nsec) / ONE_BILLION) * m;
    t += sec * m;
    return fromSeconds(t);
}

RealTime
RealTime::operator/(double d) const
{
    double t = (double(nsec) / ONE_BILLION) / d;
    t += sec / d;
    return fromSeconds(t);
}

double 
RealTime::operator/(const RealTime &r) const
{
    double lTotal = double(sec) * ONE_BILLION + double(nsec);
    double rTotal = double(r.sec) * ONE_BILLION + double(r.nsec);
    
    if (rTotal == 0) return 0.0;
    else return lTotal/rTotal;
}

long
RealTime::realTime2Frame(const RealTime &time, unsigned int sampleRate)
{
    if (time < zeroTime) return -realTime2Frame(-time, sampleRate);
    double s = time.sec + double(time.nsec + 1) / 1000000000.0;
    return long(s * sampleRate);
}

RealTime
RealTime::frame2RealTime(long frame, unsigned int sampleRate)
{
    if (frame < 0) return -frame2RealTime(-frame, sampleRate);

    RealTime rt;
    rt.sec = frame / long(sampleRate);
    frame -= rt.sec * long(sampleRate);
    rt.nsec = (int)(((double(frame) * 1000000.0) / long(sampleRate)) * 1000.0);
    return rt;
}

const RealTime RealTime::zeroTime(0,0);

