#pragma once

#include "reader.h"

#include <util/generic/maybe.h>
#include <util/generic/bt_exception.h>
#include <util/generic/yexception.h>
#include <util/system/yassert.h>

/** Imperative recursive-descent parsing helpers.
 *
 *  These functions help verify conditions and advance parser state.
 *  For aggregate parsing functions, common precondition is to require Begin{X}
 *  event prior to function invocation. Thus, parsers are composable by calling
 *  sub-parser after dispatching on opening event, e.g.:
 *
 *    if (reader.LastEvent().Type() == EEventType::BeginMap) {
 *        ReadSomeMap(reader)
 *    }
 *
 */

namespace NYsonPull {
    namespace NReadOps {
        class TExpectationFailure: public TWithBackTrace<yexception> {
        };

        inline void Expect(const TEvent& got, EEventType expected) {
            Y_ENSURE_EX(
                got.Type() == expected,
                TExpectationFailure() << "expected " << expected << ", got " << got);
        }

        inline void Expect(const TScalar& got, EScalarType expected) {
            Y_ENSURE_EX(
                got.Type() == expected,
                TExpectationFailure() << "expected scalar " << expected << ", got " << got);
        }

        // ExpectBegin{X} functions verify that last event WAS X
        // SkipBegin{X} functions verify that next event WILL BE X and CONSUME it

        inline void ExpectBeginStream(TReader& reader) {
            Expect(reader.LastEvent(), EEventType::BeginStream);
        }

        inline void SkipBeginStream(TReader& reader) {
            Expect(reader.NextEvent(), EEventType::BeginStream);
        }

        inline void ExpectBeginMap(TReader& reader) {
            Expect(reader.LastEvent(), EEventType::BeginMap);
        }

        inline void SkipBeginMap(TReader& reader) {
            Expect(reader.NextEvent(), EEventType::BeginMap);
        }

        inline void ExpectBeginList(TReader& reader) {
            Expect(reader.LastEvent(), EEventType::BeginList);
        }

        inline void SkipBeginList(TReader& reader) {
            Expect(reader.NextEvent(), EEventType::BeginList);
        }

        inline bool ReadListItem(TReader& reader) {
            return reader.NextEvent().Type() != EEventType::EndList;
        }

        inline TMaybe<TStringBuf> ReadKey(TReader& reader) {
            const auto& event = reader.NextEvent();
            switch (event.Type()) {
                case EEventType::Key:
                    return event.AsString();
                case EEventType::EndMap:
                    return Nothing();
                default:
                    ythrow yexception() << "Unexpected event: " << event;
            }
        }

        template <typename T = const TScalar&>
        inline T ReadScalar(TReader& reader);

        template <>
        inline const TScalar& ReadScalar<const TScalar&>(TReader& reader) {
            const auto& event = reader.NextEvent();
            Expect(event, EEventType::Scalar);
            return event.AsScalar();
        }

        template <>
        inline i64 ReadScalar<i64>(TReader& reader) {
            const auto& scalar = ReadScalar(reader);
            Expect(scalar, EScalarType::Int64);
            return scalar.AsInt64();
        }

        template <>
        inline ui64 ReadScalar<ui64>(TReader& reader) {
            const auto& scalar = ReadScalar(reader);
            Expect(scalar, EScalarType::UInt64);
            return scalar.AsUInt64();
        }

        template <>
        inline double ReadScalar<double>(TReader& reader) {
            const auto& scalar = ReadScalar(reader);
            Expect(scalar, EScalarType::Float64);
            return scalar.AsFloat64();
        }

        template <>
        inline TStringBuf ReadScalar<TStringBuf>(TReader& reader) {
            const auto& scalar = ReadScalar(reader);
            Expect(scalar, EScalarType::String);
            return scalar.AsString();
        }

        template <>
        inline TString ReadScalar<TString>(TReader& reader) {
            return TString(ReadScalar<TStringBuf>(reader));
        }

        template <>
        inline bool ReadScalar<bool>(TReader& reader) {
            const auto& scalar = ReadScalar(reader);
            Expect(scalar, EScalarType::Boolean);
            return scalar.AsBoolean();
        }

        // Skip value that was already started with `event`
        void SkipCurrentValue(const TEvent& event, TReader& reader);

        // Skip value that starts at `reader.next_event()`
        void SkipValue(TReader& reader);

        // Skip values with attributes, wait for map value
        void SkipControlRecords(TReader& reader);
    }
}