//
//  Copyright (c) 2009-2011 Artyom Beilis (Tonkikh)
//
//  Distributed under the Boost Software License, Version 1.0. (See
//  accompanying file LICENSE_1_0.txt or copy at
//  http://www.boost.org/LICENSE_1_0.txt)
//
#define BOOSTER_SOURCE
#include <booster/locale/date_time.h>
#include <booster/locale/formatting.h>
#include <booster/thread.h>
#include <math.h>

namespace booster {
namespace locale {

using namespace period;

/////////////////////////
// Calendar
////////////////////////

calendar::calendar(std::locale const &l,std::string const &zone) :
    locale_(l),
    tz_(zone),
    impl_(std::use_facet<calendar_facet>(l).create_calendar())
{
    impl_->set_timezone(tz_);
}

calendar::calendar(std::string const &zone) :
    tz_(zone),
    impl_(std::use_facet<calendar_facet>(std::locale()).create_calendar())
{
    impl_->set_timezone(tz_);
}


calendar::calendar(std::locale const &l) :
    locale_(l),
    tz_(time_zone::global()),
    impl_(std::use_facet<calendar_facet>(l).create_calendar())
{
    impl_->set_timezone(tz_);
}

calendar::calendar(std::ios_base &ios) :
    locale_(ios.getloc()),
    tz_(ios_info::get(ios).time_zone()),
    impl_(std::use_facet<calendar_facet>(locale_).create_calendar())
{
    impl_->set_timezone(tz_);
    
}

calendar::calendar() :
    tz_(time_zone::global()),
    impl_(std::use_facet<calendar_facet>(std::locale()).create_calendar())
{
    impl_->set_timezone(tz_);
}

calendar::~calendar()
{
}

calendar::calendar(calendar const &other) :
    locale_(other.locale_),
    tz_(other.tz_),
    impl_(other.impl_->clone())
{
}

calendar const &calendar::operator = (calendar const &other) 
{
    if(this !=&other) {
        impl_.reset(other.impl_->clone());
        locale_ = other.locale_;
        tz_ = other.tz_;
    }
    return *this;
}


bool calendar::is_gregorian() const
{
    return impl_->get_option(abstract_calendar::is_gregorian)!=0;
}

std::string calendar::get_time_zone() const
{
    return tz_;
}

std::locale calendar::get_locale() const
{
    return locale_;
}

int calendar::minimum(period_type f) const
{
    return impl_->get_value(f.mark(),abstract_calendar::absolute_minimum);
}

int calendar::greatest_minimum(period_type f) const
{
    return impl_->get_value(f.mark(),abstract_calendar::greatest_minimum);
}

int calendar::maximum(period_type f) const
{
    return impl_->get_value(f.mark(),abstract_calendar::absolute_maximum);
}

int calendar::least_maximum(period_type f) const
{
    return impl_->get_value(f.mark(),abstract_calendar::least_maximum);
}

int calendar::first_day_of_week() const
{
    return impl_->get_value(period::marks::first_day_of_week,abstract_calendar::current);
}

bool calendar::operator==(calendar const &other) const
{
    return impl_->same(other.impl_.get());
}

bool calendar::operator!=(calendar const &other) const
{
    return !(*this==other);
}

//////////////////////////////////
// date_time
/////////////////

date_time::date_time() :
    impl_(std::use_facet<calendar_facet>(std::locale()).create_calendar())
{
    impl_->set_timezone(time_zone::global());
}

date_time::date_time(date_time const &other)
{
    impl_.reset(other.impl_->clone());
}

date_time::date_time(date_time const &other,date_time_period_set const &s)
{
    impl_.reset(other.impl_->clone());
    for(unsigned i=0;i<s.size();i++) {
        impl_->set_value(s[i].type.mark(),s[i].value);
    }
    impl_->normalize();
}

date_time const &date_time::operator = (date_time const &other)
{
    if(this != &other) {
        date_time tmp(other);
        swap(tmp);
    }
    return *this;
}

date_time::~date_time()
{
}

date_time::date_time(double t) :
    impl_(std::use_facet<calendar_facet>(std::locale()).create_calendar())
{
    impl_->set_timezone(time_zone::global());
    time(t);
}

date_time::date_time(double t,calendar const &cal) :
    impl_(cal.impl_->clone())
{
    time(t);
}

date_time::date_time(calendar const &cal) :
    impl_(cal.impl_->clone())
{
}



date_time::date_time(date_time_period_set const &s) :
    impl_(std::use_facet<calendar_facet>(std::locale()).create_calendar())
{
    impl_->set_timezone(time_zone::global());
    for(unsigned i=0;i<s.size();i++) {
        impl_->set_value(s[i].type.mark(),s[i].value);
    }
    impl_->normalize();
}
date_time::date_time(date_time_period_set const &s,calendar const &cal) :
    impl_(cal.impl_->clone())
{
    for(unsigned i=0;i<s.size();i++) {
        impl_->set_value(s[i].type.mark(),s[i].value);
    }
    impl_->normalize();
}

date_time const &date_time::operator=(date_time_period_set const &s)
{
    for(unsigned i=0;i<s.size();i++)
        impl_->set_value(s[i].type.mark(),s[i].value);
    impl_->normalize();
    return *this;
}

void date_time::set(period_type f,int v)
{
    impl_->set_value(f.mark(),v);
    impl_->normalize();
}

int date_time::get(period_type f) const
{
    return impl_->get_value(f.mark(),abstract_calendar::current);
}

date_time date_time::operator+(date_time_period const &v) const
{
    date_time tmp(*this);
    tmp+=v;
    return tmp;
}

date_time date_time::operator-(date_time_period const &v) const
{
    date_time tmp(*this);
    tmp-=v;
    return tmp;
}

date_time date_time::operator<<(date_time_period const &v) const
{
    date_time tmp(*this);
    tmp<<=v;
    return tmp;
}

date_time date_time::operator>>(date_time_period const &v) const
{
    date_time tmp(*this);
    tmp>>=v;
    return tmp;
}

date_time const &date_time::operator+=(date_time_period const &v) 
{
    impl_->adjust_value(v.type.mark(),abstract_calendar::move,v.value);
    return *this;
}

date_time const &date_time::operator-=(date_time_period const &v) 
{
    impl_->adjust_value(v.type.mark(),abstract_calendar::move,-v.value);
    return *this;
}

date_time const &date_time::operator<<=(date_time_period const &v) 
{
    impl_->adjust_value(v.type.mark(),abstract_calendar::roll,v.value);
    return *this;
}

date_time const &date_time::operator>>=(date_time_period const &v) 
{
    impl_->adjust_value(v.type.mark(),abstract_calendar::roll,-v.value);
    return *this;
}


date_time date_time::operator+(date_time_period_set const &v) const
{
    date_time tmp(*this);
    tmp+=v;
    return tmp;
}

date_time date_time::operator-(date_time_period_set const &v) const
{
    date_time tmp(*this);
    tmp-=v;
    return tmp;
}

date_time date_time::operator<<(date_time_period_set const &v) const
{
    date_time tmp(*this);
    tmp<<=v;
    return tmp;
}

date_time date_time::operator>>(date_time_period_set const &v) const
{
    date_time tmp(*this);
    tmp>>=v;
    return tmp;
}

date_time const &date_time::operator+=(date_time_period_set const &v) 
{
    for(unsigned i=0;i<v.size();i++) {
        *this+=v[i];
    }
    return *this;
}

date_time const &date_time::operator-=(date_time_period_set const &v) 
{
    for(unsigned i=0;i<v.size();i++) {
        *this-=v[i];
    }
    return *this;
}

date_time const &date_time::operator<<=(date_time_period_set const &v) 
{
    for(unsigned i=0;i<v.size();i++) {
        *this<<=v[i];
    }
    return *this;
}

date_time const &date_time::operator>>=(date_time_period_set const &v) 
{
    for(unsigned i=0;i<v.size();i++) {
        *this>>=v[i];
    }
    return *this;
}

double date_time::time() const
{
    posix_time ptime = impl_->get_time();
    return double(ptime.seconds)+1e-9*ptime.nanoseconds;
}

void date_time::time(double v)
{
    double dseconds = floor(v);
    int64_t seconds = static_cast<int64_t>(dseconds);
    double fract = v - dseconds;
    int nano = static_cast<int>(fract * 1e9);
    if(nano < 0)
        nano = 0;
    else if(nano >999999999)
        nano = 999999999;
    posix_time ptime;
    ptime.seconds = seconds;
    ptime.nanoseconds = nano;
    impl_->set_time(ptime);
}

namespace {
    int compare(posix_time const &left,posix_time const &right)
    {
        if(left.seconds < right.seconds)
            return -1;
        if(left.seconds > right.seconds)
            return 1;
        if(left.nanoseconds < right.nanoseconds)
            return -1;
        if(left.nanoseconds > right.nanoseconds)
            return 1;
        return 0;
    }
}

bool date_time::operator==(date_time const &other) const
{
    return compare(impl_->get_time(),other.impl_->get_time()) == 0;
}

bool date_time::operator!=(date_time const &other) const
{
    return !(*this==other);
}

bool date_time::operator<(date_time const &other) const
{
    return compare(impl_->get_time(),other.impl_->get_time()) < 0;
}

bool date_time::operator>=(date_time const &other) const
{
    return !(*this<other);
}

bool date_time::operator>(date_time const &other) const
{
    return compare(impl_->get_time(),other.impl_->get_time()) > 0;
}

bool date_time::operator<=(date_time const &other) const
{
    return !(*this>other);
}

void date_time::swap(date_time &other)
{
    impl_.swap(other.impl_);
}

int date_time::difference(date_time const &other,period_type f) const
{
    return impl_->difference(other.impl_.get(),f.mark());
}

int date_time::maximum(period_type f) const
{
    return impl_->get_value(f.mark(),abstract_calendar::actual_maximum);
}

int date_time::minimum(period_type f) const
{
    return impl_->get_value(f.mark(),abstract_calendar::actual_minimum);
}

bool date_time::is_in_daylight_saving_time() const
{
    return impl_->get_option(abstract_calendar::is_dst)!=0;
}

namespace time_zone {
    booster::mutex &tz_mutex()
    {
        static booster::mutex m;
        return m;
    }
    std::string &tz_id()
    {
        static std::string id;
        return id;
    }
    std::string global()
    {
        booster::unique_lock<booster::mutex> lock(tz_mutex());
        std::string id = tz_id();
        return id;
    }
    std::string global(std::string const &new_id)
    {
        booster::unique_lock<booster::mutex> lock(tz_mutex());
        std::string id = tz_id();
        tz_id() = new_id;
        return id;
    }
}



} // locale
} // boost



// vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4

