/*
   Copyright (C) 2002-2003 Arash Bijanzadeh  and FarsiKDE Project <www.farsikde.org>
   Contact: Arash Bijanzadeh <a.bijanzadeh@linuxiran.org>

   This program is part of FarsiKDE

   FarsiKDE 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.

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


#include <tqdatetime.h>
#include <tqstring.h>
#include <tqstringlist.h>
#include <math.h>

#include <tdeglobal.h>
#include <tdelocale.h>
#include <kdebug.h>
#include <stdio.h>

#include "kcalendarsystemjalali.h"

static const int  gMonthDay[2][13]={
        {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
        {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
};

static const    int     jMonthDay[2][13] = {
        {0, 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29},
        {0, 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 30},
};

typedef struct {
        int day;
        int mon;
        int year;
        } SDATE;
// converting funcs from

static int Ceil(float number)
{
    int ret;
    if(number>0)
	number += 0.5;
    ret =(int) number;
    return ret;
}

static long jalali_jdn(int year, int month, int day)
{
    const long PERSIAN_EPOCH = 1948321; /* The JDN of 1 Farvardin 1*/
    int epbase;
    long epyear;
    long mdays;
    long jdn;
    epbase = year - 474;
    epyear = 474 + (epbase % 2820);
    if (month <= 7)
        mdays = (month - 1) * 31;
    else
        mdays = (month - 1) * 30 + 6;
    jdn = day + mdays ;
    jdn += (((epyear * 682) - 110) / 2816) ;
    jdn	+= (epyear - 1) * 365;
    jdn += (epbase / 2820) * 1029983 ;
    jdn += (PERSIAN_EPOCH - 1);
    return jdn;
}


static SDATE jdn_jalali(long jdn)
{
    static SDATE ret;
    int day, month, year;
    int iYear, iMonth, iDay;
    int depoch;
    int cycle;
    int cyear;
    int ycycle;
    int aux1, aux2;
    int yday;
    day = 1;
    month = 1;
    year = 475;
    depoch = jdn - jalali_jdn(year,month, day);
    cycle = (int) (depoch / 1029983);
    cyear = depoch % 1029983;
    if( cyear == 1029982)
        ycycle = 2820;
    else{
        aux1 = cyear / 366;
        aux2 = cyear % 366;
        ycycle = (((2134 * aux1) + (2816 * aux2) + 2815) / 1028522) + aux1 + 1;
    }
    iYear = ycycle + (2820 * cycle) + 474;
    if (iYear <= 0)
        iYear = iYear - 1;
    year = iYear;
    yday = (jdn - jalali_jdn(year, month, day)) + 1;
    if(yday <= 186 )
        iMonth = Ceil((yday-1) / 31);
    else
        iMonth = Ceil((yday - 7) / 30);
    iMonth++;
    month = iMonth;
    iDay = (jdn - jalali_jdn(year, month, day)) + 1;
    ret.day = iDay;
    ret.mon = iMonth;
    ret.year = iYear;
    return ret;
}



static long civil_jdn(int year, int month, int day)
{
    long jdn = ((1461 * (year + 4800 + ((month - 14) / 12))) / 4)
	+ ((367 * (month - 2 - 12 * (((month - 14) / 12)))) / 12)
	- ((3 * (((year + 4900 + ((month - 14) / 12)) / 100))) / 4)
	+ day - 32075;
    return jdn;
}

static SDATE jdn_civil(long jdn)
{
    long l, n, i, j;
    static SDATE ret;
    int iday, imonth, iyear;
    l = jdn + 68569;
    n = ((4 * l) / 146097);
    l = l - ((146097 * n + 3) / 4);
    i = ((4000 * (l + 1)) / 1461001);
    l = l - ((1461 * i) / 4) + 31;
    j = ((80 * l) / 2447);
    iday = l - ((2447 * j) / 80);
    l = (j / 11);
    imonth = j + 2 - 12 * l;
    iyear = 100 * (n - 49) + i + l;
    ret.day = iday;
    ret.mon = imonth;
    ret.year = iyear;
    return (ret);
}

static SDATE *jalaliToGregorian(int y,int m,int d)
{
static SDATE sd;
long jday = jalali_jdn(y,m,d);
sd= jdn_civil(jday);
return (&sd);
}
static SDATE *gregorianToJalali(int y,int m, int d)
{
    static SDATE sd;
    long   jdn = civil_jdn(y,m,d);//TQDate::gregorianToJulian(y, m, d);
    sd = jdn_jalali(jdn);
    return(&sd);
}
static void gregorianToJalali(const TQDate & date, int * pYear, int * pMonth,
                               int * pDay)
{
  SDATE *sd;
  sd = gregorianToJalali(date.year(), date.month(), date.day());
  if (pYear)
    *pYear = sd->year;
  if (pMonth)
    *pMonth = sd->mon;
  if (pDay)
    *pDay = sd->day;

}

// End of converting functions

static int isJalaliLeap(int year)
{
 int     tmp;
 tmp = year % 33;
 if (tmp == 1 || tmp == 5||tmp==9||tmp==13||tmp==17||tmp==22||tmp==26||tmp==30)
     return 1;
else
     return 0;
}
static int hndays(int m,int y)
{
  return jMonthDay[isJalaliLeap(y)][m];
}


KCalendarSystemJalali::KCalendarSystemJalali(const TDELocale * locale)
  : KCalendarSystem(locale)
{
}

KCalendarSystemJalali::~KCalendarSystemJalali()
{
}

int KCalendarSystemJalali::year(const TQDate& date) const

{
  kdDebug(5400) << "Jalali year..." <<  endl;
int y;
  gregorianToJalali(date, &y, 0, 0);
  return y;
}

int KCalendarSystemJalali::month (const TQDate& date) const

{
  kdDebug(5400) << "Jalali month..." <<  endl;
int m;
  gregorianToJalali(date, 0 , &m, 0);
  return m;
}

int KCalendarSystemJalali::day(const TQDate& date) const

{
  kdDebug(5400) << "Jalali day..." <<  endl;
int d;
  gregorianToJalali(date, 0, 0, &d);
  return d;
}

int KCalendarSystemJalali::dayOfWeek(const TQDate& date) const
{
//same same I think?!
  return date.dayOfWeek();

}

//NOT TESTED YET
int KCalendarSystemJalali::dayOfYear(const TQDate & date) const
{
  TQDate first;
  setYMD(first, year(date), 1, 1);

  return first.daysTo(date) + 1;
}

//MAY BE BUGGY
bool KCalendarSystemJalali::setYMD(TQDate & date, int y, int m, int d) const
{
  // range checks
  if ( y < minValidYear() || y > maxValidYear() )
    return false;

  if ( m < 1 || m > 12 )
    return false;

  if ( d < 1 || d > hndays(m, y) )
    return false;

  SDATE  *gd =jalaliToGregorian( y, m, d);

  return date.setYMD(gd->year, gd->mon, gd->day);
}

TQDate KCalendarSystemJalali::addYears( const TQDate & date, int nyears ) const
{
  TQDate result = date;
  int y = year(date) + nyears;
  setYMD( result, y, month(date), day(date) );

  return result;
}

TQDate KCalendarSystemJalali::addMonths( const TQDate & date, int nmonths ) const
{
  TQDate result = date;
  int m = month(date);
  int y = year(date);

  if ( nmonths < 0 )
  {
    m += 12;
    y -= 1;
  }

  --m; // this only works if we start counting at zero
  m += nmonths;
  y += m / 12;
  m %= 12;
  ++m;

  setYMD( result, y, m, day(date) );

  return result;
}

TQDate KCalendarSystemJalali::addDays( const TQDate & date, int ndays ) const
{
  return TQT_TQDATE_OBJECT(date.addDays( ndays ));
}

int KCalendarSystemJalali::monthsInYear( const TQDate & date ) const
{
  Q_UNUSED( date )

  return 12;
}

int KCalendarSystemJalali::daysInYear(const TQDate & date) const
{
Q_UNUSED(date);
int result;
//SDATE *sd = gregorianToJalali(year(date),month(date),day(date));
//if (isJalaliLeap(sd->year))
	result=366;
//else
//	result=365;
return result;
}

int KCalendarSystemJalali::daysInMonth(const TQDate & date) const
{
SDATE *sd = gregorianToJalali(date.year(),date.month(),date.day());
return hndays(sd->mon,sd->year);
}

int KCalendarSystemJalali::weeksInYear(int year) const

{
  Q_UNUSED(year);
// couldn't understand it!
return 52;
}

int KCalendarSystemJalali::weekNumber(const TQDate& date, int * yearNum) const
{
  TQDate firstDayWeek1, lastDayOfYear;
  int y = year(date);
  int week;
  int weekDay1, dayOfWeek1InYear;

  // let's guess 1st day of 1st week
  setYMD(firstDayWeek1, y, 1, 1);
  weekDay1 = dayOfWeek(firstDayWeek1);

  // iso 8601: week 1  is the first containing thursday and week starts on
  // monday
  if (weekDay1 > 4 /*Thursday*/)
    firstDayWeek1 = addDays(firstDayWeek1 , 7 - weekDay1 + 1); // next monday

  dayOfWeek1InYear = dayOfYear(firstDayWeek1);

  if ( dayOfYear(date) < dayOfWeek1InYear ) // our date in prev year's week
  {
    if ( yearNum )
      *yearNum = y - 1;
    return weeksInYear(y - 1);
  }
 // let' check if its last week belongs to next year
  setYMD(lastDayOfYear, y, 12, hndays(12, y));
  if ( (dayOfYear(date) >= daysInYear(date) - dayOfWeek(lastDayOfYear) + 1)
       // our date is in last week
       && dayOfWeek(lastDayOfYear) < 4) // 1st week in next year has thursday
    {
      if ( yearNum )
        *yearNum = y + 1;
      week = 1;
    }
  else
    week = firstDayWeek1.daysTo(date) / 7 + 1;

  return week;
}

TQString KCalendarSystemJalali::monthName(int month, int year, bool shortName)
  const
{
  Q_UNUSED(year);

  if (shortName)
    switch ( month )
      {
      case 1:
        return locale()->translate("Far");
      case 2:
        return locale()->translate("Ord");
      case 3:
        return locale()->translate("Kho");
      case 4:
        return locale()->translate("Tir");
      case 5:
        return locale()->translate("Mor");
      case 6:
        return locale()->translate("Sha");
      case 7:
        return locale()->translate("Meh");
      case 8:
        return locale()->translate("Aba");
      case 9:
        return locale()->translate("Aza");
      case 10:
        return locale()->translate("Dei");
      case 11:
        return locale()->translate("Bah");
      case 12:
        return locale()->translate("Esf");
    }
  else
    switch ( month )
      {
      case 1:
        return locale()->translate("Farvardin");
      case 2:
        return locale()->translate("Ordibehesht");
      case 3:
        return locale()->translate("Khordad");
      case 4:
        return locale()->translate("Tir");
      case 5:
        return locale()->translate("Mordad");
      case 6:
        return locale()->translate("Shahrivar");
      case 7:
        return locale()->translate("Mehr");
      case 8:
        return locale()->translate("Aban");
      case 9:
        return locale()->translate("Azar");
      case 10:
      	return locale()->translate("Dei");
      case 11:
        return locale()->translate("Bahman");
      case 12:
        return locale()->translate("Esfand");
      }

  return TQString::null;
}

TQString KCalendarSystemJalali::monthName(const TQDate& date, bool shortName)
  const
{
  int mon;
  gregorianToJalali(date,0,&mon,0);
  //SDATE *sd = gregorianToJalali(date.year(),date.month(),date.day());
  return (monthName(mon, 0, shortName));
}

TQString KCalendarSystemJalali::monthNamePossessive(const TQDate& date,
                                                     bool shortName  ) const
{
  return monthName(date,shortName);
}

TQString KCalendarSystemJalali::monthNamePossessive(int month, int year,
                                                     bool shortName ) const
{
  return monthName(month,year,shortName);
}


TQString KCalendarSystemJalali::weekDayName(int day, bool shortName) const
{
  if ( shortName )
    switch (day)
      {
      case 1:
        return locale()->translate("2sh");
      case 2:
        return locale()->translate("3sh");
      case 3:
        return locale()->translate("4sh");
      case 4:
        return locale()->translate("5sh");
      case 5:
        return locale()->translate("Jom");
      case 6:
        return locale()->translate("shn");
      case 7:
        return locale()->translate("1sh");
      }
  else
    switch ( day )
      {
      case 1:
        return locale()->translate("Do shanbe");
      case 2:
        return locale()->translate("Se shanbe");
      case 3:
        return locale()->translate("Chahar shanbe");
      case 4:
        return locale()->translate("Panj shanbe");
      case 5:
        return locale()->translate("Jumee");
      case 6:
        return locale()->translate("Shanbe");
      case 7:
        return locale()->translate("Yek-shanbe");
      }

  return TQString::null;
}

TQString KCalendarSystemJalali::weekDayName(const TQDate &date,bool shortName)
  const
{
 return weekDayName(dayOfWeek(date), shortName);
}

// Min valid year that may be converted to QDate
int KCalendarSystemJalali::minValidYear() const
{
  TQDate date(1753, 1, 1);

  return year(date);
}

// Max valid year that may be converted to QDate
int KCalendarSystemJalali::maxValidYear() const
{
/*
  TQDate date(8000, 1, 1);

  SDATE *sd = toJalali(date);

  return sd->year;
  */
  return 10000;
}
int KCalendarSystemJalali::weekDayOfPray() const
{
  return 5; // friday
}
TQString KCalendarSystemJalali::calendarName() const
{
  return TQString::fromLatin1("jalali");
}

bool KCalendarSystemJalali::isLunar() const
{
  return false;
}

bool KCalendarSystemJalali::isLunisolar() const
{
  return false;
}

bool KCalendarSystemJalali::isSolar() const
{
  return true;
}
