/*---------------------------------------------------------------------\
|                          ____ _   __ __ ___                          |
|                         |__  / \ / / . \ . \                         |
|                           / / \ V /|  _/  _/                         |
|                          / /__ | | | | | |                           |
|                         /_____||_| |_| |_|                           |
|                                                                      |
\---------------------------------------------------------------------*/
/** \file	zypp/Date.cc
 *
*/
#include <iostream>
//#include <zypp-core/base/Logger.h>

#include <zypp-core/base/String.h>
#include <zypp-core/base/Xml.h>

#include <zypp-core/Date.h>

using std::endl;

///////////////////////////////////////////////////////////////////
namespace zypp
{
  ///////////////////////////////////////////////////////////////////
  namespace
  {
    ///////////////////////////////////////////////////////////////////
    /// \class LocaleGuard
    /// \brief Temporarily adjust Locale
    /// \ingroup RAII
    struct LocaleGuard
    {
      LocaleGuard()
      {
        const char * tmp = ::setlocale( LC_TIME, NULL );
        _mylocale = tmp ? tmp : "";

        if ( _mylocale.find( "UTF-8" ) == std::string::npos
          && _mylocale.find( "utf-8" ) == std::string::npos
          && _mylocale != "POSIX"
          && _mylocale != "C"
          && _mylocale != "" )
        {
          // language[_territory][.codeset][@modifier]
          // add/exchange codeset with UTF-8
          std::string needLocale = ".UTF-8";
          std::string::size_type loc = _mylocale.find_first_of( ".@" );
          if ( loc != std::string::npos )
          {
            // prepend language[_territory]
            needLocale = _mylocale.substr( 0, loc ) + needLocale;
            loc = _mylocale.find_last_of( '@' );
            if ( loc != std::string::npos )
            {
              // append [@modifier]
              needLocale += _mylocale.substr( loc );
            }
          }
          else
          {
            // append ".UTF-8"
            needLocale = _mylocale + needLocale;
          }
          ::setlocale( LC_TIME, needLocale.c_str() );
        }
        else
        {
          // no need to change the locale
          _mylocale.clear();
        }
      }

      LocaleGuard(const LocaleGuard &) = delete;
      LocaleGuard(LocaleGuard &&) = delete;
      LocaleGuard &operator=(const LocaleGuard &) = delete;
      LocaleGuard &operator=(LocaleGuard &&) = delete;

      ~LocaleGuard() {
        if ( ! _mylocale.empty() )
          ::setlocale( LC_TIME, _mylocale.c_str() );
      }

    private:
      std::string _mylocale;
    };
    ///////////////////////////////////////////////////////////////////

    inline bool isDST( struct tm & tm )
    {
      time_t t = ::mktime( &tm );
      struct tm *tm2 = ::localtime( &t );
      return ( tm2 && tm2->tm_isdst > 0 );
    }

    inline const char * _dateFormat( Date::DateFormat dateFormat_r )
    {
      static const char * fmt[] = {
        "",		///< ""
        "%Y-%m-%d",	///< 2014-02-07
        "%Y-%m",	///< 2014-02
        "%Y",		///< 2014
        "%G-W%V",	///< 2014-W06
        "%G-W%V-%u",	///< 2014-W06-5 (1 is Monday)
        "%Y-%j",	///< 2014-038
      };
      return fmt[dateFormat_r.asIntegral()];
    }

    inline const char * _timeFormat( Date::TimeFormat timeFormat_r )
    {
      static const char * fmt[] = {
        "",		///< ""
        "%H:%M:%S",	///< 07:06:41
        "%H:%M",	///< 07:06
        "%H",		///< 07
      };
      return fmt[timeFormat_r.asIntegral()];
    }

    inline const char * _timeZoneFormat( Date::TimeZoneFormat timeZoneFormat_r )
    {
      static const char * fmt[] = {
        "",		///< ""
        " %Z",		///< UTC, CET, ...
        "%z",		///< +0000
      };
      return fmt[timeZoneFormat_r.asIntegral()];
    }

    inline std::string doForm( const std::string & format_r, Date::TimeBase base_r, const Date::ValueType & date_r )
    {
      if ( ! date_r )
        return "0";

      LocaleGuard guard;
      static char buf[512];
      if ( ! strftime( buf, 512, format_r.c_str(), (base_r == Date::TB_UTC ? gmtime : localtime)( &date_r ) ) )
        *buf = '\0';
      else
      {
        // strip a trailing '00' in a timeZoneFormat
        unsigned l = ::strlen( buf );
        if ( l >= 5
          && ( buf[l-1] == '0' )
          && ( buf[l-2] == '0' )
          && ( buf[l-5] == '+' || buf[l-5] == '-') )
          buf[l-2] = '\0';
      }
      return buf;
    }
  } // namespace
  ///////////////////////////////////////////////////////////////////

  const Date::ValueType Date::second;
  const Date::ValueType Date::minute;
  const Date::ValueType Date::hour;
  const Date::ValueType Date::day;
  const Date::ValueType Date::month28;
  const Date::ValueType Date::month29;
  const Date::ValueType Date::month30;
  const Date::ValueType Date::month31;
  const Date::ValueType Date::month;
  const Date::ValueType Date::year365;
  const Date::ValueType Date::year366;
  const Date::ValueType Date::year;

  Date::Date( const std::string & seconds_r )
  { str::strtonum( seconds_r, _date ); }

  Date::Date( const std::string & date_str, const std::string & format )
    : _date( Date( date_str, format, TB_LOCALTIME ) )
  {}

  Date::Date( const std::string & date_str, const std::string & format, Date::TimeBase base_r )
    : _date(0)
  {
    LocaleGuard guard;

    struct tm tm = {0,0,0,0,0,0,0,0,0,0,0};
    char * res = ::strptime( date_str.c_str(), format.c_str(), &tm );
    if (res == NULL)
      throw DateFormatException( str::form( "Invalid date format: '%s'", date_str.c_str() ) );

    if ( isDST(tm) )
      tm.tm_isdst = 1;
    _date = (base_r == TB_UTC ? ::timegm : ::timelocal)( &tm );
  }

  std::string Date::form( const std::string & format_r, Date::TimeBase base_r ) const
  { return doForm( format_r, base_r, _date ); }

  std::string Date::print( DateFormat dateFormat_r, TimeFormat timeFormat_r, TimeZoneFormat timeZoneFormat_r, TimeBase base_r ) const
  {
    str::Str str;
    if ( dateFormat_r != DateFormat::none )
      str << _dateFormat( dateFormat_r );
    if ( timeFormat_r != TimeFormat::none )
    {
      if ( dateFormat_r != DateFormat::none )
        str << ' ';
      str << _timeFormat( timeFormat_r );
      if ( timeZoneFormat_r != TimeZoneFormat::none )
        str << _timeZoneFormat( timeZoneFormat_r );
    }
    return doForm( str, base_r, _date );
  }

  std::string Date::printISO( DateFormat dateFormat_r, TimeFormat timeFormat_r, TimeZoneFormat timeZoneFormat_r, TimeBase base_r ) const
  {
    str::Str str;
    if ( dateFormat_r != DateFormat::none )
      str << _dateFormat( dateFormat_r );
    if ( timeFormat_r != TimeFormat::none )
    {
      if ( dateFormat_r != DateFormat::none )
        str << 'T';
      str << _timeFormat( timeFormat_r );
      switch ( timeZoneFormat_r.asEnum() )
      {
        case TimeZoneFormat::none:
          break;
        case TimeZoneFormat::name:
          if ( base_r == TB_UTC )
          {
            str << 'Z';
            break;
          }
          // else: FALLTHROUGH and print offset!
        case TimeZoneFormat::offset:
          str << _timeZoneFormat( TimeZoneFormat::offset );
          break;
      }
    }
    return doForm( str, base_r, _date );
  }

  std::ostream & dumpAsXmlOn( std::ostream & str, const Date & obj, const std::string & name_r )
  {
    return xmlout::node( str, name_r, {
      { "time_t",	Date::ValueType(obj) },
      { "text",	 	obj.printISO( Date::TB_UTC ) },
    } );
  }

} // namespace zypp
///////////////////////////////////////////////////////////////////
