// Copyright © 2002-2010 Steve Lhomme.
// SPDX-License-Identifier: LGPL-2.1-or-later

/*!
  \file
  \version \$Id$
  \author Steve Lhomme     <robux4 @ users.sf.net>
  \author Jory Stone       <jcsston @ toughguy.net>
*/

#include <cassert>
#include <iterator>
#include <limits>
#include <new>

#include "ebml/EbmlUnicodeString.h"

#include <utf8/checked.h>

namespace libebml {

// ===================== UTFstring class ===================

UTFstring::UTFstring()
  :_Length(0)
  ,_Data(nullptr)
{}

UTFstring::UTFstring(const wchar_t * _aBuf)
  :_Length(0)
  ,_Data(nullptr)
{
  *this = _aBuf;
}

UTFstring::UTFstring(std::wstring const &_aBuf)
  :_Length(0)
  ,_Data(nullptr)
{
  *this = _aBuf.c_str();
}

UTFstring::~UTFstring()
{
  delete [] _Data;
}

UTFstring::UTFstring(const UTFstring & _aBuf)
  :_Length(0)
  ,_Data(nullptr)
{
  *this = _aBuf.c_str();
}

UTFstring & UTFstring::operator=(const UTFstring & _aBuf)
{
  *this = _aBuf.c_str();
  return *this;
}

UTFstring::operator const wchar_t*() const {return _Data;}


UTFstring & UTFstring::operator=(const wchar_t * _aBuf)
{
  delete [] _Data;
  if (_aBuf == nullptr) {
    _Data = new wchar_t[1];
    _Data[0] = 0;
    UpdateFromUCS2();
    return *this;
  }

  size_t aLen;
  for (aLen=0; _aBuf[aLen] != 0; aLen++);
  _Length = aLen;
  _Data = new wchar_t[_Length+1];
  for (aLen=0; _aBuf[aLen] != 0; aLen++) {
    _Data[aLen] = _aBuf[aLen];
  }
  _Data[aLen] = 0;
  UpdateFromUCS2();
  return *this;
}

UTFstring & UTFstring::operator=(wchar_t _aChar)
{
  delete [] _Data;
  _Data = new wchar_t[2];
  _Length = 1;
  _Data[0] = _aChar;
  _Data[1] = 0;
  UpdateFromUCS2();
  return *this;
}

bool UTFstring::operator==(const UTFstring& _aStr) const
{
  if ((_Data == nullptr) && (_aStr._Data == nullptr))
    return true;
  if ((_Data == nullptr) || (_aStr._Data == nullptr))
    return false;
  return wcscmp_internal(_Data, _aStr._Data);
}

void UTFstring::SetUTF8(const std::string & _aStr)
{
  UTF8string = _aStr;
  UpdateFromUTF8();
}

/*!
  \see RFC 2279
*/
void UTFstring::UpdateFromUTF8()
{
  // Only convert up to the first \0 character if present.
  std::string::iterator End = UTF8string.end(), Current = UTF8string.begin();
  while ((Current != End) && *Current)
    ++Current;

  std::wstring Temp;
  try {
    // Even though the function names hint at UCS2, the internal
    // representation must actually be compatible with the C++
    // library's implementation. Implementations with sizeof(wchar_t)
    // == 4 are using UCS4.
    if (sizeof(wchar_t) == 2)
      ::utf8::utf8to16(UTF8string.begin(), Current, std::back_inserter(Temp));
    else
      ::utf8::utf8to32(UTF8string.begin(), Current, std::back_inserter(Temp));
  } catch (::utf8::invalid_code_point &) {
  } catch (::utf8::invalid_utf8 &) {
  }

  delete [] _Data;
  _Length = Temp.length();
  _Data   = new wchar_t[_Length + 1];

  std::memcpy(_Data, Temp.c_str(), sizeof(wchar_t) * (_Length + 1));
}

void UTFstring::UpdateFromUCS2()
{
  UTF8string.clear();

  if (!_Data)
    return;

  // Only convert up to the first \0 character if present.
  size_t Current = 0;
  while ((Current < _Length) && _Data[Current])
    ++Current;

  try {
    // Even though the function is called UCS2, the internal
    // representation must actually be compatible with the C++
    // library's implementation. Implementations with sizeof(wchar_t)
    // == 4 are using UCS4.
    if (sizeof(wchar_t) == 2)
      ::utf8::utf16to8(_Data, _Data + Current, std::back_inserter(UTF8string));
    else
      ::utf8::utf32to8(_Data, _Data + Current, std::back_inserter(UTF8string));
  } catch (::utf8::invalid_code_point &) {
  } catch (::utf8::invalid_utf16 &) {
  }
}

bool UTFstring::wcscmp_internal(const wchar_t *str1, const wchar_t *str2)
{
  size_t Index=0;
  while (str1[Index] == str2[Index] && str1[Index] != 0) {
    Index++;
  }
  return (str1[Index] == str2[Index]);
}

// ===================== EbmlUnicodeString class ===================

EbmlUnicodeString::EbmlUnicodeString()
  :EbmlElement(0, false)
{
  SetDefaultSize(0);
}

EbmlUnicodeString::EbmlUnicodeString(const UTFstring & aDefaultValue)
  :EbmlElement(0, true), Value(aDefaultValue), DefaultValue(aDefaultValue)
{
  SetDefaultSize(0);
  SetDefaultIsSet();
}

void EbmlUnicodeString::SetDefaultValue(UTFstring & aValue)
{
  assert(!DefaultISset());
  DefaultValue = aValue;
  SetDefaultIsSet();
}

const UTFstring & EbmlUnicodeString::DefaultVal() const
{
  assert(DefaultISset());
  return DefaultValue;
}


/*!
\note limited to UCS-2
\todo handle exception on errors
*/
filepos_t EbmlUnicodeString::RenderData(IOCallback & output, bool /* bForceRender */, bool /* bWithDefault */)
{
  uint32 Result = Value.GetUTF8().length();

  if (Result != 0) {
    output.writeFully(Value.GetUTF8().c_str(), Result);
  }

  if (Result < GetDefaultSize()) {
    // pad the rest with 0
    auto Pad = new (std::nothrow) binary[GetDefaultSize() - Result];
    if (Pad != nullptr) {
      memset(Pad, 0x00, GetDefaultSize() - Result);
      output.writeFully(Pad, GetDefaultSize() - Result);

      Result = GetDefaultSize();
      delete [] Pad;
    }
  }

  return Result;
}

EbmlUnicodeString::operator const UTFstring &() const {return Value;}

EbmlUnicodeString & EbmlUnicodeString::operator=(const UTFstring & NewString)
{
  Value = NewString;
  SetValueIsSet();
  return *this;
}

EbmlUnicodeString &EbmlUnicodeString::SetValue(UTFstring const &NewValue) {
  return *this = NewValue;
}

EbmlUnicodeString &EbmlUnicodeString::SetValueUTF8(std::string const &NewValue) {
  UTFstring NewValueUTFstring;
  NewValueUTFstring.SetUTF8(NewValue);
  return *this = NewValueUTFstring;
}

UTFstring EbmlUnicodeString::GetValue() const {
  return Value;
}

std::string EbmlUnicodeString::GetValueUTF8() const {
  return Value.GetUTF8();
}

/*!
\note limited to UCS-2
*/
uint64 EbmlUnicodeString::UpdateSize(bool bWithDefault, bool /* bForceRender */)
{
  if (!bWithDefault && IsDefaultValue())
    return 0;

  SetSize_(Value.GetUTF8().length());
  if (GetSize() < GetDefaultSize())
    SetSize_(GetDefaultSize());

  return GetSize();
}

/*!
  \note limited to UCS-2
*/
filepos_t EbmlUnicodeString::ReadData(IOCallback & input, ScopeMode ReadFully)
{
  if (ReadFully == SCOPE_NO_DATA)
    return GetSize();

  if (GetSize() == 0) {
    Value = static_cast<UTFstring::value_type>(0);

  } else {
    std::string Buffer(static_cast<std::string::size_type>(GetSize()), static_cast<char>(0));
    input.readFully(&Buffer[0], GetSize());

    Value.SetUTF8(Buffer.c_str()); // Let conversion to std::string cut off at the first 0
  }

  SetValueIsSet();

  return GetSize();
}

} // namespace libebml
