/* This file is part of the KDE project
   Copyright (C) 1998-2003 The KSpread Team
                           www.koffice.org/kspread
   Copyright (C) 2005 Tomas Mecir <mecirt@gmail.com>

   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.

   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.
*/

// built-in date/time functions

#include "functions.h"
#include "kspread_functions_helper.h"
#include "valuecalc.h"
#include "valueconverter.h"

#include <kcalendarsystem.h>
#include <klocale.h>

using namespace KSpread;

// prototypes, sorted
Value func_currentDate (valVector args, ValueCalc *calc, FuncExtra *);
Value func_currentDateTime (valVector args, ValueCalc *calc, FuncExtra *);
Value func_currentTime (valVector args, ValueCalc *calc, FuncExtra *);
Value func_date (valVector args, ValueCalc *calc, FuncExtra *);
Value func_datevalue (valVector args, ValueCalc *calc, FuncExtra *);
Value func_day (valVector args, ValueCalc *calc, FuncExtra *);
Value func_dayname (valVector args, ValueCalc *calc, FuncExtra *);
Value func_dayOfYear (valVector args, ValueCalc *calc, FuncExtra *);
Value func_days (valVector args, ValueCalc *calc, FuncExtra *);
Value func_days360 (valVector args, ValueCalc *calc, FuncExtra *);
Value func_daysInMonth (valVector args, ValueCalc *calc, FuncExtra *);
Value func_daysInYear  (valVector args, ValueCalc *calc, FuncExtra *);
Value func_easterSunday (valVector args, ValueCalc *calc, FuncExtra *);
Value func_edate (valVector args, ValueCalc *calc, FuncExtra *);
Value func_eomonth (valVector args, ValueCalc *calc, FuncExtra *);
Value func_hour (valVector args, ValueCalc *calc, FuncExtra *);
Value func_hours (valVector args, ValueCalc *calc, FuncExtra *);
Value func_isLeapYear  (valVector args, ValueCalc *calc, FuncExtra *);
Value func_isoWeekNum (valVector args, ValueCalc *calc, FuncExtra *);
Value func_minute (valVector args, ValueCalc *calc, FuncExtra *);
Value func_minutes (valVector args, ValueCalc *calc, FuncExtra *);
Value func_month (valVector args, ValueCalc *calc, FuncExtra *);
Value func_monthname (valVector args, ValueCalc *calc, FuncExtra *);
Value func_months (valVector args, ValueCalc *calc, FuncExtra *);
Value func_second (valVector args, ValueCalc *calc, FuncExtra *);
Value func_seconds (valVector args, ValueCalc *calc, FuncExtra *);
Value func_time (valVector args, ValueCalc *calc, FuncExtra *);
Value func_timevalue (valVector args, ValueCalc *calc, FuncExtra *);
Value func_today (valVector args, ValueCalc *calc, FuncExtra *);
Value func_weekday (valVector args, ValueCalc *calc, FuncExtra *);
Value func_weeks (valVector args, ValueCalc *calc, FuncExtra *);
Value func_weeksInYear (valVector args, ValueCalc *calc, FuncExtra *);
Value func_year (valVector args, ValueCalc *calc, FuncExtra *);
Value func_years (valVector args, ValueCalc *calc, FuncExtra *);

// registers all date/time functions
// sadly, many of these functions aren't Excel compatible
void RegisterDateTimeFunctions()
{
  // missing: Excel:    WORKDAY, NETWORKDAYS, WEEKNUM, DATEDIF
  //          Gnumeric: UNIX2DATE, DATE2UNIX
  // TODO: do we really need DATEVALUE and TIMEVALUE ?
  FunctionRepository* repo = FunctionRepository::self();
  Function *f;
  
  f = new Function ("CURRENTDATE",  func_currentDate);
  f->setParamCount (0);
  repo->add (f);
  f = new Function ("CURRENTDATETIME",  func_currentDateTime);
  f->setParamCount (0);
  repo->add (f);
  f = new Function ("CURRENTTIME",  func_currentTime);
  f->setParamCount (0);
  repo->add (f);
  f = new Function ("DATE",  func_date);
  f->setParamCount (3);
  repo->add (f);
  f = new Function ("DATEVALUE",  func_datevalue);
  repo->add (f);
  f = new Function ("DAY",  func_day);
  repo->add (f);
  f = new Function ("DAYNAME",  func_dayname);
  repo->add (f);
  f = new Function ("DAYOFYEAR",  func_dayOfYear);
  f->setParamCount (3);
  repo->add (f);
  f = new Function ("DAYS",  func_days);
  f->setParamCount (2);
  repo->add (f);
  f = new Function ("DAYS360",  func_days360);
  f->setParamCount (2, 3);
  repo->add (f);
  f = new Function ("DAYSINMONTH",  func_daysInMonth);
  f->setParamCount (2);
  repo->add (f);
  f = new Function ("DAYSINYEAR",  func_daysInYear);
  repo->add (f);
  f = new Function ("EASTERSUNDAY",  func_easterSunday);
  repo->add (f);
  f = new Function ("EDATE",  func_edate);
  f->setParamCount (2);
  repo->add (f);
  f = new Function ("EOMONTH",  func_eomonth);
  f->setParamCount (2);
  repo->add (f);
  f = new Function ("HOUR",  func_hour);
  f->setParamCount (0, 1);
  repo->add (f);
  f = new Function ("HOURS",  func_hour);  // same as HOUR
  f->setParamCount (0, 1);
  repo->add (f);
  f = new Function ("ISLEAPYEAR",  func_isLeapYear);
  repo->add (f);
  f = new Function ("ISOWEEKNUM",  func_isoWeekNum);
  repo->add (f);
  f = new Function ("MINUTE",  func_minute);
  f->setParamCount (0, 1);
  repo->add (f);
  f = new Function ("MINUTES",  func_minute);  // same as MINUTE
  f->setParamCount (0, 1);
  repo->add (f);
  f = new Function ("MONTH",  func_month);
  repo->add (f);
  f = new Function ("MONTHNAME",  func_monthname);
  repo->add (f);
  f = new Function ("MONTHS",  func_months);
  f->setParamCount (3);
  repo->add (f);
  f = new Function ("NOW",  func_currentDateTime);
  f->setParamCount (0);
  repo->add (f);
  f = new Function ("SECOND",  func_second);
  f->setParamCount (0, 1);
  repo->add (f);
  f = new Function ("SECONDS",  func_second);  // same as SECOND
  f->setParamCount (0, 1);
  repo->add (f);
  f = new Function ("TIME",  func_time);
  f->setParamCount (3);
  repo->add (f);
  f = new Function ("TIMEVALUE",  func_timevalue);
  repo->add (f);
  f = new Function ("TODAY",  func_currentDate);
  f->setParamCount (0);
  repo->add (f);
  f = new Function ("WEEKDAY",  func_weekday);
  f->setParamCount (1, 2);
  repo->add (f);
  f = new Function ("WEEKS",  func_weeks);
  f->setParamCount (3);
  repo->add (f);
  f = new Function ("WEEKSINYEAR",  func_weeksInYear);
  repo->add (f);
  f = new Function ("YEAR",   func_year);
  repo->add (f);
  f = new Function ("YEARS",  func_years);
  f->setParamCount (3);
  repo->add (f);
}

// Function: EDATE
Value func_edate (valVector args, ValueCalc *calc, FuncExtra *)
{
  QDate date = calc->conv()->asDate (args[0]).asDate();
  int months = calc->conv()->asInteger (args[1]).asInteger();
  
  date = calc->conv()->locale()->calendar()->addMonths (date, months);

  if (!date.isValid())
    return Value::errorVALUE();

  return Value (date);
}

// Function: EOMONTH
Value func_eomonth (valVector args, ValueCalc *calc, FuncExtra *)
{
  // add months to date using EDATE
  Value modDate = func_edate (args, calc, 0);
  if (modDate.isError()) return modDate;
  
  // modDate is currently in Date format
  QDate date = modDate.asDate();
  date.setYMD (date.year(), date.month(), date.daysInMonth());

  return Value (date);
}

// Function: DAYS360
// algorithm adapted from gnumeric
Value func_days360 (valVector args, ValueCalc *calc, FuncExtra *)
{
  QDate date1 = calc->conv()->asDate (args[0]).asDate();
  QDate date2 = calc->conv()->asDate (args[1]).asDate();
  bool european = false;
  if (args.count() == 3)
    european = calc->conv()->asBoolean (args[2]).asBoolean();

  int day1, day2;
  int month1, month2;
  int year1, year2;
  bool negative = false;

  if (date1.daysTo( date2 ) < 0)
  {
    QDate tmp( date1 );
    date1 = date2;
    date2 = tmp;
    negative = true;
  }

  day1   = date1.day();
  day2   = date2.day();
  month1 = date1.month();
  month2 = date2.month();
  year1  = date1.year();
  year2  = date2.year();

  if ( european )
  {
    if ( day1 == 31 )
      day1 = 30;
    if ( day2 == 31 )
      day2 = 30;
  }
  else
  {
    // thanks to the Gnumeric developers for this...
    if ( month1 == 2 && month2 == 2
         && date1.daysInMonth() == day1
         && date2.daysInMonth() == day2 )
      day2 = 30;

    if ( month1 == 2 && date1.daysInMonth() == day1 )
      day1 = 30;

    if ( day2 == 31 && day1 >= 30 )
      day2 = 30;

    if ( day1 == 31 )
      day1 = 30;
  }

  int result = ( ( year2 - year1 ) * 12 + ( month2 - month1 ) ) * 30
    + ( day2 - day1 );

  return Value (result);
}

// Function: YEAR
Value func_year (valVector args, ValueCalc *calc, FuncExtra *)
{
  Value v = calc->conv()->asDate (args[0]);
  if (v.isError()) return v;
  QDate date = v.asDate();
  return Value (date.year ());
}

// Function: MONTH
Value func_month (valVector args, ValueCalc *calc, FuncExtra *)
{
  Value v = calc->conv()->asDate (args[0]);
  if (v.isError()) return v;
  QDate date = v.asDate();
  return Value (date.month ());
}

// Function: DAY
Value func_day (valVector args, ValueCalc *calc, FuncExtra *)
{
  Value v = calc->conv()->asDate (args[0]);
  if (v.isError()) return v;
  QDate date = v.asDate();
  return Value (date.day ());
}

// Function: HOUR
Value func_hour (valVector args, ValueCalc *calc, FuncExtra *)
{
  QTime time;
  if (args.count() == 1)
  {
    Value v = calc->conv()->asTime (args[0]);
    if (v.isError()) return v;
    time = v.asTime();
  }
  else
    time = QTime::currentTime ();
  return Value (time.hour ());
}

// Function: MINUTE
Value func_minute (valVector args, ValueCalc *calc, FuncExtra *)
{
  QTime time;
  if (args.count() == 1)
  {
    Value v = calc->conv()->asTime (args[0]);
    if (v.isError()) return v;
    time = v.asTime();
  }
  else
    time = QTime::currentTime ();
  return Value (time.minute ());
}

// Function: SECOND
Value func_second (valVector args, ValueCalc *calc, FuncExtra *)
{
  QTime time;
  if (args.count() == 1)
  {
    Value v = calc->conv()->asTime (args[0]);
    if (v.isError()) return v;
    time = v.asTime();
  }
  else
    time = QTime::currentTime ();
  return Value (time.second ());
}

// Function: weekday
Value func_weekday (valVector args, ValueCalc *calc, FuncExtra *)
{
  Value v = calc->conv()->asDate (args[0]).asDate();
  if (v.isError()) return v;
  QDate date = v.asDate();
  int method = 1;
  if (args.count() == 2)
    method = calc->conv()->asInteger (args[1]).asInteger();
  
  if ( method < 1 || method > 3 )
    return Value::errorVALUE();

  int result = date.dayOfWeek();

  if (method == 3)
    --result;
  else if (method == 1)
  {
    ++result;
    result = result % 7;
  }

  return Value (result);
}

// Function: datevalue
// same result would be obtained by applying number format on a date value
Value func_datevalue (valVector args, ValueCalc *calc, FuncExtra *)
{
  if (args[0].isString()) {
    Value v = calc->conv()->asDate (args[0]);
    if (! v.isError())
      return calc->conv()->asFloat (v);
  }
  return Value::errorVALUE();
}

// Function: timevalue
// same result would be obtained by applying number format on a time value
Value func_timevalue (valVector args, ValueCalc *calc, FuncExtra *)
{
  if (args[0].isString()) {
    Value v = calc->conv()->asTime (args[0]);
    if (! v.isError())
      return calc->conv()->asFloat (v);
  }
  return Value::errorVALUE();
}

// Function: years
Value func_years (valVector args, ValueCalc *calc, FuncExtra *)
{
  QDate date1 = calc->conv()->asDate (args[0]).asDate();
  QDate date2 = calc->conv()->asDate (args[1]).asDate();
  if (!date1.isValid() || !date2.isValid())
    return Value::errorVALUE();

  int type = calc->conv()->asInteger (args[2]).asInteger();
  if (type == 0)
  {
    // max. possible years between both dates
    int years = date2.year() - date1.year();

    if (date2.month() < date1.month())
      --years;
    else if ( (date2.month() == date1.month()) && (date2.day() < date1.day()) )
      --years;

    return Value (years);
  }
    
  // type is non-zero now
  // the number of full years in between, starting on 1/1/XXXX
  if ( date1.year() == date2.year() )
    return Value (0);

  if ( (date1.month() != 1) || (date1.day() != 1) )
    date1.setYMD(date1.year() + 1, 1, 1);
  date2.setYMD(date2.year(), 1, 1);

  return Value (date2.year() - date1.year());
}

// Function: months
Value func_months (valVector args, ValueCalc *calc, FuncExtra *)
{
  QDate date1 = calc->conv()->asDate (args[0]).asDate();
  QDate date2 = calc->conv()->asDate (args[1]).asDate();
  if (!date1.isValid() || !date2.isValid())
    return Value::errorVALUE();

  int type = calc->conv()->asInteger (args[2]).asInteger();
  if (type == 0)
  {
    int months  = (date2.year() - date1.year()) * 12;
    months += date2.month() - date1.month();

    if (date2.day() < date1.day())
      if (date2.day() != date2.daysInMonth())
        --months;

    return Value (months);
  }
  
  // type is now non-zero
  // the number of full months in between, starting on 1/XX/XXXX
  if (date1.month() == 12)
    date1.setYMD(date1.year() + 1, 1, 1);
  else
    date1.setYMD(date1.year(), date1.month() + 1, 1);
  date2.setYMD(date2.year(), date2.month(), 1);

  int months = (date2.year() - date1.year()) * 12;
  months += date2.month() - date1.month();

  return Value (months);
}

// Function: weeks
Value func_weeks (valVector args, ValueCalc *calc, FuncExtra *)
{
  QDate date1 = calc->conv()->asDate (args[0]).asDate();
  QDate date2 = calc->conv()->asDate (args[1]).asDate();
  if (!date1.isValid() || !date2.isValid())
    return Value::errorVALUE();

  int type = calc->conv()->asInteger (args[2]).asInteger();
  int days = date1.daysTo (date2);
  if (type == 0)
    // just the number of full weeks between
    return Value ((int) (days / 7));
    
  // the number of full weeks between starting on mondays
  int weekStartDay = calc->conv()->locale()->weekStartDay();
  
  int dow1 = date1.dayOfWeek();
  int dow2 = date2.dayOfWeek();

  days -= (7 + (weekStartDay % 7) - dow1);
  days -= ((dow2 - weekStartDay) % 7);

  return Value ((int) (days / 7));
}

// Function: days
Value func_days (valVector args, ValueCalc *calc, FuncExtra *)
{
  QDate date1 = calc->conv()->asDate (args[0]).asDate();
  QDate date2 = calc->conv()->asDate (args[1]).asDate();
  if (!date1.isValid() || !date2.isValid())
    return Value::errorVALUE();

  return Value (date1.daysTo (date2));
}

// Function: date
Value func_date (valVector args, ValueCalc *calc, FuncExtra *)
{
  int y = calc->conv()->asInteger (args[0]).asInteger();
  int m = calc->conv()->asInteger (args[1]).asInteger();
  int d = calc->conv()->asInteger (args[2]).asInteger();
  
  QDate _date;
  if( _date.setYMD (y, m, d))
    return Value (_date);
  return Value::errorVALUE();
}

// Function: day
Value func_dayname (valVector args, ValueCalc *calc, FuncExtra *)
{
  int number = calc->conv()->asInteger (args[0]).asInteger();

  QString weekName = calc->conv()->locale()->calendar()->weekDayName (number);
  if (weekName.isNull())
    return Value::errorVALUE();
  return Value (weekName);
}

// Function: monthname
Value func_monthname (valVector args, ValueCalc *calc, FuncExtra *)
{
  int number = calc->conv()->asInteger (args[0]).asInteger();

  QString monthName = calc->conv()->locale()->calendar()->monthName (number,
      QDate::currentDate().year());
  if (monthName.isNull())
    return Value::errorVALUE();
  return Value (monthName);
}

// Function: time
Value func_time (valVector args, ValueCalc *calc, FuncExtra *)
{
  int h = calc->conv()->asInteger (args[0]).asInteger();
  int m = calc->conv()->asInteger (args[1]).asInteger();
  int s = calc->conv()->asInteger (args[2]).asInteger();
  
  /* normalize the data */
  m += s / 60;
  s = s % 60;
  h += m / 60;
  m = m % 60;
  // we'll lose hours data that carries over into days
  h = h % 24;

  // now carry down hours/minutes for negative minutes/seconds
  if (s < 0) {
    s += 60;
    m -= 1;
  }
  if (m < 0) {
    m += 60;
    h -= 1;
  }
  if (h < 0)
    h += 24;

  return Value (QTime (h, m, s));
}

// Function: currentDate
Value func_currentDate (valVector, ValueCalc *, FuncExtra *)
{
  return Value (QDate::currentDate ());
}

// Function: currentTime
Value func_currentTime (valVector, ValueCalc *, FuncExtra *)
{
  return Value (QTime::currentTime ());
}

// Function: currentDateTime
Value func_currentDateTime (valVector, ValueCalc *, FuncExtra *)
{
  return Value (QDateTime::currentDateTime ());
}

// Function: dayOfYear
Value func_dayOfYear (valVector args, ValueCalc *calc, FuncExtra *)
{
  Value date = func_date (args, calc, 0);
  if (date.isError()) return date;
  return Value (date.asDate().dayOfYear());
}

// Function: daysInMonth
Value func_daysInMonth (valVector args, ValueCalc *calc, FuncExtra *)
{
  int y = calc->conv()->asInteger (args[0]).asInteger();
  int m = calc->conv()->asInteger (args[1]).asInteger();
  QDate date (y, m, 1);
  return Value (date.daysInMonth());
}

// Function: isLeapYear
Value func_isLeapYear  (valVector args, ValueCalc *calc, FuncExtra *)
{
  int y = calc->conv()->asInteger (args[0]).asInteger();
  return Value (QDate::leapYear (y));
}

// Function: daysInYear
Value func_daysInYear  (valVector args, ValueCalc *calc, FuncExtra *)
{
  int y = calc->conv()->asInteger (args[0]).asInteger();
  return Value (QDate::leapYear (y) ? 366 : 365);
}

// Function: weeksInYear
Value func_weeksInYear (valVector args, ValueCalc *calc, FuncExtra *)
{
  int y = calc->conv()->asInteger (args[0]).asInteger();
  QDate date (y, 12, 31);  // last day of the year
  return Value (date.weekNumber ());
}

// Function: easterSunday
Value func_easterSunday (valVector args, ValueCalc *calc, FuncExtra *)
{
  int nDay, nMonth;
  int nYear = calc->conv()->asInteger (args[0]).asInteger();

  // (Tomas) the person who wrote this should be hanged :>
  int B,C,D,E,F,G,H,I,K,L,M,N,O;
  N = nYear % 19;
  B = int(nYear / 100);
  C = nYear % 100;
  D = int(B / 4);
  E = B % 4;
  F = int((B + 8) / 25);
  G = int((B - F + 1) / 3);
  H = (19 * N + B - D - G + 15) % 30;
  I = int(C / 4);
  K = C % 4;
  L = (32 + 2 * E + 2 * I - H - K) % 7;
  M = int((N + 11 * H + 22 * L) / 451);
  O = H + L - 7 * M + 114;
  nDay = O % 31 + 1;
  nMonth = int(O / 31);

  return Value (QDate (nYear, nMonth, nDay));
}

// Function: isoWeekNum
Value func_isoWeekNum (valVector args, ValueCalc *calc, FuncExtra *)
{
  QDate date = calc->conv()->asDate (args[0]).asDate();
  if (!date.isValid())
      return Value::errorVALUE();

  return Value (date.weekNumber());
}
