/**
 * \file src/serialization/include/megbrain/serialization/serializer.h
 * MegEngine is Licensed under the Apache License, Version 2.0 (the "License")
 *
 * Copyright (c) 2014-2021 Megvii Inc. All rights reserved.
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT ARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 */

#pragma once

#include "megbrain/graph.h"
#include "megbrain/serialization/dump_format.h"
#include "megbrain/serialization/file.h"
#include "megbrain/serialization/load_dump_config.h"

namespace mgb {
namespace serialization {
    /*!
     * \brief load graph from megbrain dump file
     *
     * Each GraphLoader instance can create multiple graphs, but all the created
     * graphs share underlying params (i.e. values for SharedDeviceTensor are
     * shared)
     */
    class GraphLoader {
        public:
            using LoadConfig = GraphLoadConfig;
            struct LoadResult {
                //! expliit dtor decl to reduce binary size
                ~LoadResult() noexcept;

                using TensorMap = std::unordered_map<
                    std::string, std::shared_ptr<HostTensorND>>;

                std::shared_ptr<ComputingGraph> graph;

                //! name to host tensor used in this graph, usually for input
                //! tensors
                TensorMap tensor_map;

                //! name to output var nodes specified during serializing
                std::unordered_map<std::string, SymbolVar> output_var_map;

                //! map from original id to loaded output vars
                std::unordered_map<size_t, SymbolVar> output_var_map_id;

                //! original output vars in the order passed to
                //! GraphDumper::dump
                SymbolVarArray output_var_list;

                /*!
                 * \brief call graph->compile() but also checks for comp seq rec
                 *
                 * graph would be destructed if comp_node_seq_record_level == 2;
                 * this method should be called in favor of graph->compile().
                 */
                std::unique_ptr<cg::AsyncExecutable> graph_compile(
                        const ComputingGraph::OutputSpec &outspec);
            };

            //! helper to disable inplace arith graph optimization during
            //! de-serialization
            struct ScopedGraphOptDisabler {
                bool option_saved;
                std::shared_ptr<ComputingGraph> cg;
                ScopedGraphOptDisabler(std::shared_ptr<ComputingGraph>& cg_p)
                        : option_saved(true), cg(cg_p) {
                    std::swap(option_saved,
                              cg->options().disable_inplace_arith_opt);
                }
                ~ScopedGraphOptDisabler() {
                    cg->options().disable_inplace_arith_opt = option_saved;
                }
            };

            //! mem_node => tensor_value
            using SharedTensorMapEntry =
                    ThinHashMap<MemNode, std::shared_ptr<DeviceTensorND>>;

            /*!
             * tensor_id => (tensor_name, (mem_node => tensor_value))
             *
             * Since tensor IDs are guaranteed to be consecutive, a vector is
             * used to implement the map.
             *
             * Either all tensor names are empty, or they are guaranteed to be
             * distinct non-empty strings at dump time.
             */
            using SharedTensorIDMap =
                std::vector<std::pair<std::string, SharedTensorMapEntry>>;

            //! tensor_name => SharedTensorMapEntry
            using SharedTensorNameMap = std::unordered_map<
                std::string, const SharedTensorMapEntry*>;

            static std::unique_ptr<GraphLoader> make(
                    std::unique_ptr<InputFile> file,
                    GraphDumpFormat format = {});

            static Maybe<GraphDumpFormat> identify_graph_dump_format(
                    InputFile& file);

            virtual ~GraphLoader() = default;

            /*!
             * \brief reset underlying input file from which further load()
             *      would read
             *
             * This method can be used to release the currently owned file to
             * the caller.
             *
             * \param file new input file, can be null
             * \return original input file that is currently used
             */
            virtual std::unique_ptr<InputFile> reset_file(
                    std::unique_ptr<InputFile> file = {}) = 0;

            /*!
             * \brief create a new graph instance; not thread safe
             * \param rewind whether to call InputFile::rewind before loading
             */
            virtual LoadResult load(const LoadConfig &config = {},
                    bool rewind = true) = 0;

            /*!
             * \brief get mapping from tensor ID to device tensor shared
             *      between instances
             *
             * The shared tensors are usually used as model params in a machine
             * learning context. For each param name, the returned value has a
             * map from a memory node to the first param loaded on that mem node
             */
            virtual const SharedTensorIDMap& shared_tensor_id_map() const = 0;

            //! helper for constructing SharedTensorNameMap from
            //! SharedTensorIDMap
            SharedTensorNameMap shared_tensor_name_map();

            virtual GraphDumpFormat format() const = 0;
    };

    /*!
     * \brief dump graph into given output file
     */
    class GraphDumper {
        public:
            using DumpConfig = GraphDumpConfig;
            struct DumpResult {
                //! number of oprs written
                size_t nr_opr = 0;

                //! hash of the graph
                uint64_t content_hash;

                //! full dump size and param value size
                size_t tot_bytes = 0, tensor_value_bytes = 0;

                std::vector<std::string>
                    inputs,     //!< input tensor names
                    outputs,    //!< output var names
                    params;     //!< dumped param names
            };

            static std::unique_ptr<GraphDumper> make(
                    std::unique_ptr<OutputFile> file,
                    GraphDumpFormat format = {});

            virtual ~GraphDumper() = default;

            /*!
             * \brief whether an operator should be removed in graph
             *      serialization file
             */
            static bool should_remove_in_dump(cg::OperatorNodeBase *opr);

            virtual DumpResult dump(
                    const SymbolVarArray &output_vars,
                    const DumpConfig &config = {}) = 0;
            
            virtual GraphDumpFormat format() const = 0;
    };

} // namespace serialization
} // namespace mgb

// vim: syntax=cpp.doxygen foldmethod=marker foldmarker=f{{{,f}}}

