/* This file is part of the KDE project
   Copyright 2007 Stefan Nikolaus <stefan.nikolaus@kdemail.net>
   Copyright 2004 Tomas Mecir <mecirt@gmail.com>
   Copyright 1998,1999 Torben Weis <weis@kde.org>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

// Local
#include "ValueParser.h"

#include "CalculationSettings.h"
#include "Localization.h"
#include "Style.h"
#include "Value.h"

using namespace Calligra::Sheets;

ValueParser::ValueParser(const CalculationSettings* settings)
        : m_settings(settings)
{
}

const CalculationSettings* ValueParser::settings() const
{
    return m_settings;
}

Value ValueParser::parse(const QString& str) const
{
    Value val;

    // If the text is empty, we don't have a value
    // If the user stated explicitly that they wanted text
    // (using the format or using a quote),
    // then we don't parse as a value, but as string.
    if (str.isEmpty() || str.at(0) == '\'') {
        val = Value(str);
        return val;
    }

    bool ok;

    QString strStripped = str.trimmed();
    // Try parsing as various datatypes, to find the type of the string

    // First as number
    val = tryParseNumber(strStripped, &ok);

    if (ok)
        return val;

// Then as bool
// Note - I swapped the order of these two to try parsing as a number
// first because that will probably be the most common case
    val = tryParseBool(strStripped, &ok);
    if (ok)
        return val;

    // Test for money number
    Number money = m_settings->locale()->readMoney(strStripped, &ok);
    if (ok) {
        val = Value(money);
        val.setFormat(Value::fmt_Money);
        return val;
    }

    val = tryParseDate(strStripped, &ok);
    if (ok)
        return val;

    val = tryParseTime(strStripped, &ok);
    if (ok)
        return val;

    // Nothing particular found, then this is simply a string
    val = Value(str);
    return val;
}

Value ValueParser::tryParseBool(const QString& str, bool *ok) const
{
    Value val;
    if (ok) *ok = false;

    const QString& lowerStr = str.toLower();
    const QStringList localeCodes(m_settings->locale()->country());

    if ((lowerStr == "true") ||
            (lowerStr == ki18n("true").toString(localeCodes).toLower())) {
        val = Value(true);
        if (ok) *ok = true;
    } else if ((lowerStr == "false") ||
               (lowerStr == ki18n("false").toString(localeCodes).toLower())) {
        val = Value(false);
        if (ok) *ok = true;
    }
    return val;
}

Value ValueParser::readNumber(const QString& _str, bool *ok) const
{
    bool isInt = false;
    QString str = _str.trimmed();
    bool neg = str.indexOf(m_settings->locale()->negativeSign()) == 0;
    if (neg)
        str.remove(0, m_settings->locale()->negativeSign().length());

    /* will hold the scientific notation portion of the number.
    Example, with 2.34E+23, exponentialPart == "E+23"
    */
    QString exponentialPart;
    int EPos  = str.indexOf('E', 0, Qt::CaseInsensitive);

    if (EPos != -1) {
        exponentialPart = str.mid(EPos);
        str = str.left(EPos);
    }

    int pos;
    int fracPos;
    QString major;
    QString minor;
    if ((pos = str.indexOf(m_settings->locale()->decimalSymbol())) != -1) {
        major = str.left(pos);
        minor = str.mid(pos + m_settings->locale()->decimalSymbol().length());
        isInt = false;
    } else if (((pos = str.indexOf(' ')) != -1) &&
               ((fracPos = str.indexOf('/')) != -1)) {
        // try to parse fractions of this form:
        // [0-9]+ [0-9]+/[1-9][0-9]?
        major = str.left(pos);
        QString numerator = str.mid(pos + 1, (fracPos - pos - 1));
        QString denominator = str.mid(fracPos + 1);
        double minorVal = numerator.toDouble() / denominator.toDouble();
        if (minorVal > 1) {
            // assume major is just a plain number
            double wholePart = floor(minorVal);
            minorVal -= wholePart;
            major = QString("%1").arg(major.toInt() + (int)wholePart);
        }
        minor = QString::number(minorVal, 'f').remove(0, 2);     // chop off the "0." part
        // debugSheets <<"fraction:" << major <<"." << minor;
    } else {
        major = str;
        isInt = (EPos == -1); // only, if no exponential part was found
    }

    // Remove thousand separators
    int thlen = m_settings->locale()->thousandsSeparator().length();
    int lastpos = 0;
    while ((pos = major.indexOf(m_settings->locale()->thousandsSeparator())) > 0) {
        // e.g. 12,,345,,678,,922 Acceptable positions (from the end) are 5, 10, 15... i.e. (3+thlen)*N
        int fromEnd = major.length() - pos;
        if (fromEnd % (3 + thlen) != 0 // Needs to be a multiple, otherwise it's an error
                || pos - lastpos > 3 // More than 3 digits between two separators -> error
                || pos == 0          // Can't start with a separator
                || (lastpos > 0 && pos - lastpos != 3)) { // Must have exactly 3 digits between two separators
            if (ok) *ok = false;
            return Value();
        }

        lastpos = pos;
        major.remove(pos, thlen);
    }
    if (lastpos > 0 && major.length() - lastpos != 3) { // Must have exactly 3 digits after the last separator
        if (ok) *ok = false;
        return Value();
    }

    // log10(2^63) ~= 18
    if (isInt && major.length() > 19) isInt = false;

    QString tot;
    if (neg) tot = '-';
    tot += major;
    if (!isInt) tot += '.' + minor + exponentialPart;

    return isInt ? Value(tot.toLongLong(ok)) : Value(tot.toDouble(ok));
}

Number ValueParser::readImaginary(const QString& str, bool* ok) const
{
    if (str.isEmpty()) {
        if (ok) *ok = false;
        return 0.0;
    }

    Number imag = 0.0;
    if (str[0] == 'i' || str[0] == 'j') {
        if (str.length() == 1) {
            if (ok) *ok = true;
            imag = 1.0;
        } else
            imag = readNumber(str.mid(1), ok).asFloat();
    } else if (str[str.length()-1] == 'i' || str[str.length()-1] == 'j') {
        const QString minus(m_settings->locale()->negativeSign());
        if (str.length() == 2 && str[0] == '+') {
            if (ok) *ok = true;
            imag = 1.0;
        } else if (str.length() == minus.length() + 1 && str.left(minus.length()) == minus) {
            if (ok) *ok = true;
            imag = -1.0;
        } else
            imag = readNumber(str.left(str.length() - 1), ok).asFloat();
    } else
        *ok = false;
    return imag;
}

Value ValueParser::tryParseNumber(const QString& str, bool *ok) const
{
    Value value;
    if (str.endsWith('%')) {   // percentage
        const Number val = readNumber(str.left(str.length() - 1).trimmed(), ok).asFloat();
        if (*ok) {
            //debugSheets <<"ValueParser::tryParseNumber '" << str <<
            //    "' successfully parsed as percentage: " << val << '%' << endl;
            value = Value(val / 100.0);
            value.setFormat(Value::fmt_Percent);
        }
    } else if (str.count('i') == 1 || str.count('j') == 1) {    // complex number
        Number real = 0.0;
        Number imag = 0.0;
        const QString minus(m_settings->locale()->negativeSign());
        // both parts, real and imaginary, present?
        int sepPos;
        if ((sepPos = str.indexOf('+', 1)) != -1) {
            // imaginary part
            imag = readImaginary(str.mid(sepPos + 1).trimmed(), ok);
            // real part
            if (*ok)
                real = readNumber(str.left(sepPos).trimmed(), ok).asFloat();
        } else if ((sepPos = str.indexOf(minus, minus.length())) != -1) {
            // imaginary part
            imag = -readImaginary(str.mid(sepPos + 1).trimmed(), ok);
            // real part
            if (*ok)
                real = readNumber(str.left(sepPos).trimmed(), ok).asFloat();
        } else {
            // imaginary part
            if (str.trimmed().length() > 1)   // but don't parse a stand-alone 'i'
              imag = readImaginary(str.trimmed(), ok);
            // real part
            if (*ok)
                real = 0.0;
        }
        if (*ok)
            value = Value(complex<Number>(real, imag));
    } else // real number
        value = readNumber(str, ok);
    return value;
}

Value ValueParser::tryParseDate(const QString& str, bool *ok) const
{
    bool valid = false;
    QDate tmpDate = m_settings->locale()->readDate(str, &valid);
    if (!valid) {
        // Try without the year
        // The tricky part is that we need to remove any separator around the year
        // For instance %Y-%m-%d becomes %m-%d and %d/%m/%Y becomes %d/%m
        // If the year is in the middle, say %m-%Y/%d, we'll remove the sep.
        // before it (%m/%d).
        QString fmt = m_settings->locale()->dateFormatShort();
        int yearPos = fmt.indexOf("%Y", 0, Qt::CaseInsensitive);
        if (yearPos > -1) {
            if (yearPos == 0) {
                fmt.remove(0, 2);
                while (fmt[0] != '%')
                    fmt.remove(0, 1);
            } else {
                fmt.remove(yearPos, 2);
                for (; yearPos > 0 && fmt[yearPos-1] != '%'; --yearPos)
                    fmt.remove(yearPos, 1);
            }
            //debugSheets <<"Cell::tryParseDate short format w/o date:" << fmt;
            tmpDate = m_settings->locale()->readDate(str, fmt, &valid);
        }
    }
    if (valid) {
        // Note: if shortdate format only specifies 2 digits year, then 3/4/1955
        // will be treated as in year 3055, while 3/4/55 as year 2055
        // (because 55 < 69, see KLocale) and thus there's no way to enter for
        // year 1995

        // The following fixes the problem, 3/4/1955 will always be 1955

        QString fmt = m_settings->locale()->dateFormatShort();
        if ((fmt.contains("%y") == 1) && (tmpDate.year() > 2999))
            tmpDate = tmpDate.addYears(-1900);

        // this is another HACK !
        // with two digit years, 0-69 is treated as year 2000-2069 (see KLocale)
        // however, in Excel only 0-29 is year 2000-2029, 30 or later is 1930
        // onwards

        // the following provides workaround for KLocale so we're compatible
        // with Excel
        // (e.g 3/4/45 is Mar 4, 1945 not Mar 4, 2045)
        if ((tmpDate.year() >= 2030) && (tmpDate.year() <= 2069)) {
            QString yearFourDigits = QString::number(tmpDate.year());
            QString yearTwoDigits = QString::number(tmpDate.year() % 100);

            // if year is 2045, check to see if "2045" isn't there --> actual
            // input is "45"
            if ((str.count(yearTwoDigits) >= 1) &&
                    (str.count(yearFourDigits) == 0))
                tmpDate = tmpDate.addYears(-100);
        }
    }
    if (!valid) {
        //try to use the standard Qt date parsing, using ISO 8601 format
        tmpDate = QDate::fromString(str, Qt::ISODate);
        if (tmpDate.isValid()) {
            valid = true;
        }
    }

    if (ok)
        *ok = valid;

    return Value(tmpDate, m_settings);
}

Value ValueParser::tryParseTime(const QString& str, bool *ok) const
{
    bool valid = false;
    QDateTime tmpTime = readTime(str, true, &valid);
    if (!valid)
        tmpTime = readTime(str, false, &valid);

    if (!valid) {
        const QStringList localeCodes(m_settings->locale()->country());
        const QString stringPm = ki18n("pm").toString(localeCodes);
        const QString stringAm = ki18n("am").toString(localeCodes);
        int pos = 0;
        if ((pos = str.indexOf(stringPm, 0, Qt::CaseInsensitive)) != -1) {
            // cut off 'PM'
            QString tmp = str.mid(0, str.length() - stringPm.length());
            tmp = tmp.simplified();
            // try again
            tmpTime = readTime(tmp, true, &valid);
            if (!valid)
                tmpTime = readTime(tmp, false, &valid);
            if (valid && tmpTime.time().hour() > 11)
                valid = false;
            else if (valid)
                tmpTime = tmpTime.addSecs(43200); // add 12 hours
        } else if ((pos = str.indexOf(stringAm, 0, Qt::CaseInsensitive)) != -1) {
            // cut off 'AM'
            QString tmp = str.mid(0, str.length() - stringAm.length());
            tmp = tmp.simplified();
            // try again
            tmpTime = readTime(tmp, true, &valid);
            if (!valid)
                tmpTime = readTime(tmp, false, &valid);
            if (valid && tmpTime.time().hour() > 11)
                valid = false;
        }
    }

    if (ok)
        *ok = valid;
    Value value;
    if (valid) {
        value = Value(tmpTime, m_settings);
        value.setFormat(Value::fmt_Time);
    }
    return value;
}

QDateTime ValueParser::readTime(const QString& intstr, bool withSeconds, bool* ok) const
{
    QString str = intstr.simplified().toLower();
    QString format = m_settings->locale()->timeFormat().simplified();
    if (!withSeconds) {
        int n = format.indexOf("%S");
        format = format.left(n - 1);
    }

    QDateTime result;
    int hour = 0;
    int minute = 0;
    int second = 0;
    int msecs = 0;
    bool g_12h = false;
    bool pm = false;
    bool negative = false;
    uint strpos = 0;
    uint formatpos = 0;

    const uint l  = format.length();
    const uint sl = str.length();

    while (l > formatpos || sl > strpos) {
        if (!(l > formatpos && sl > strpos))
            goto error;

        QChar c(format.at(formatpos++));

        if (c != '%') {
            if (c.isSpace())
                ++strpos;
            else if (c != str.at(strpos++))
                goto error;
            continue;
        }

        // remove space at the beginning
        if (sl > strpos && str.at(strpos).isSpace())
            ++strpos;

        c = format.at(formatpos++);
        switch (c.toLatin1()) {
        case 'p': {
            const QStringList localeCodes(m_settings->locale()->country());
            QString s(ki18n("pm").toString(localeCodes).toLower());
            int len = s.length();
            if (str.mid(strpos, len) == s) {
                pm = true;
                strpos += len;
            } else {
                s = ki18n("am").toString(localeCodes).toLower();
                len = s.length();
                if (str.mid(strpos, len) == s) {
                    pm = false;
                    strpos += len;
                } else
                    goto error;
            }
        }
        break;

        case 'k':
        case 'H':
            g_12h = false;
            if (str.at(strpos) == '-') {
                negative = true;
                if (sl <= ++strpos)
                    goto error;
            }
            hour = readInt(str, strpos);
            if (hour < 0)
                goto error;

            break;

        case 'l':
        case 'I':
            g_12h = true;
            if (str.at(strpos) == '-') {
                negative = true;
                if (sl <= ++strpos)
                    goto error;
            }
            hour = readInt(str, strpos);
            if (hour < 1 || hour > 12)
                goto error;

            break;

        case 'M':
            minute = readInt(str, strpos);
            if (minute < 0 || minute > 59)
                goto error;

            break;

        case 'S':
            if (!withSeconds)
                break;
            second = readInt(str, strpos);
            if (second < 0 || second > 59)
                goto error;
            if (strpos < sl && str.indexOf(m_settings->locale()->decimalSymbol()) == (int)strpos) {
                strpos += m_settings->locale()->decimalSymbol().length();
                msecs = readInt(str, strpos);
                if (msecs < 0 || msecs > 999)
                    goto error;
            }

            break;
        }
    }

    if (g_12h) {
        hour %= 12;
        if (pm) hour += 12;
    }

    if (ok)
        *ok = true;
    result = QDateTime(m_settings->referenceDate(), QTime(0, 0), Qt::UTC);
    msecs += (((hour * 60 + minute) * 60 + second) * 1000);
    result = result.addMSecs(negative ? -msecs : msecs);
    return result;

error:
    if (ok)
        *ok = false;
    // return invalid date if it didn't work
    return QDateTime(m_settings->referenceDate(), QTime(-1, -1, -1), Qt::UTC);
}

/**
 * helper function to read integers, used in readTime
 * @param str
 * @param pos the position to start at. It will be updated when we parse it.
 * @return the integer read in the string, or -1 if no string
 */
int ValueParser::readInt(const QString& str, uint& pos) const
{
    if (!str.at(pos).isDigit())
        return -1;
    int result = 0;
    for (; (uint) str.length() > pos && str.at(pos).isDigit(); pos++) {
        result *= 10;
        result += str.at(pos).digitValue();
    }

    return result;
}

