/**
 * @file
 * $Id$
 * $Revision$
 * $Author$
 * $Date$
 *
 * This file is part of The iWear Framework.
 *
 * The iWear Framework 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 as in version 2 of the License.

 * 
 * The iWear Framework 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
 * The iWear Framework; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifndef __IWEAR_DATETIME_H
#define __IWEAR_DATETIME_H

#ifndef __IWEAR_UTILITY_H
#include <iwear/utility.h>
#endif

#ifndef __IWEAR_EXCEPTION_H
#include <iwear/exceptions.h>
#endif

#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE
#endif

#include <ctime>

namespace iwear
{
    enum weekday
    {
	sunday,
	monday,
	tuesday,
	wednesday,
	thursday,
	fryday,
	saturday
    };

    enum month
    {
	january,
	february,
	march,
	april,
	may,
	june,
	july,
	august,
	september,
	october,
	november,
	december
    };
/**
 * This class more or less acts as a kind of wrapper for the struct tm. But
 * since we use bitfields, the compiler will squeeze some bytes together so
 * this representation is somewhat smaller. This is at the cost of a few calls
 * to construct a struct tm on the stack when calling certain conversion
 * functions....
 */
class DateTime
{
private:
    float seconds;
    /**
     * For this we can work from 1900 to 2156 ... this can be changed then ;)
     */
    uint8_t year;
    unsigned int mday: 5;
    unsigned int hours: 5;
    unsigned int minutes: 6;
    unsigned int yday: 9;
    month mon: 4;
    weekday wday: 3;
    bool is_dst: 1;
protected:
    /**
     * Adjusts the weekday and day of year
     */
    void adjust_additional( void );
public:
    void check_correctness( void );
    /**
     * Default constructor sets the time to NOW
     */
    DateTime();
    // Let the compiler do the copy constructor and assignment operator

    /**
     * fill in the date from a time_t
     */
    DateTime( const time_t& t );
    /**
     * Fill in the date from a double whith seconds since epoch
     */
    DateTime( const double& t );
    /**
     * Read a struct tm for the date
     */
    DateTime( const struct tm& t );

    /**
     * @{
     * @name Values you can get
     */
    inline uint32_t get_year( void ) const { return (1900+year); }
    inline month get_month( void ) const { return mon; }
    inline uint32_t get_day( void ) const { return mday; }
    inline uint32_t get_yday( void ) const { return yday; }
    inline uint32_t get_hour( void ) const { return hours; }
    inline uint32_t get_minute( void ) const { return minutes; }
    inline weekday get_weekday( void ) const { return wday; }
    inline float get_second( void ) const { return seconds; }

    time_t get_time_t( void ) const;
    double get_time_d( void ) const;
    struct tm get_time_tm( void ) const;
    string get_time_s( const char * fmt = NULL ) const;
    /**
     * @}
     */

    /**
     * @{
     * @name Values you can set. Theyre a few less than what you can get.
     */
    /**
     * set the hour, does not need to adjust weekday etc.
     */
    void set_hour( uint32_t h ) { hours = h; }
    /**
     * set the hour, does not need to adjust weekday etc.
     */
    void set_minute( uint32_t m ) { minutes = m; }
    /**
     * set the hour, does not need to adjust weekday etc.
     */
    void set_second( float s ) { seconds = s; }

    /**
     * Sets the year and adjusts weekday
     */
    void set_year( uint32_t y );

    /**
     * Sets the month and adjusts weekday
     */
    void set_month( month m );

    /**
     * Sets the day in the current month...
     */
    void set_day( uint32_t d );

    /**
     * Sets the time from a time_t
     */
    void set_time_t( time_t t );
    /**
     * Sets the time from a double holding seconds since epoch
     */
    void set_time_d( double t );
    /**
     * Sets the time from a struct tm
     */
    void set_time_tm( const struct tm& t );
    void set_time_s( const char * t, const char* fmt = NULL);
    void set_time_s( const string& t, const char * fmt = NULL);
    /**
     * @}
     */
};

inline void DateTime::set_time_s( const char * t, const char * fmt )/*{{{*/
{
    if( !fmt )
    {
	fmt = "%a %b %e %H:%M:%S %Y";
    }
    struct tm me;
    memset(&me,0,sizeof(me));
    strptime(t,fmt,&me);
    set_time_tm(me);
    check_correctness();
}/*}}}*/

inline void DateTime::check_correctness( void )/*{{{*/
{
    struct tm s = get_time_tm();
    time_t bf = get_time_t();
    adjust_additional();
    time_t af = get_time_t();
    if ( af != bf )
    {
	set_time_tm(s);
	THROW( invaliddate_error,(string("Date does not exist : ") + get_time_s()));
    }
}/*}}}*/

inline void DateTime::set_time_s( const string& t, const char * fmt)/*{{{*/
{
    set_time_s(t.c_str(),fmt);
}/*}}}*/

inline void DateTime::set_year( uint32_t y )/*{{{*/
{
    year = (y-1900);
    adjust_additional();
}/*}}}*/

inline void DateTime::set_day( uint32_t d )/*{{{*/
{
    if ( d > 31 ) THROW( invalidargument_error,("Month day > 31 ?", d));
    mday = d;
    adjust_additional();
}/*}}}*/

inline void DateTime::set_month( month m )/*{{{*/
{
    mon = m;
    adjust_additional();
}/*}}}*/

inline DateTime::DateTime()/*{{{*/
{
    struct timeval nowtv;
    gettimeofday(&nowtv,NULL);
    double nowd = nowtv.tv_sec;
    nowd += ((double)nowtv.tv_usec)/1000000.0;
    set_time_d(nowd);
}/*}}}*/

inline DateTime::DateTime( const time_t& t )/*{{{*/
{
    set_time_t(t);
}/*}}}*/

inline DateTime::DateTime( const double& t )/*{{{*/
{
    set_time_d(t);
}/*}}}*/

inline DateTime::DateTime( const struct tm& t )/*{{{*/
{
    set_time_tm(t);
}/*}}}*/

inline void DateTime::set_time_d( double t )/*{{{*/
{
    set_time_t(static_cast<time_t>(floor(t)));
    seconds += (t-floor(t));
}/*}}}*/

inline void DateTime::set_time_t( time_t t )/*{{{*/
{
    struct tm nowtm;
    gmtime_r(&t, &nowtm);
    set_time_tm(nowtm);
}/*}}}*/

inline void DateTime::set_time_tm( const struct tm& nowtm )/*{{{*/
{
    year = nowtm.tm_year;
    mday = nowtm.tm_mday;
    hours = nowtm.tm_hour;
    minutes = nowtm.tm_min;
    yday = nowtm.tm_year;
    mon = static_cast<iwear::month>(nowtm.tm_mon);
    wday = static_cast<iwear::weekday>(nowtm.tm_wday);
    is_dst = nowtm.tm_isdst;
    seconds = nowtm.tm_sec;
}/*}}}*/

inline struct tm DateTime::get_time_tm( void ) const/*{{{*/
{
    struct tm me;
    memset(&me,0,sizeof(me));
    me.tm_year =    year ;
    me.tm_mday =    mday ;
    me.tm_hour =    hours ;
    me.tm_min =     minutes ;
    me.tm_yday =    yday ;
    me.tm_mon =     mon ;
    me.tm_wday =    wday ;
    me.tm_isdst =   is_dst ;
    me.tm_sec =     static_cast<int>(floor(seconds));
    return me;
}/*}}}*/

inline time_t DateTime::get_time_t( void ) const/*{{{*/
{
    struct tm me = get_time_tm();
    time_t t;
    t = timegm(&me);
    return t;
}/*}}}*/

inline double DateTime::get_time_d( void ) const/*{{{*/
{
    time_t me = get_time_t();
//    cout << "time_t : " << me << endl;
    double t = me;
    t += (seconds - floor(seconds)); 
    return t;
}/*}}}*/

inline void DateTime::adjust_additional( void )/*{{{*/
{
    struct tm me;
    me = get_time_tm();
    time_t metime = timegm(&me);
    gmtime_r(&metime,&me);
    mon = static_cast<iwear::month>(me.tm_mon);
    wday = static_cast<iwear::weekday>(me.tm_wday);
    is_dst = me.tm_isdst;
}/*}}}*/

inline string DateTime::get_time_s( const char * fmt ) const/*{{{*/
{
    if ( ! fmt )
    {
	fmt = "%a %b %e %H:%M:%S %Y";
    }
    char buf[30];
    memset(buf,0,30);
    struct tm me;
//    memset(&me,0,sizeof(me));
    me = get_time_tm();
    strftime(buf,30,fmt,&me);

//    asctime_r(&me, buf);
//    buf[24] = '\0';
    return buf;
}/*}}}*/

inline std::ostream& operator<<( std::ostream& o, const DateTime d )/*{{{*/
{
    o << d.get_time_s("%c") << std::endl;
    return o;
}/*}}}*/

}

#endif
