#include "skiff_schema.h"

#include "skiff.h"

#include <util/generic/hash.h>

namespace NSkiff {

////////////////////////////////////////////////////////////////////////////////

bool operator==(const TSkiffSchema& lhs, const TSkiffSchema& rhs)
{
    if (lhs.GetWireType() != rhs.GetWireType() || lhs.GetName() != rhs.GetName()) {
        return false;
    }
    const auto& lhsChildren = lhs.GetChildren();
    const auto& rhsChildren = rhs.GetChildren();
    return std::equal(
        std::begin(lhsChildren),
        std::end(lhsChildren),
        std::begin(rhsChildren),
        std::end(rhsChildren),
        TSkiffSchemaPtrEqual());
}

bool operator!=(const TSkiffSchema& lhs, const TSkiffSchema& rhs)
{
    return !(lhs == rhs);
}

////////////////////////////////////////////////////////////////////////////////

void PrintShortDebugString(const std::shared_ptr<const TSkiffSchema>& schema, IOutputStream* out)
{
    (*out) << ToString(schema->GetWireType());
    if (!IsSimpleType(schema->GetWireType())) {
        auto children = schema->GetChildren();
        if (!children.empty()) {
            (*out) << '<';
            for (const auto& child : children) {
                PrintShortDebugString(child, out);
                (*out) << ';';
            }
            (*out) << '>';
        }
    }
}

TString GetShortDebugString(const std::shared_ptr<const TSkiffSchema>& schema)
{
    TStringStream out;
    PrintShortDebugString(schema, &out);
    return out.Str();
}

std::shared_ptr<TSimpleTypeSchema> CreateSimpleTypeSchema(EWireType type)
{
    return std::make_shared<TSimpleTypeSchema>(type);
}

static void VerifyNonemptyChildren(const TSkiffSchemaList& children, EWireType wireType)
{
    if (children.empty()) {
        ythrow TSkiffException() << "\"" << ToString(wireType) << "\" must have at least one child";
    }
}

std::shared_ptr<TTupleSchema> CreateTupleSchema(TSkiffSchemaList children)
{
    return std::make_shared<TTupleSchema>(std::move(children));
}

std::shared_ptr<TVariant8Schema> CreateVariant8Schema(TSkiffSchemaList children)
{
    VerifyNonemptyChildren(children, EWireType::Variant8);
    return std::make_shared<TVariant8Schema>(std::move(children));
}

std::shared_ptr<TVariant16Schema> CreateVariant16Schema(TSkiffSchemaList children)
{
    VerifyNonemptyChildren(children, EWireType::Variant16);
    return std::make_shared<TVariant16Schema>(std::move(children));
}

std::shared_ptr<TRepeatedVariant8Schema> CreateRepeatedVariant8Schema(TSkiffSchemaList children)
{
    VerifyNonemptyChildren(children, EWireType::RepeatedVariant8);
    return std::make_shared<TRepeatedVariant8Schema>(std::move(children));
}

std::shared_ptr<TRepeatedVariant16Schema> CreateRepeatedVariant16Schema(TSkiffSchemaList children)
{
    VerifyNonemptyChildren(children, EWireType::RepeatedVariant16);
    return std::make_shared<TRepeatedVariant16Schema>(std::move(children));
}

////////////////////////////////////////////////////////////////////////////////

TSkiffSchema::TSkiffSchema(EWireType type)
    : Type_(type)
{ }

EWireType TSkiffSchema::GetWireType() const
{
    return Type_;
}

std::shared_ptr<TSkiffSchema> TSkiffSchema::SetName(TString name)
{
    Name_ = std::move(name);
    return shared_from_this();
}

const TString& TSkiffSchema::GetName() const
{
    return Name_;
}

const TSkiffSchemaList& TSkiffSchema::GetChildren() const
{
    static const TSkiffSchemaList children;
    return children;
}

////////////////////////////////////////////////////////////////////////////////

TSimpleTypeSchema::TSimpleTypeSchema(EWireType type)
    : TSkiffSchema(type)
{
    Y_ABORT_UNLESS(IsSimpleType(type));
}

////////////////////////////////////////////////////////////////////////////////

size_t TSkiffSchemaPtrHasher::operator()(const std::shared_ptr<TSkiffSchema>& schema) const
{
    return THash<NSkiff::TSkiffSchema>()(*schema);
}

size_t TSkiffSchemaPtrEqual::operator()(
    const std::shared_ptr<TSkiffSchema>& lhs,
    const std::shared_ptr<TSkiffSchema>& rhs) const
{
    return *lhs == *rhs;
}

////////////////////////////////////////////////////////////////////////////////

} // namespace NSkiff

////////////////////////////////////////////////////////////////////////////////

size_t THash<NSkiff::TSkiffSchema>::operator()(const NSkiff::TSkiffSchema &schema) const
{
    auto hash = CombineHashes(
        THash<TString>()(schema.GetName()),
        static_cast<size_t>(schema.GetWireType()));
    for (const auto& child : schema.GetChildren()) {
        hash = CombineHashes(hash, (*this)(*child));
    }
    return hash;
}

////////////////////////////////////////////////////////////////////////////////