// xlocbuf internal header

// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#ifndef _XLOCBUF_
#define _XLOCBUF_
#include <yvals_core.h>
#if _STL_COMPILER_PREPROCESSOR
#include <streambuf>

#pragma pack(push, _CRT_PACKING)
#pragma warning(push, _STL_WARNING_LEVEL)
#pragma warning(disable : _STL_DISABLED_WARNINGS)
_STL_DISABLE_CLANG_WARNINGS
#pragma push_macro("new")
#undef new

_STD_BEGIN
_STL_DISABLE_DEPRECATED_WARNING
_EXPORT_STD template <class _Codecvt, class _Elem = wchar_t, class _Traits = char_traits<_Elem>>
class _CXX17_DEPRECATE_CODECVT_HEADER wbuffer_convert
    : public basic_streambuf<_Elem, _Traits> { // stream buffer associated with a codecvt facet
private:
    enum _Mode { _Unused, _Wrote, _Need, _Got, _Eof };
    enum { _STRING_INC = 8 };

public:
    using _Mysb        = streambuf;
    using _Byte_traits = char_traits<char>;

    using int_type   = typename _Traits::int_type;
    using pos_type   = typename _Traits::pos_type;
    using off_type   = typename _Traits::off_type;
    using state_type = typename _Codecvt::state_type;

    wbuffer_convert() : _State(), _Pcvt(new _Codecvt), _Mystrbuf(nullptr), _Status(_Unused), _Nback(0) {
        // construct without buffer pointer
        _Loc = locale{_Loc, _Pcvt};
    }

    explicit wbuffer_convert(_Mysb* _Strbuf)
        : _State(), _Pcvt(new _Codecvt), _Mystrbuf(_Strbuf), _Status(_Unused), _Nback(0) {
        // construct with byte stream buffer pointer
        _Loc = locale{_Loc, _Pcvt};
    }

    wbuffer_convert(_Mysb* _Strbuf, const _Codecvt* _Pcvt_arg)
        : _State(), _Pcvt(_Pcvt_arg), _Mystrbuf(_Strbuf), _Status(_Unused), _Nback(0) {
        // construct with byte stream buffer pointer and codecvt
        _Loc = locale{_Loc, _Pcvt};
    }

    wbuffer_convert(_Mysb* _Strbuf, const _Codecvt* _Pcvt_arg, state_type _State_arg)
        : _State(_State_arg), _Pcvt(_Pcvt_arg), _Mystrbuf(_Strbuf), _Status(_Unused), _Nback(0) {
        // construct with byte stream buffer pointer, codecvt, and state
        _Loc = locale{_Loc, _Pcvt};
    }

    ~wbuffer_convert() noexcept override {
        while (_Status == _Wrote) { // put any trailing homing shift
            if (_Str.size() < _STRING_INC) {
                _Str.assign(_STRING_INC, '\0');
            }

            char* _Buf = &_Str[0];
            char* _Dest;
            switch (_Pcvt->unshift(_State, _Buf, _Buf + _Str.size(), _Dest)) { // test result of homing conversion
            case _Codecvt::ok:
                _Status = _Unused; // homed successfully
                _FALLTHROUGH;

            case _Codecvt::partial:
                { // put any generated bytes
                    ptrdiff_t _Count = _Dest - _Buf;
                    if (0 < _Count
                        && _Byte_traits::eq_int_type(
                            _Byte_traits::eof(), static_cast<_Byte_traits::int_type>(_Mystrbuf->sputn(_Buf, _Count)))) {
                        return; // write failed
                    }

                    if (_Status == _Wrote && _Count == 0) {
                        _Str.append(_STRING_INC, '\0'); // try with more space
                    }

                    break;
                }

            case _Codecvt::noconv:
                return; // nothing to do

            default:
                return; // conversion failed
            }
        }
    }

    _NODISCARD _Mysb* rdbuf() const {
        return _Mystrbuf;
    }

    _Mysb* rdbuf(_Mysb* _Strbuf) { // set byte stream buffer pointer
        _Mysb* _Oldstrbuf = _Mystrbuf;
        _Mystrbuf         = _Strbuf;
        return _Oldstrbuf;
    }

    _NODISCARD state_type state() const {
        return _State;
    }

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

protected:
    int_type overflow(int_type _Meta = _Traits::eof()) override { // put an element to stream
        if (_Traits::eq_int_type(_Traits::eof(), _Meta)) {
            return _Traits::not_eof(_Meta); // EOF, return success code
        } else if (!_Mystrbuf || 0 < _Nback || (_Status != _Unused && _Status != _Wrote)) {
            return _Traits::eof(); // no buffer or reading, fail
        } else { // put using codecvt facet
            const _Elem _Ch = _Traits::to_char_type(_Meta);

            if (_Str.size() < _STRING_INC) {
                _Str.assign(_STRING_INC, '\0');
            }

            for (_Status = _Wrote;;) {
                char* _Buf = &_Str[0];
                const _Elem* _Src;
                char* _Dest;

                // test result of converting one element
                switch (_Pcvt->out(_State, &_Ch, &_Ch + 1, _Src, _Buf, _Buf + _Str.size(), _Dest)) {
                case _Codecvt::partial:
                case _Codecvt::ok:
                    { // converted something, try to put it out
                        ptrdiff_t _Count = _Dest - _Buf;
                        if (0 < _Count
                            && _Byte_traits::eq_int_type(_Byte_traits::eof(),
                                static_cast<_Byte_traits::int_type>(_Mystrbuf->sputn(_Buf, _Count)))) {
                            return _Traits::eof(); // write failed
                        }

                        if (_Src != &_Ch) {
                            return _Meta; // converted whole element
                        }

                        if (0 >= _Count) {
                            if (_Str.size() >= 4 * _STRING_INC) {
                                return _Traits::eof(); // conversion failed
                            }

                            _Str.append(_STRING_INC, '\0'); // try with more space
                        }

                        break;
                    }

                case _Codecvt::noconv:
                    if (_Traits::eq_int_type(
                            _Traits::eof(), static_cast<int_type>(_Mystrbuf->sputn(reinterpret_cast<const char*>(&_Ch),
                                                static_cast<streamsize>(sizeof(_Elem)))))) {
                        return _Traits::eof();
                    }

                    return _Meta; // put native byte order

                default:
                    return _Traits::eof(); // conversion failed
                }
            }
        }
    }

    int_type pbackfail(int_type _Meta = _Traits::eof()) override { // put an element back to stream
        if (_STD size(_Myback) <= _Nback || _Status == _Wrote) {
            return _Traits::eof(); // nowhere to put back
        } else { // enough room, put it back
            if (!_Traits::eq_int_type(_Traits::eof(), _Meta)) {
                _Myback[_Nback] = _Traits::to_char_type(_Meta);
            }

            ++_Nback;
            if (_Status == _Unused) {
                _Status = _Got;
            }

            return _Meta;
        }
    }

    int_type underflow() override { // get an element from stream, but don't point past it
        int_type _Meta;

        if (0 >= _Nback) {
            if (_Traits::eq_int_type(_Traits::eof(), _Meta = _Get_elem())) {
                return _Meta; // _Get_elem failed, return EOF
            }

            _Myback[_Nback++] = _Traits::to_char_type(_Meta);
        }

        return _Traits::to_int_type(_Myback[_Nback - 1]);
    }

#pragma warning(push)
#pragma warning(disable : 6385) // Reading invalid data from 'this->_Myback':
                                // the readable size is 'X' bytes, but 'Y' bytes may be read.
    int_type uflow() override { // get an element from stream, point past it
        int_type _Meta;

        if (0 >= _Nback) {
            if (_Traits::eq_int_type(_Traits::eof(), _Meta = _Get_elem())) {
                return _Meta; // _Get_elem failed, return EOF
            }

            _Myback[_Nback++] = _Traits::to_char_type(_Meta);
        }

        return _Traits::to_int_type(_Myback[--_Nback]);
    }
#pragma warning(pop)

    pos_type seekoff(off_type, ios_base::seekdir,
        ios_base::openmode = static_cast<ios_base::openmode>(ios_base::in | ios_base::out)) override {
        return pos_type{off_type{-1}}; // always fail
    }

    pos_type seekpos(
        pos_type, ios_base::openmode = static_cast<ios_base::openmode>(ios_base::in | ios_base::out)) override {
        return pos_type{off_type{-1}}; // always fail
    }

private:
    int_type _Get_elem() { // compose an element from byte stream buffer
        if (_Mystrbuf && _Status != _Wrote) { // got buffer, haven't written, try to compose an element
            if (_Status != _Eof) {
                if (_Str.empty()) {
                    _Status = _Need;
                } else {
                    _Status = _Got;
                }
            }

            while (_Status != _Eof) { // get using codecvt facet
                char* _Buf = &_Str[0];
                _Elem _Ch;
                _Elem* _Dest;
                const char* _Src;
                int _Meta;

                if (_Status == _Need) {
                    if (_Byte_traits::eq_int_type(_Byte_traits::eof(), _Meta = _Mystrbuf->sbumpc())) {
                        _Status = _Eof;
                    } else {
                        _Str.push_back(_Byte_traits::to_char_type(_Meta));
                    }
                }

                // test result of converting one element
                switch (_Pcvt->in(_State, _Buf, _Buf + _Str.size(), _Src, &_Ch, &_Ch + 1, _Dest)) {
                case _Codecvt::partial:
                case _Codecvt::ok:
                    _Str.erase(0, static_cast<size_t>(_Src - _Buf)); // discard any used input
                    if (_Dest != &_Ch) {
                        return _Traits::to_int_type(_Ch);
                    }

                    break;

                case _Codecvt::noconv:
                    if (_Str.size() < sizeof(_Elem)) {
                        break; // no conversion, but need more chars
                    }

                    _CSTD memcpy(&_Ch, _Buf, sizeof(_Elem)); // copy raw bytes to element
                    _Str.erase(0, sizeof(_Elem));
                    return _Traits::to_int_type(_Ch); // return result

                default:
                    _Status = _Eof; // conversion failed
                    break;
                }
            }
        }

        return _Traits::eof();
    }

    state_type _State; // code conversion state
    const _Codecvt* _Pcvt; // the codecvt facet
    _Mysb* _Mystrbuf; // pointer to stream buffer
    _Mode _Status; // buffer read/write status
    size_t _Nback; // number of elements in putback buffer
    _Elem _Myback[8]; // putback buffer
    string _Str; // unconsumed input bytes
    locale _Loc; // manages reference to codecvt facet
};

_EXPORT_STD template <class _Codecvt, class _Elem = wchar_t, class _Walloc = allocator<_Elem>,
    class _Balloc = allocator<char>>
class _CXX17_DEPRECATE_CODECVT_HEADER wstring_convert { // converts between _Elem (wide) and char (byte) strings
private:
    enum { _BUF_INC = 8, _BUF_MAX = 16 };
    void _Init(const _Codecvt* _Pcvt_arg = new _Codecvt) { // initialize the object
        _State = state_type{};
        _Pcvt  = _Pcvt_arg;
        _Loc   = locale{_Loc, _Pcvt};
        _Nconv = 0;
    }

public:
    using byte_string = basic_string<char, char_traits<char>, _Balloc>;
    using wide_string = basic_string<_Elem, char_traits<_Elem>, _Walloc>;
    using state_type  = typename _Codecvt::state_type;
    using int_type    = typename wide_string::traits_type::int_type;

    wstring_convert() : _Has_state(false), _Has_berr(false), _Has_werr(false) { // construct with no error strings
        _Init();
    }

    explicit wstring_convert(const _Codecvt* _Pcvt_arg)
        : _Has_state(false), _Has_berr(false), _Has_werr(false) { // construct with no error strings and codecvt
        _Init(_Pcvt_arg);
    }

    wstring_convert(const _Codecvt* _Pcvt_arg, state_type _State_arg)
        : _Has_state(true), _Has_berr(false), _Has_werr(false) { // construct with no error strings, codecvt, and state
        _Init(_Pcvt_arg);
        _State = _State_arg;
    }

    explicit wstring_convert(const byte_string& _Berr_arg)
        : _Berr(_Berr_arg), _Has_state(false), _Has_berr(true), _Has_werr(false) { // construct with byte error string
        _Init();
    }

    wstring_convert(const byte_string& _Berr_arg, const wide_string& _Werr_arg)
        : _Berr(_Berr_arg), _Werr(_Werr_arg), _Has_state(false), _Has_berr(true),
          _Has_werr(true) { // construct with byte and wide error strings
        _Init();
    }

    virtual ~wstring_convert() noexcept {}

    _NODISCARD size_t converted() const noexcept { // get conversion count
        return _Nconv;
    }

    _NODISCARD state_type state() const {
        return _State;
    }

    _NODISCARD wide_string from_bytes(char _Byte) { // convert a byte to a wide string
        return from_bytes(&_Byte, &_Byte + 1);
    }

    _NODISCARD wide_string from_bytes(const char* _Ptr) { // convert a NTBS to a wide string
        return from_bytes(_Ptr, _Ptr + _CSTD strlen(_Ptr));
    }

    _NODISCARD wide_string from_bytes(const byte_string& _Bstr) { // convert a byte string to a wide string
        const char* _Ptr = _Bstr.c_str();
        return from_bytes(_Ptr, _Ptr + _Bstr.size());
    }

    _NODISCARD wide_string from_bytes(
        const char* _First, const char* _Last) { // convert byte sequence [_First, _Last) to a wide string
        wide_string _Wbuf;
        wide_string _Wstr;
        const char* _First_sav = _First;

        if (!_Has_state) {
            _State = state_type{}; // reset state if not remembered
        }

        _Wbuf.append(_BUF_INC, _Elem{});
        for (_Nconv = 0; _First != _Last; _Nconv = static_cast<size_t>(_First - _First_sav)) {
            // convert one or more bytes
            _Elem* _Dest = &_Wbuf[0];
            _Elem* _Dnext;

            // test result of converting one or more bytes
            switch (_Pcvt->in(_State, _First, _Last, _First, _Dest, _Dest + _Wbuf.size(), _Dnext)) {
            case _Codecvt::partial:
            case _Codecvt::ok:
                if (_Dest < _Dnext) {
                    _Wstr.append(_Dest, static_cast<size_t>(_Dnext - _Dest));
                } else if (_Wbuf.size() < _BUF_MAX) {
                    _Wbuf.append(_BUF_INC, _Elem{});
                } else if (_Has_werr) {
                    return _Werr;
                } else {
                    _Throw_range_error("bad conversion");
                }

                break;

            case _Codecvt::noconv:
                for (; _First != _Last; ++_First) {
                    _Wstr.push_back(static_cast<_Elem>(static_cast<unsigned char>(*_First)));
                }

                break; // no conversion, just copy code values

            default:
                if (_Has_werr) {
                    return _Werr;
                } else {
                    _Throw_range_error("bad conversion");
                }
            }
        }
        return _Wstr;
    }

    _NODISCARD byte_string to_bytes(_Elem _Char) { // convert a wide char to a byte string
        return to_bytes(&_Char, &_Char + 1);
    }

    _NODISCARD byte_string to_bytes(const _Elem* _Wptr) { // convert a NTWCS to a byte string
        const _Elem* _Next = _Wptr;
        while (*_Next != 0) {
            ++_Next;
        }

        return to_bytes(_Wptr, _Next);
    }

    _NODISCARD byte_string to_bytes(const wide_string& _Wstr) { // convert a wide string to a byte string
        const _Elem* _Wptr = _Wstr.c_str();
        return to_bytes(_Wptr, _Wptr + _Wstr.size());
    }

    _NODISCARD byte_string to_bytes(
        const _Elem* _First, const _Elem* _Last) { // convert wide sequence [_First, _Last) to a byte string
        byte_string _Bbuf;
        byte_string _Bstr;
        const _Elem* _First_sav = _First;

        if (!_Has_state) {
            _State = state_type{}; // reset state if not remembered
        }

        _Bbuf.append(_BUF_INC, '\0');
        for (_Nconv = 0; _First != _Last; _Nconv = static_cast<size_t>(_First - _First_sav)) {
            // convert one or more wide chars
            char* _Dest = &_Bbuf[0];
            char* _Dnext;

            // test result of converting one or more wide chars
            switch (_Pcvt->out(_State, _First, _Last, _First, _Dest, _Dest + _Bbuf.size(), _Dnext)) {
            case _Codecvt::partial:
            case _Codecvt::ok:
                if (_Dest < _Dnext) {
                    _Bstr.append(_Dest, static_cast<size_t>(_Dnext - _Dest));
                } else if (_Bbuf.size() < _BUF_MAX) {
                    _Bbuf.append(_BUF_INC, '\0');
                } else if (_Has_berr) {
                    return _Berr;
                } else {
                    _Throw_range_error("bad conversion");
                }

                break;

            case _Codecvt::noconv:
                for (; _First != _Last; ++_First) {
                    _Bstr.push_back(static_cast<char>(static_cast<int_type>(*_First)));
                }

                break; // no conversion, just copy code values

            default:
                if (_Has_berr) {
                    return _Berr;
                } else {
                    _Throw_range_error("bad conversion");
                }
            }
        }
        return _Bstr;
    }

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

private:
    const _Codecvt* _Pcvt; // the codecvt facet
    locale _Loc; // manages reference to codecvt facet
    byte_string _Berr;
    wide_string _Werr;
    state_type _State; // the remembered state
    bool _Has_state;
    bool _Has_berr;
    bool _Has_werr;
    size_t _Nconv;
};
_STL_RESTORE_DEPRECATED_WARNING
_STD_END
#pragma pop_macro("new")
_STL_RESTORE_CLANG_WARNINGS
#pragma warning(pop)
#pragma pack(pop)
#endif // _STL_COMPILER_PREPROCESSOR
#endif // _XLOCBUF_
