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

/*!
  \file
  \version \$Id$
  \author Steve Lhomme     <robux4 @ users.sf.net>
  \author Moritz Bunkus <moritz @ bunkus.org>
*/
#include <cassert>
#include <limits>

#include "ebml/EbmlSInteger.h"

// Converting unsigned int types to signed ints assuming the
// underlying bits in memory should represent the 2's complement of a
// signed integer. See https://stackoverflow.com/a/13208789/507077

namespace {

int64
ToSigned(uint64 u) {
  if (u <= static_cast<uint64>(std::numeric_limits<int64>::max()))
    return static_cast<int64>(u);

  return static_cast<int64>(u - std::numeric_limits<int64>::min()) + std::numeric_limits<int64>::min();
}

} // namespace

namespace libebml {

EbmlSInteger::EbmlSInteger()
  :EbmlElement(DEFAULT_INT_SIZE, false)
{}

EbmlSInteger::EbmlSInteger(int64 aDefaultValue)
  :EbmlElement(DEFAULT_INT_SIZE, true), Value(aDefaultValue)
{
  SetDefaultIsSet();
}

EbmlSInteger::operator int8() const {return  static_cast<int8>(Value);}
EbmlSInteger::operator int16() const {return static_cast<int16>(Value);}
EbmlSInteger::operator int32() const {return static_cast<int32>(Value);}
EbmlSInteger::operator int64() const {return Value;}

int64 EbmlSInteger::GetValue() const {return Value;}

EbmlSInteger & EbmlSInteger::SetValue(int64 NewValue) {
  return *this = NewValue;
}

/*!
  \todo handle exception on errors
*/
filepos_t EbmlSInteger::RenderData(IOCallback & output, bool /* bForceRender */, bool /* bWithDefault */)
{
  binary FinalData[8]; // we don't handle more than 64 bits integers
  unsigned int i;

  if (GetSizeLength() > 8)
    return 0; // integer bigger coded on more than 64 bits are not supported

  int64 TempValue = Value;
  for (i=0; i<GetSize();i++) {
    FinalData[GetSize()-i-1] = static_cast<binary>(TempValue & 0xFF);
    TempValue >>= 8;
  }

  output.writeFully(FinalData,GetSize());

  return GetSize();
}

uint64 EbmlSInteger::UpdateSize(bool bWithDefault, bool /* bForceRender */)
{
  if (!bWithDefault && IsDefaultValue())
    return 0;

  if (Value <= 0x7F && Value >= (-0x80)) {
    SetSize_(1);
  } else if (Value <= 0x7FFF && Value >= (-0x8000)) {
    SetSize_(2);
  } else if (Value <= 0x7FFFFF && Value >= (-0x800000)) {
    SetSize_(3);
  } else if (Value <= EBML_PRETTYLONGINT(0x7FFFFFFF) && Value >= (EBML_PRETTYLONGINT(-0x80000000))) {
    SetSize_(4);
  } else if (Value <= EBML_PRETTYLONGINT(0x7FFFFFFFFF) &&
             Value >= EBML_PRETTYLONGINT(-0x8000000000)) {
    SetSize_(5);
  } else if (Value <= EBML_PRETTYLONGINT(0x7FFFFFFFFFFF) &&
             Value >= EBML_PRETTYLONGINT(-0x800000000000)) {
    SetSize_(6);
  } else if (Value <= EBML_PRETTYLONGINT(0x7FFFFFFFFFFFFF) &&
             Value >= EBML_PRETTYLONGINT(-0x80000000000000)) {
    SetSize_(7);
  } else {
    SetSize_(8);
  }

  if (GetDefaultSize() > GetSize()) {
    SetSize_(GetDefaultSize());
  }

  return GetSize();
}

filepos_t EbmlSInteger::ReadData(IOCallback & input, ScopeMode ReadFully)
{
  if (ReadFully == SCOPE_NO_DATA)
    return GetSize();

  if (GetSize() > 8) {
    // impossible to read, skip it
    input.setFilePointer(GetSize(), seek_current);
    return GetSize();
  }

  binary Buffer[8];
  input.readFully(Buffer, GetSize());

  uint64 TempValue = Buffer[0] & 0x80 ? std::numeric_limits<uint64>::max() : 0;

  for (unsigned int i=0; i<GetSize(); i++) {
    TempValue <<= 8;
    TempValue |= Buffer[i];
  }

  Value = ToSigned(TempValue);

  SetValueIsSet();
  return GetSize();
}

bool EbmlSInteger::IsSmallerThan(const EbmlElement *Cmp) const
{
  if (EbmlId(*this) == EbmlId(*Cmp))
    return this->Value < static_cast<const EbmlSInteger *>(Cmp)->Value;

  return false;
}

} // namespace libebml
