#include <limits>

#include "emitterstate.h"
#include "yaml-cpp/exceptions.h"  // IWYU pragma: keep

namespace YODA_YAML {
EmitterState::EmitterState()
    : m_isGood(true),
      m_lastError{},
      // default global manipulators
      m_charset(EmitNonAscii),
      m_strFmt(Auto),
      m_boolFmt(TrueFalseBool),
      m_boolLengthFmt(LongBool),
      m_boolCaseFmt(LowerCase),
      m_nullFmt(TildeNull),
      m_intFmt(Dec),
      m_indent(2),
      m_preCommentIndent(2),
      m_postCommentIndent(1),
      m_seqFmt(Block),
      m_mapFmt(Block),
      m_mapKeyFmt(Auto),
      m_floatPrecision(std::numeric_limits<float>::max_digits10),
      m_doublePrecision(std::numeric_limits<double>::max_digits10),
      //
      m_modifiedSettings{},
      m_globalModifiedSettings{},
      m_groups{},
      m_curIndent(0),
      m_hasAnchor(false),
      m_hasAlias(false),
      m_hasTag(false),
      m_hasNonContent(false),
      m_docCount(0) {}

EmitterState::~EmitterState() = default;

// SetLocalValue
// . We blindly tries to set all possible formatters to this value
// . Only the ones that make sense will be accepted
void EmitterState::SetLocalValue(EMITTER_MANIP value) {
  SetOutputCharset(value, FmtScope::Local);
  SetStringFormat(value, FmtScope::Local);
  SetBoolFormat(value, FmtScope::Local);
  SetBoolCaseFormat(value, FmtScope::Local);
  SetBoolLengthFormat(value, FmtScope::Local);
  SetNullFormat(value, FmtScope::Local);
  SetIntFormat(value, FmtScope::Local);
  SetFlowType(GroupType::Seq, value, FmtScope::Local);
  SetFlowType(GroupType::Map, value, FmtScope::Local);
  SetMapKeyFormat(value, FmtScope::Local);
}

void EmitterState::SetAnchor() { m_hasAnchor = true; }

void EmitterState::SetAlias() { m_hasAlias = true; }

void EmitterState::SetTag() { m_hasTag = true; }

void EmitterState::SetNonContent() { m_hasNonContent = true; }

void EmitterState::SetLongKey() {
  assert(!m_groups.empty());
  if (m_groups.empty()) {
    return;
  }

  assert(m_groups.back()->type == GroupType::Map);
  m_groups.back()->longKey = true;
}

void EmitterState::ForceFlow() {
  assert(!m_groups.empty());
  if (m_groups.empty()) {
    return;
  }

  m_groups.back()->flowType = FlowType::Flow;
}

void EmitterState::StartedNode() {
  if (m_groups.empty()) {
    m_docCount++;
  } else {
    m_groups.back()->childCount++;
    if (m_groups.back()->childCount % 2 == 0) {
      m_groups.back()->longKey = false;
    }
  }

  m_hasAnchor = false;
  m_hasAlias = false;
  m_hasTag = false;
  m_hasNonContent = false;
}

EmitterNodeType::value EmitterState::NextGroupType(
    GroupType::value type) const {
  if (type == GroupType::Seq) {
    if (GetFlowType(type) == Block)
      return EmitterNodeType::BlockSeq;
    return EmitterNodeType::FlowSeq;
  }

  if (GetFlowType(type) == Block)
    return EmitterNodeType::BlockMap;
  return EmitterNodeType::FlowMap;

  // can't happen
  assert(false);
  return EmitterNodeType::NoType;
}

void EmitterState::StartedDoc() {
  m_hasAnchor = false;
  m_hasTag = false;
  m_hasNonContent = false;
}

void EmitterState::EndedDoc() {
  m_hasAnchor = false;
  m_hasTag = false;
  m_hasNonContent = false;
}

void EmitterState::StartedScalar() {
  StartedNode();
  ClearModifiedSettings();
}

void EmitterState::StartedGroup(GroupType::value type) {
  StartedNode();

  const std::size_t lastGroupIndent =
      (m_groups.empty() ? 0 : m_groups.back()->indent);
  m_curIndent += lastGroupIndent;

  // TODO: Create move constructors for settings types to simplify transfer
  std::unique_ptr<Group> pGroup(new Group(type));

  // transfer settings (which last until this group is done)
  //
  // NB: if pGroup->modifiedSettings == m_modifiedSettings,
  // m_modifiedSettings is not changed!
  pGroup->modifiedSettings = std::move(m_modifiedSettings);

  // set up group
  if (GetFlowType(type) == Block) {
    pGroup->flowType = FlowType::Block;
  } else {
    pGroup->flowType = FlowType::Flow;
  }
  pGroup->indent = GetIndent();

  m_groups.push_back(std::move(pGroup));
}

void EmitterState::EndedGroup(GroupType::value type) {
  if (m_groups.empty()) {
    if (type == GroupType::Seq) {
      return SetError(ErrorMsg::UNEXPECTED_END_SEQ);
    }
    return SetError(ErrorMsg::UNEXPECTED_END_MAP);
  }

  if (m_hasTag) {
    SetError(ErrorMsg::INVALID_TAG);
  }
  if (m_hasAnchor) {
    SetError(ErrorMsg::INVALID_ANCHOR);
  }

  // get rid of the current group
  {
    std::unique_ptr<Group> pFinishedGroup = std::move(m_groups.back());
    m_groups.pop_back();
    if (pFinishedGroup->type != type) {
      return SetError(ErrorMsg::UNMATCHED_GROUP_TAG);
    }
  }

  // reset old settings
  std::size_t lastIndent = (m_groups.empty() ? 0 : m_groups.back()->indent);
  assert(m_curIndent >= lastIndent);
  m_curIndent -= lastIndent;

  // some global settings that we changed may have been overridden
  // by a local setting we just popped, so we need to restore them
  m_globalModifiedSettings.restore();

  ClearModifiedSettings();
  m_hasAnchor = false;
  m_hasTag = false;
  m_hasNonContent = false;
}

EmitterNodeType::value EmitterState::CurGroupNodeType() const {
  if (m_groups.empty()) {
    return EmitterNodeType::NoType;
  }

  return m_groups.back()->NodeType();
}

GroupType::value EmitterState::CurGroupType() const {
  return m_groups.empty() ? GroupType::NoType : m_groups.back()->type;
}

FlowType::value EmitterState::CurGroupFlowType() const {
  return m_groups.empty() ? FlowType::NoType : m_groups.back()->flowType;
}

std::size_t EmitterState::CurGroupIndent() const {
  return m_groups.empty() ? 0 : m_groups.back()->indent;
}

std::size_t EmitterState::CurGroupChildCount() const {
  return m_groups.empty() ? m_docCount : m_groups.back()->childCount;
}

bool EmitterState::CurGroupLongKey() const {
  return m_groups.empty() ? false : m_groups.back()->longKey;
}

std::size_t EmitterState::LastIndent() const {
  if (m_groups.size() <= 1) {
    return 0;
  }

  return m_curIndent - m_groups[m_groups.size() - 2]->indent;
}

void EmitterState::ClearModifiedSettings() { m_modifiedSettings.clear(); }

void EmitterState::RestoreGlobalModifiedSettings() {
  m_globalModifiedSettings.restore();
}

bool EmitterState::SetOutputCharset(EMITTER_MANIP value,
                                    FmtScope::value scope) {
  switch (value) {
    case EmitNonAscii:
    case EscapeNonAscii:
    case EscapeAsJson:
      _Set(m_charset, value, scope);
      return true;
    default:
      return false;
  }
}

bool EmitterState::SetStringFormat(EMITTER_MANIP value, FmtScope::value scope) {
  switch (value) {
    case Auto:
    case SingleQuoted:
    case DoubleQuoted:
    case Literal:
      _Set(m_strFmt, value, scope);
      return true;
    default:
      return false;
  }
}

bool EmitterState::SetBoolFormat(EMITTER_MANIP value, FmtScope::value scope) {
  switch (value) {
    case OnOffBool:
    case TrueFalseBool:
    case YesNoBool:
      _Set(m_boolFmt, value, scope);
      return true;
    default:
      return false;
  }
}

bool EmitterState::SetBoolLengthFormat(EMITTER_MANIP value,
                                       FmtScope::value scope) {
  switch (value) {
    case LongBool:
    case ShortBool:
      _Set(m_boolLengthFmt, value, scope);
      return true;
    default:
      return false;
  }
}

bool EmitterState::SetBoolCaseFormat(EMITTER_MANIP value,
                                     FmtScope::value scope) {
  switch (value) {
    case UpperCase:
    case LowerCase:
    case CamelCase:
      _Set(m_boolCaseFmt, value, scope);
      return true;
    default:
      return false;
  }
}

bool EmitterState::SetNullFormat(EMITTER_MANIP value, FmtScope::value scope) {
  switch (value) {
    case LowerNull:
    case UpperNull:
    case CamelNull:
    case TildeNull:
      _Set(m_nullFmt, value, scope);
      return true;
    default:
      return false;
  }
}

bool EmitterState::SetIntFormat(EMITTER_MANIP value, FmtScope::value scope) {
  switch (value) {
    case Dec:
    case Hex:
    case Oct:
      _Set(m_intFmt, value, scope);
      return true;
    default:
      return false;
  }
}

bool EmitterState::SetIndent(std::size_t value, FmtScope::value scope) {
  if (value <= 1)
    return false;

  _Set(m_indent, value, scope);
  return true;
}

bool EmitterState::SetPreCommentIndent(std::size_t value,
                                       FmtScope::value scope) {
  if (value == 0)
    return false;

  _Set(m_preCommentIndent, value, scope);
  return true;
}

bool EmitterState::SetPostCommentIndent(std::size_t value,
                                        FmtScope::value scope) {
  if (value == 0)
    return false;

  _Set(m_postCommentIndent, value, scope);
  return true;
}

bool EmitterState::SetFlowType(GroupType::value groupType, EMITTER_MANIP value,
                               FmtScope::value scope) {
  switch (value) {
    case Block:
    case Flow:
      _Set(groupType == GroupType::Seq ? m_seqFmt : m_mapFmt, value, scope);
      return true;
    default:
      return false;
  }
}

EMITTER_MANIP EmitterState::GetFlowType(GroupType::value groupType) const {
  // force flow style if we're currently in a flow
  if (CurGroupFlowType() == FlowType::Flow)
    return Flow;

  // otherwise, go with what's asked of us
  return (groupType == GroupType::Seq ? m_seqFmt.get() : m_mapFmt.get());
}

bool EmitterState::SetMapKeyFormat(EMITTER_MANIP value, FmtScope::value scope) {
  switch (value) {
    case Auto:
    case LongKey:
      _Set(m_mapKeyFmt, value, scope);
      return true;
    default:
      return false;
  }
}

bool EmitterState::SetFloatPrecision(std::size_t value, FmtScope::value scope) {
  if (value > std::numeric_limits<float>::max_digits10)
    return false;
  _Set(m_floatPrecision, value, scope);
  return true;
}

bool EmitterState::SetDoublePrecision(std::size_t value,
                                      FmtScope::value scope) {
  if (value > std::numeric_limits<double>::max_digits10)
    return false;
  _Set(m_doublePrecision, value, scope);
  return true;
}
}  // namespace YODA_YAML
