//
// Copyright (c) 2009-2011 Artyom Beilis (Tonkikh)
//
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#ifndef BOOST_LOCALE_IMPL_WIN32_API_HPP
#define BOOST_LOCALE_IMPL_WIN32_API_HPP

#include <string>
#include <vector>
#include <sstream>
#include <iomanip>
#include <limits>
#include <ctime>
#include <stdexcept>

#include "boost/locale/win32/lcid.hpp"

#ifndef NOMINMAX
#define NOMINMAX
#endif
#ifndef UNICODE
#define UNICODE
#endif
#include <windows.h>

#include <boost/locale/conversion.hpp>
#include <boost/locale/collator.hpp>

#define BOOST_LOCALE_WINDOWS_2000_API

#if defined(_WIN32_NT) && _WIN32_NT >= 0x600 && !defined(BOOST_LOCALE_WINDOWS_2000_API)
#define BOOST_LOCALE_WINDOWS_VISTA_API
#else
#define BOOST_LOCALE_WINDOWS_2000_API
#endif

namespace boost {
namespace locale {
namespace impl_win {

    struct numeric_info {
        std::wstring thousands_sep;
        std::wstring decimal_point;
        std::string grouping;
    };

    inline DWORD collation_level_to_flag(collator_base::level_type level)
    {
        DWORD flags;
        switch(level) {
        case collator_base::primary:
            flags = NORM_IGNORESYMBOLS | NORM_IGNORECASE | NORM_IGNORENONSPACE;
            break;
        case collator_base::secondary:
            flags = NORM_IGNORESYMBOLS | NORM_IGNORECASE;
            break;
        case collator_base::tertiary:
            flags = NORM_IGNORESYMBOLS;
            break;
        default:
            flags = 0;
        }
        return flags;
    }



    #ifdef BOOST_LOCALE_WINDOWS_2000_API

    class winlocale{
    public:
        winlocale() :
            lcid(0)
        {
        }

        winlocale(std::string const &name)
        {
            lcid = locale_to_lcid(name);
        }

        unsigned lcid;

        bool is_c() const
        {
            return lcid == 0;
        }
    };


    ////////////////////////////////////////////////////////////////////////
    ///
    /// Number Format
    ///
    ////////////////////////////////////////////////////////////////////////

    inline numeric_info wcsnumformat_l(winlocale const &l)
    {
        numeric_info res;
        res.decimal_point = L'.';
        unsigned lcid = l.lcid;

        if(lcid == 0)
            return res;

        // limits according to MSDN
        static const int th_size = 4;
        static const int de_size = 4;
        static const int gr_size = 10;

        wchar_t th[th_size]={0};
        wchar_t de[de_size]={0};
        wchar_t gr[gr_size]={0};

        if( GetLocaleInfoW(lcid,LOCALE_STHOUSAND,th,th_size)==0
            || GetLocaleInfoW(lcid,LOCALE_SDECIMAL ,de,de_size)==0
            || GetLocaleInfoW(lcid,LOCALE_SGROUPING,gr,gr_size)==0)
        {
            return res;
        }
        res.decimal_point = de;
        res.thousands_sep = th;
        bool inf_group = false;
        for(unsigned i=0;gr[i];i++) {
            if(gr[i]==L';')
                continue;
            if(L'1'<= gr[i] && gr[i]<=L'9') {
                res.grouping += char(gr[i]-L'0');
            }
            else if(gr[i]==L'0')
                inf_group = true;
        }
        if(!inf_group) {
BOOST_LOCALE_START_CONST_CONDITION
            if(std::numeric_limits<char>::is_signed) {
                res.grouping+=std::numeric_limits<char>::min();
            }
            else {
                res.grouping+=std::numeric_limits<char>::max();
            }
BOOST_LOCALE_END_CONST_CONDITION
        }
        return res;
    }

    inline std::wstring win_map_string_l(unsigned flags,wchar_t const *begin,wchar_t const *end,winlocale const &l)
    {
        std::wstring res;
        if(end - begin > std::numeric_limits<int>::max())
            throw std::length_error("String to long for int type");
        int len = LCMapStringW(l.lcid,flags,begin, static_cast<int>(end-begin),0,0);
        if(len == 0)
            return res;
        if(len == std::numeric_limits<int>::max())
            throw std::length_error("String to long for int type");
        std::vector<wchar_t> buf(len+1);
        int l2 = LCMapStringW(l.lcid,flags,begin,static_cast<int>(end-begin),&buf.front(),static_cast<int>(buf.size()));
        res.assign(&buf.front(),l2);
        return res;
    }

    ////////////////////////////////////////////////////////////////////////
    ///
    /// Collation
    ///
    ////////////////////////////////////////////////////////////////////////


    inline int wcscoll_l(   collator_base::level_type level,
                            wchar_t const *lb,wchar_t const *le,
                            wchar_t const *rb,wchar_t const *re,
                            winlocale const &l)
    {
        if(le - lb > std::numeric_limits<int>::max() || re - rb > std::numeric_limits<int>::max())
            throw std::length_error("String to long for int type");
        return CompareStringW(l.lcid,collation_level_to_flag(level),lb,static_cast<int>(le-lb),rb,static_cast<int>(re-rb)) - 2;
    }


    ////////////////////////////////////////////////////////////////////////
    ///
    /// Money Format
    ///
    ////////////////////////////////////////////////////////////////////////

    inline std::wstring wcsfmon_l(double value,winlocale const &l)
    {
        std::wostringstream ss;
        ss.imbue(std::locale::classic());

        ss << std::setprecision(std::numeric_limits<double>::digits10+1) << value;
        std::wstring sval = ss.str();
        int len = GetCurrencyFormatW(l.lcid,0,sval.c_str(),0,0,0);
        std::vector<wchar_t> buf(len+1);
        GetCurrencyFormatW(l.lcid,0,sval.c_str(),0,&buf.front(),len);
        return &buf.front();
    }

    ////////////////////////////////////////////////////////////////////////
    ///
    /// Time Format
    ///
    ////////////////////////////////////////////////////////////////////////


    inline std::wstring wcs_format_date_l(wchar_t const *format,SYSTEMTIME const *tm,winlocale const &l)
    {
        int len = GetDateFormatW(l.lcid,0,tm,format,0,0);
        std::vector<wchar_t> buf(len+1);
        GetDateFormatW(l.lcid,0,tm,format,&buf.front(),len);
        return &buf.front();
    }

    inline std::wstring wcs_format_time_l(wchar_t const *format,SYSTEMTIME const *tm,winlocale const &l)
    {
        int len = GetTimeFormatW(l.lcid,0,tm,format,0,0);
        std::vector<wchar_t> buf(len+1);
        GetTimeFormatW(l.lcid,0,tm,format,&buf.front(),len);
        return &buf.front();
    }

    inline std::wstring wcsfold(wchar_t const *begin,wchar_t const *end)
    {
        winlocale l;
        l.lcid = 0x0409; // en-US
        return win_map_string_l(LCMAP_LOWERCASE,begin,end,l);
    }

    inline std::wstring wcsnormalize(norm_type norm,wchar_t const *begin,wchar_t const *end)
    {
        // We use FoldString, under Vista it actually does normalization;
        // under XP and below it does something similar, half job, better then nothing
        unsigned flags = 0;
        switch(norm) {
        case norm_nfd:
            flags = MAP_COMPOSITE;
            break;
        case norm_nfc:
            flags = MAP_PRECOMPOSED;
            break;
        case norm_nfkd:
            flags = MAP_FOLDCZONE;
            break;
        case norm_nfkc:
            flags = MAP_FOLDCZONE | MAP_COMPOSITE;
            break;
        default:
            flags = MAP_PRECOMPOSED;
        }

        if(end - begin > std::numeric_limits<int>::max())
            throw std::length_error("String to long for int type");
        int len = FoldStringW(flags,begin,static_cast<int>(end-begin),0,0);
        if(len == 0)
            return std::wstring();
        if(len == std::numeric_limits<int>::max())
            throw std::length_error("String to long for int type");
        std::vector<wchar_t> v(len+1);
        len = FoldStringW(flags,begin,static_cast<int>(end-begin),&v.front(),len+1);
        return std::wstring(&v.front(),len);
    }


    #endif

    inline std::wstring wcsxfrm_l(collator_base::level_type level,wchar_t const *begin,wchar_t const *end,winlocale const &l)
    {
        int flag = LCMAP_SORTKEY | collation_level_to_flag(level);

        return win_map_string_l(flag,begin,end,l);
    }

    inline std::wstring towupper_l(wchar_t const *begin,wchar_t const *end,winlocale const &l)
    {
        return win_map_string_l(LCMAP_UPPERCASE | LCMAP_LINGUISTIC_CASING,begin,end,l);
    }

    inline std::wstring towlower_l(wchar_t const *begin,wchar_t const *end,winlocale const &l)
    {
        return win_map_string_l(LCMAP_LOWERCASE | LCMAP_LINGUISTIC_CASING,begin,end,l);
    }

    inline std::wstring wcsftime_l(char c,std::tm const *tm,winlocale const &l)
    {
        SYSTEMTIME wtm=SYSTEMTIME();
        wtm.wYear = static_cast<WORD>(tm->tm_year + 1900);
        wtm.wMonth = static_cast<WORD>(tm->tm_mon+1);
        wtm.wDayOfWeek = static_cast<WORD>(tm->tm_wday);
        wtm.wDay = static_cast<WORD>(tm->tm_mday);
        wtm.wHour = static_cast<WORD>(tm->tm_hour);
        wtm.wMinute = static_cast<WORD>(tm->tm_min);
        wtm.wSecond = static_cast<WORD>(tm->tm_sec);
        switch(c) {
        case 'a': // Abbr Weekday
            return wcs_format_date_l(L"ddd",&wtm,l);
        case 'A': // Full Weekday
            return wcs_format_date_l(L"dddd",&wtm,l);
        case 'b': // Abbr Month
            return wcs_format_date_l(L"MMM",&wtm,l);
        case 'B': // Full Month
            return wcs_format_date_l(L"MMMM",&wtm,l);
        case 'c': // DateTile Full
            return wcs_format_date_l(0,&wtm,l) + L" " + wcs_format_time_l(0,&wtm,l);
        // not supported by WIN ;(
        //  case 'C': // Century -> 1980 -> 19
        //  retur
        case 'd': // Day of Month [01,31]
            return wcs_format_date_l(L"dd",&wtm,l);
        case 'D': // %m/%d/%y
            return wcs_format_date_l(L"MM/dd/yy",&wtm,l);
        case 'e': // Day of Month [1,31]
            return wcs_format_date_l(L"d",&wtm,l);
        case 'h': // == b
            return wcs_format_date_l(L"MMM",&wtm,l);
        case 'H': // 24 clock hour 00,23
            return wcs_format_time_l(L"HH",&wtm,l);
        case 'I': // 12 clock hour 01,12
            return wcs_format_time_l(L"hh",&wtm,l);
        /*
        case 'j': // day of year 001,366
            return "D";*/
        case 'm': // month as [01,12]
            return wcs_format_date_l(L"MM",&wtm,l);
        case 'M': // minute [00,59]
            return wcs_format_time_l(L"mm",&wtm,l);
        case 'n': // \n
            return L"\n";
        case 'p': // am-pm
            return wcs_format_time_l(L"tt",&wtm,l);
        case 'r': // time with AM/PM %I:%M:%S %p
            return wcs_format_time_l(L"hh:mm:ss tt",&wtm,l);
        case 'R': // %H:%M
            return wcs_format_time_l(L"HH:mm",&wtm,l);
        case 'S': // second [00,61]
            return wcs_format_time_l(L"ss",&wtm,l);
        case 't': // \t
            return L"\t";
        case 'T': // %H:%M:%S
            return wcs_format_time_l(L"HH:mm:ss",&wtm,l);
/*          case 'u': // weekday 1,7 1=Monday
        case 'U': // week number of year [00,53] Sunday first
        case 'V': // week number of year [01,53] Monday first
        case 'w': // weekday 0,7 0=Sunday
        case 'W': // week number of year [00,53] Monday first, */
        case 'x': // Date
            return wcs_format_date_l(0,&wtm,l);
        case 'X': // Time
            return wcs_format_time_l(0,&wtm,l);
        case 'y': // Year [00-99]
            return wcs_format_date_l(L"yy",&wtm,l);
        case 'Y': // Year 1998
            return wcs_format_date_l(L"yyyy",&wtm,l);
        case '%': // %
            return L"%";
        default:
            return L"";
        }
    }



} // win
} // locale
} // boost
#endif

// boostinspect:nominmax
