//  Copyright (C) 2010, Vaclav Haisman. All rights reserved.
//  
//  Redistribution and use in source and binary forms, with or without modifica-
//  tion, are permitted provided that the following conditions are met:
//  
//  1. Redistributions of  source code must  retain the above copyright  notice,
//     this list of conditions and the following disclaimer.
//  
//  2. Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//  
//  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
//  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
//  FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
//  APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
//  INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
//  DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
//  OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
//  ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
//  (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
//  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "dcmtk/oflog/helpers/strhelp.h"
#include "dcmtk/oflog/streams.h"

#include <locale>
#include <iterator>
#include <algorithm>
#include <cstring>
#include <cwchar>
#include <cwctype>
#include <cctype>

#include <cassert>
#include "dcmtk/ofstd/ofvector.h"


namespace dcmtk
{
namespace log4cplus
{

namespace helpers
{

void clear_mbstate (mbstate_t &);

#ifdef DCMTK_LOG4CPLUS_WORKING_LOCALE

static
void
towstring_internal (STD_NAMESPACE wstring & outstr, const char * src, size_t size,
    STD_NAMESPACE locale const & loc)
{
    if (size == 0)
    {
        outstr.clear ();
        return;
    }

    typedef STD_NAMESPACE codecvt<wchar_t, char, mbstate_t> CodeCvt;
    const CodeCvt & cdcvt = STD_NAMESPACE use_facet<CodeCvt>(loc);
    mbstate_t state;
    clear_mbstate (state);

    char const * from_first = src;
    size_t const from_size = size;
    char const * const from_last = from_first + from_size;
    char const * from_next = from_first;

    OFVector<wchar_t> dest (from_size);

    wchar_t * to_first = &dest.front ();
    size_t to_size = dest.size ();
    wchar_t * to_last = to_first + to_size;
    wchar_t * to_next = to_first;

    CodeCvt::result result;
    size_t converted = 0;
    while (true)
    {
        result = cdcvt.in (
            state, from_first, from_last,
            from_next, to_first, to_last,
            to_next);
        // XXX: Even if only half of the input has been converted the
        // in() method returns CodeCvt::ok. I think it should return
        // CodeCvt::partial.
        if ((result == CodeCvt::partial || result == CodeCvt::ok)
            && from_next != from_last)
        {
            to_size = dest.size () * 2;
            dest.resize (to_size);
            converted = to_next - to_first;
            to_first = &dest.front ();
            to_last = to_first + to_size;
            to_next = to_first + converted;
            continue;
        }
        else if (result == CodeCvt::ok && from_next == from_last)
            break;
        else if (result == CodeCvt::error
            && to_next != to_last && from_next != from_last)
        {
            clear_mbstate (state);
            ++from_next;
            from_first = from_next;
            *to_next = L'?';
            ++to_next;
            to_first = to_next;
        }
        else
            break;
    }
    converted = to_next - &dest[0];

    outstr.assign (dest.begin (), dest.begin () + converted);
}


STD_NAMESPACE wstring 
towstring(const tstring& src)
{
    STD_NAMESPACE wstring ret;
    towstring_internal (ret, src.c_str (), src.size (), STD_NAMESPACE locale ());
    return ret;
}


STD_NAMESPACE wstring 
towstring(char const * src)
{
    STD_NAMESPACE wstring ret;
    towstring_internal (ret, src, STD_NAMESPACE strlen (src), STD_NAMESPACE locale ());
    return ret;
}


static
void
tostring_internal (tstring & outstr, const wchar_t * src, size_t size,
    STD_NAMESPACE locale const & loc)
{
    if (size == 0)
    {
        outstr.clear ();
        return;
    }

    typedef STD_NAMESPACE codecvt<wchar_t, char, mbstate_t> CodeCvt;
    const CodeCvt & cdcvt = STD_NAMESPACE use_facet<CodeCvt>(loc);
    mbstate_t state;
    clear_mbstate (state);

    wchar_t const * from_first = src;
    size_t const from_size = size;
    wchar_t const * const from_last = from_first + from_size;
    wchar_t const * from_next = from_first;

    OFVector<char> dest (from_size);

    char * to_first = &dest.front ();
    size_t to_size = dest.size ();
    char * to_last = to_first + to_size;
    char * to_next = to_first;

    CodeCvt::result result;
    size_t converted = 0;
    while (from_next != from_last)
    {
        result = cdcvt.out (
            state, from_first, from_last,
            from_next, to_first, to_last,
            to_next);
        // XXX: Even if only half of the input has been converted the
        // in() method returns CodeCvt::ok with VC8. I think it should
        // return CodeCvt::partial.
        if ((result == CodeCvt::partial || result == CodeCvt::ok)
            && from_next != from_last)
        {
            to_size = dest.size () * 2;
            dest.resize (to_size);
            converted = to_next - to_first;
            to_first = &dest.front ();
            to_last = to_first + to_size;
            to_next = to_first + converted;
        }
        else if (result == CodeCvt::ok && from_next == from_last)
            break;
        else if (result == CodeCvt::error
            && to_next != to_last && from_next != from_last)
        {
            clear_mbstate (state);
            ++from_next;
            from_first = from_next;
            *to_next = '?';
            ++to_next;
            to_first = to_next;
        }
        else
            break;
    }
    converted = to_next - &dest[0];

    outstr.assign (&*dest.begin (), converted);
}


tstring
tostring(const STD_NAMESPACE wstring& src)
{
    tstring ret;
    tostring_internal (ret, src.c_str (), src.size (), STD_NAMESPACE locale ());
    return ret;
}


tstring
tostring(wchar_t const * src)
{
    tstring ret;
    tostring_internal (ret, src, STD_NAMESPACE wcslen (src), STD_NAMESPACE locale ());
    return ret;
}


#endif // DCMTK_LOG4CPLUS_WORKING_LOCALE

} // namespace helpers

} // namespace log4cplus
} // end namespace dcmtk
