////////////////////////////////////////////////////////////////////////////
//
// Copyright 2015 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////

#ifndef REALM_OBJECT_STORE_HPP
#define REALM_OBJECT_STORE_HPP

#include <realm/object-store/property.hpp>
#include <realm/exceptions.hpp>

#include <realm/table_ref.hpp>
#include <realm/util/optional.hpp>

#include <functional>
#include <string>
#include <unordered_set>
#include <vector>
#include <limits>

namespace realm {
class Group;
class Transaction;
class Schema;
class SchemaChange;
class StringData;
enum class SchemaMode : uint8_t;

namespace util {
template <typename... Args>
std::string format(const char* fmt, Args&&... args);
}

class ObjectStore {
public:
    // Schema version used for uninitialized Realms
    static constexpr uint64_t NotVersioned = std::numeric_limits<uint64_t>::max();

    // get the last set schema version
    static uint64_t get_schema_version(Group const& group);

    // set the schema version without any checks
    // and the tables for the schema version and the primary key are created if they don't exist
    // NOTE: must be performed within a write transaction
    static void set_schema_version(Group& group, uint64_t version);

    // check if all of the changes in the list can be applied automatically, or
    // throw if any of them require a schema version bump and migration function
    static void verify_no_migration_required(std::vector<SchemaChange> const& changes);

    // Similar to above, but returns a bool rather than throwing/not throwing
    static bool needs_migration(std::vector<SchemaChange> const& changes);

    // check if any of the schema changes in the list are forbidden in
    // additive-only mode, and if any are throw an exception
    // returns true if any of the changes are not no-ops
    static bool verify_valid_additive_changes(std::vector<SchemaChange> const& changes, bool update_indexes = false);

    // check if the schema changes made by a different process made any changes
    // which will prevent us from being able to continue (such as removing a
    // property we were relying on)
    static void verify_valid_external_changes(std::vector<SchemaChange> const& changes);

    static void verify_compatible_for_immutable_and_readonly(std::vector<SchemaChange> const& changes);

    // check if changes is empty, and throw an exception if not
    static void verify_no_changes_required(std::vector<SchemaChange> const& changes);

    // updates a Realm from old_schema to the given target schema, creating and updating tables as needed
    // passed in target schema is updated with the correct column mapping
    // optionally runs migration function if schema is out of date
    // NOTE: must be performed within a write transaction
    static void apply_schema_changes(Transaction& group, uint64_t schema_version, Schema& target_schema,
                                     uint64_t target_schema_version, SchemaMode mode,
                                     std::vector<SchemaChange> const& changes, bool handle_automatically_backlinks,
                                     std::function<void()> migration_function = {});

    static void apply_additive_changes(Group&, std::vector<SchemaChange> const&, bool update_indexes);

    // get a table for an object type
    static realm::TableRef table_for_object_type(Group& group, StringData object_type);
    static realm::ConstTableRef table_for_object_type(Group const& group, StringData object_type);

    // get existing Schema from a group
    static Schema schema_from_group(Group const& group);

    static void set_schema_keys(Group const& group, Schema& schema);

    // deletes the table for the given type
    static void delete_data_for_object(Group& group, StringData object_type);

    // indicates if this group contains any objects
    static bool is_empty(Group const& group);

    // renames the object_type's column of the old_name to the new name
    static void rename_property(Group& group, Schema& schema, StringData object_type, StringData old_name,
                                StringData new_name);

    static std::string table_name_for_object_type(StringData class_name);
    static StringData object_type_for_table_name(StringData table_name);

private:
    friend class ObjectSchema;
};

class InvalidSchemaVersionException : public LogicError {
public:
    InvalidSchemaVersionException(uint64_t old_version, uint64_t new_version, bool must_exactly_equal);
    uint64_t old_version() const
    {
        return m_old_version;
    }
    uint64_t new_version() const
    {
        return m_new_version;
    }

private:
    uint64_t m_old_version, m_new_version;
};

// Schema validation exceptions
struct ObjectSchemaValidationException {
    ObjectSchemaValidationException(std::string message)
        : m_message(std::move(message))
    {
    }

    template <typename... Args>
    ObjectSchemaValidationException(const char* fmt, Args&&... args)
        : m_message(util::format(fmt, std::forward<Args>(args)...))
    {
    }
    std::string m_message;
};

struct SchemaValidationException : public LogicError {
    SchemaValidationException(std::vector<ObjectSchemaValidationException> const& errors);
};

struct SchemaMismatchException : public LogicError {
    SchemaMismatchException(std::vector<ObjectSchemaValidationException> const& errors);
};

struct InvalidAdditiveSchemaChangeException : public LogicError {
    InvalidAdditiveSchemaChangeException(std::vector<ObjectSchemaValidationException> const& errors);
};
struct InvalidReadOnlySchemaChangeException : public LogicError {
    InvalidReadOnlySchemaChangeException(std::vector<ObjectSchemaValidationException> const& errors);
};

struct InvalidExternalSchemaChangeException : public LogicError {
    InvalidExternalSchemaChangeException(std::vector<ObjectSchemaValidationException> const& errors);
};
} // namespace realm

#endif /* defined(REALM_OBJECT_STORE_HPP) */
