/*
* MIT License
*
* Copyright(c) 2018 Jimmie Bergmann
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files(the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions :
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/

/*
YAML documentation:
http://yaml.org/spec/1.0/index.html
https://www.codeproject.com/Articles/28720/YAML-Parser-in-C
*/

#pragma once

#include <exception>
#include <string>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <map>

/**
* @breif Namespace wrapping mini-yaml classes.
*
*/
namespace Yaml
{

    /**
    * @breif Forward declarations.
    *
    */
    class Node;


    /**
    * @breif Helper classes and functions
    *
    */
    namespace impl
    {

        /**
        * @breif Helper functionality, converting string to any data type.
        *        Strings are left untouched.
        *
        */
        template<typename T>
        struct StringConverter
        {
            static T Get(const std::string & data)
            {
                T type;
                std::stringstream ss(data);
                ss >> type;
                return type;
            }

            static T Get(const std::string & data, const T & defaultValue)
            {
                T type;
                std::stringstream ss(data);
                ss >> type;

                if(ss.fail())
                {
                    return defaultValue;
                }

                return type;
            }
        };
        template<>
        struct StringConverter<std::string>
        {
            static std::string Get(const std::string & data)
            {
                return data;
            }

            static std::string Get(const std::string & data, const std::string & defaultValue)
            {
                if(data.size() == 0)
                {
                    return defaultValue;
                }
                return data;
            }
        };

        template<>
        struct StringConverter<bool>
        {
            static bool Get(const std::string & data)
            {
                std::string tmpData = data;
                std::transform(tmpData.begin(), tmpData.end(), tmpData.begin(), ::tolower);
                if(tmpData == "true" || tmpData == "yes" || tmpData == "1")
                {
                    return true;
                }

                return false;
            }

            static bool Get(const std::string & data, const bool & defaultValue)
            {
                if(data.size() == 0)
                {
                    return defaultValue;
                }

                return Get(data);
            }
        };

    }


    /**
    * @breif Exception class.
    *
    */
    class Exception : public std::runtime_error
    {

    public:

        /**
        * @breif Enumeration of exception types.
        *
        */
        enum eType
        {
            InternalError,  ///< Internal error.
            ParsingError,   ///< Invalid parsing data.
            OperationError  ///< User operation error.
        };

        /**
        * @breif Constructor.
        *
        * @param message    Exception message.
        * @param type       Type of exception.
        *
        */
        Exception(const std::string & message, const eType type);

        /**
        * @breif Get type of exception.
        *
        */
        eType Type() const;

        /**
        * @breif Get message of exception.
        *
        */
        const char * Message() const;

    private:

        eType m_Type;   ///< Type of exception.

    };


    /**
    * @breif Internal exception class.
    *
    * @see Exception
    *
    */
    class InternalException : public Exception
    {

    public:

        /**
        * @breif Constructor.
        *
        * @param message Exception message.
        *
        */
        InternalException(const std::string & message);

    };


    /**
    * @breif Parsing exception class.
    *
    * @see Exception
    *
    */
    class ParsingException : public Exception
    {

    public:

        /**
        * @breif Constructor.
        *
        * @param message Exception message.
        *
        */
        ParsingException(const std::string & message);

    };


    /**
    * @breif Operation exception class.
    *
    * @see Exception
    *
    */
    class OperationException : public Exception
    {

    public:

        /**
        * @breif Constructor.
        *
        * @param message Exception message.
        *
        */
        OperationException(const std::string & message);

    };


    /**
    * @breif Iterator class.
    *
    */
    class Iterator
    {

    public:

        friend class Node;

        /**
        * @breif Default constructor.
        *
        */
        Iterator();

        /**
        * @breif Copy constructor.
        *
        */
        Iterator(const Iterator & it);

        /**
        * @breif Assignment operator.
        *
        */
        Iterator & operator = (const Iterator & it);

        /**
        * @breif Destructor.
        *
        */
        ~Iterator();

        /**
        * @breif Get node of iterator.
        *        First pair item is the key of map value, empty if type is sequence.
        *
        */
        std::pair<const std::string &, Node &> operator *();

        /**
        * @breif Post-increment operator.
        *
        */
        Iterator & operator ++ (int);

        /**
        * @breif Post-decrement operator.
        *
        */
        Iterator & operator -- (int);

        /**
        * @breif Check if iterator is equal to other iterator.
        *
        */
        bool operator == (const Iterator & it);

        /**
        * @breif Check if iterator is not equal to other iterator.
        *
        */
        bool operator != (const Iterator & it);

    private:

        enum eType
        {
            None,
            SequenceType,
            MapType
        };

        eType   m_Type; ///< Type of iterator.
        void *  m_pImp; ///< Implementation of iterator class.

    };


    /**
    * @breif Constant iterator class.
    *
    */
    class ConstIterator
    {

    public:

        friend class Node;

        /**
        * @breif Default constructor.
        *
        */
        ConstIterator();

        /**
        * @breif Copy constructor.
        *
        */
        ConstIterator(const ConstIterator & it);

        /**
        * @breif Assignment operator.
        *
        */
        ConstIterator & operator = (const ConstIterator & it);

        /**
        * @breif Destructor.
        *
        */
        ~ConstIterator();

        /**
        * @breif Get node of iterator.
        *        First pair item is the key of map value, empty if type is sequence.
        *
        */
        std::pair<const std::string &, const Node &> operator *();

        /**
        * @breif Post-increment operator.
        *
        */
        ConstIterator & operator ++ (int);

        /**
        * @breif Post-decrement operator.
        *
        */
        ConstIterator & operator -- (int);

        /**
        * @breif Check if iterator is equal to other iterator.
        *
        */
        bool operator == (const ConstIterator & it);

        /**
        * @breif Check if iterator is not equal to other iterator.
        *
        */
        bool operator != (const ConstIterator & it);

    private:

        enum eType
        {
            None,
            SequenceType,
            MapType
        };

        eType   m_Type; ///< Type of iterator.
        void *  m_pImp; ///< Implementation of constant iterator class.

    };


    /**
    * @breif Node class.
    *
    */
    class Node
    {

    public:

        friend class Iterator;

        /**
        * @breif Enumeration of node types.
        *
        */
        enum eType
        {
            None,
            SequenceType,
            MapType,
            ScalarType
        };

        /**
        * @breif Default constructor.
        *
        */
        Node();

        /**
        * @breif Copy constructor.
        *
        */
        Node(const Node & node);

        /**
        * @breif Assignment constructors.
        *        Converts node to scalar type if needed.
        *
        */
        Node(const std::string & value);
        Node(const char * value);

        /**
        * @breif Destructor.
        *
        */
        ~Node();

        /**
        * @breif Functions for checking type of node.
        *
        */
        eType Type() const;
        bool IsNone() const;
        bool IsSequence() const;
        bool IsMap() const;
        bool IsScalar() const;

        /**
        * @breif Completely clear node.
        *
        */
        void Clear();

        /**
        * @breif Get node as given template type.
        *
        */
        template<typename T>
        T As() const
        {
            return impl::StringConverter<T>::Get(AsString());
        }

        /**
        * @breif Get node as given template type.
        *
        */
        template<typename T>
        T As(const T & defaultValue) const
        {
            return impl::StringConverter<T>::Get(AsString(), defaultValue);
        }

        /**
        * @breif Get size of node.
        *        Nodes of type None or Scalar will return 0.
        *
        */
        size_t Size() const;

        // Sequence operators

        /**
        * @breif Insert sequence item at given index.
        *        Converts node to sequence type if needed.
        *        Adding new item to end of sequence if index is larger than sequence size.
        *
        */
        Node & Insert(const size_t index);

        /**
        * @breif Add new sequence index to back.
        *        Converts node to sequence type if needed.
        *
        */
        Node & PushFront();

         /**
        * @breif Add new sequence index to front.
        *        Converts node to sequence type if needed.
        *
        */
        Node & PushBack();

        /**
        * @breif    Get sequence/map item.
        *           Converts node to sequence/map type if needed.
        *
        * @param index  Sequence index. Returns None type Node if index is unknown.
        * @param key    Map key. Creates a new node if key is unknown.
        *
        */
        Node & operator []  (const size_t index);
        Node & operator [] (const std::string & key);

        /**
        * @breif Erase item.
        *        No action if node is not a sequence or map.
        *
        */
        void Erase(const size_t index);
        void Erase(const std::string & key);

        /**
        * @breif Assignment operators.
        *
        */
        Node & operator = (const Node & node);
        Node & operator = (const std::string & value);
        Node & operator = (const char * value);

        /**
        * @breif Get start iterator.
        *
        */
        Iterator Begin();
        ConstIterator Begin() const;

        /**
        * @breif Get end iterator.
        *
        */
        Iterator End();
        ConstIterator End() const;


    private:

        /**
        * @breif Get as string. If type is scalar, else empty.
        *
        */
        const std::string & AsString() const;

        void * m_pImp; ///< Implementation of node class.

    };


    /**
    * @breif Parsing functions.
    *        Population given root node with deserialized data.
    *
    * @param root       Root node to populate.
    * @param filename   Path of input file.
    * @param stream     Input stream.
    * @param string     String of input data.
    * @param buffer     Char array of input data.
    * @param size       Buffer size.
    *
    * @throw InternalException  An internal error occurred.
    * @throw ParsingException   Invalid input YAML data.
    * @throw OperationException If filename or buffer pointer is invalid.
    *
    */
    void Parse(Node & root, const char * filename);
    void Parse(Node & root, std::iostream & stream);
    void Parse(Node & root, const std::string & string);
    void Parse(Node & root, const char * buffer, const size_t size);


    /**
    * @breif    Serialization configuration structure,
    *           describing output behavior.
    *
    */
    struct SerializeConfig
    {

        /**
        * @breif Constructor.
        *
        * @param spaceIndentation       Number of spaces per indentation.
        * @param scalarMaxLength        Maximum length of scalars. Serialized as folder scalars if exceeded.
        *                               Ignored if equal to 0.
        * @param sequenceMapNewline     Put maps on a new line if parent node is a sequence.
        * @param mapScalarNewline       Put scalars on a new line if parent node is a map.
        *
        */
        SerializeConfig(const size_t spaceIndentation = 2,
                        const size_t scalarMaxLength = 64,
                        const bool sequenceMapNewline = false,
                        const bool mapScalarNewline = false);

        size_t SpaceIndentation;    ///< Number of spaces per indentation.
        size_t ScalarMaxLength;     ///< Maximum length of scalars. Serialized as folder scalars if exceeded.
        bool SequenceMapNewline;    ///< Put maps on a new line if parent node is a sequence.
        bool MapScalarNewline;      ///< Put scalars on a new line if parent node is a map.
    };


    /**
    * @breif Serialization functions.
    *
    * @param root       Root node to serialize.
    * @param filename   Path of output file.
    * @param stream     Output stream.
    * @param string     String of output data.
    * @param config     Serialization configurations.
    *
    * @throw InternalException  An internal error occurred.
    * @throw OperationException If filename or buffer pointer is invalid.
    *                           If config is invalid.
    *
    */
    void Serialize(const Node & root, const char * filename, const SerializeConfig & config = SerializeConfig(2, 64, false, false));
    void Serialize(const Node & root, std::iostream & stream, const SerializeConfig & config = SerializeConfig(2, 64, false, false));
    void Serialize(const Node & root, std::string & string, const SerializeConfig & config = SerializeConfig(2, 64, false, false));

}
