#pragma once

#include <atomic>
#include <bit>
#include <chrono>
#include <map>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include <filesystem>

#include <pl/api.hpp>

#include <pl/core/log_console.hpp>
#include <pl/core/token.hpp>
#include <pl/core/errors/error.hpp>

#include <pl/helpers/types.hpp>

#include <wolv/io/fs.hpp>
#include <wolv/container/interval_tree.hpp>

namespace hex::prv {
    class Provider;
}

namespace pl {

    namespace core {
        class Preprocessor;
        class Lexer;
        class Parser;
        class Validator;
        class Evaluator;

        namespace ast { class ASTNode; }
    }

    namespace ptrn {
        class Pattern;
        class IIterable;
    }

    /**
     * @brief This is the main entry point for the Pattern Language
     * @note The runtime can be reused for multiple executions, but if you want to execute multiple files at once, you should create a new runtime for each file
     * @note Things like the abort function and getter functions to check if the runtime is currently executing code are thread safe. However, the runtime is not thread safe in general
     */
    class PatternLanguage {
    public:

        /**
         * @brief Construct a new Pattern Language object
         * @param addLibStd Whether to add the standard library functions to the language
         */
        explicit PatternLanguage(bool addLibStd = true);
        ~PatternLanguage();

        PatternLanguage(const PatternLanguage&) = delete;
        PatternLanguage(PatternLanguage &&other) noexcept;

        struct Internals {
            std::unique_ptr<core::Preprocessor> preprocessor;
            std::unique_ptr<core::Lexer>        lexer;
            std::unique_ptr<core::Parser>       parser;
            std::unique_ptr<core::Validator>    validator;
            std::unique_ptr<core::Evaluator>    evaluator;
        };

        /**
         * @brief Parses a pattern language code string and returns the generated AST
         * @param code Code to parse
         * @return Generated AST
         */
        [[nodiscard]] std::optional<std::vector<std::shared_ptr<core::ast::ASTNode>>> parseString(const std::string &code);

        /**
         * @brief Executes a pattern language code string
         * @param code Code to execute
         * @param envVars List of environment variables to set
         * @param inVariables List of input variables
         * @param checkResult Whether to check the result of the execution
         * @return True if the execution was successful, false otherwise
         */
        [[nodiscard]] bool executeString(std::string code, const std::map<std::string, core::Token::Literal> &envVars = {}, const std::map<std::string, core::Token::Literal> &inVariables = {}, bool checkResult = true);

        /**
         * @brief Executes a pattern language file
         * @param path Path to the file to execute
         * @param envVars List of environment variables to set
         * @param inVariables List of input variables
         * @param checkResult Whether to check the result of the execution
         * @return True if the execution was successful, false otherwise
         */
        [[nodiscard]] bool executeFile(const std::filesystem::path &path, const std::map<std::string, core::Token::Literal> &envVars = {}, const std::map<std::string, core::Token::Literal> &inVariables = {}, bool checkResult = true);

        /**
         * @brief Executes code as if it was run inside of a function
         * @param code Code to execute
         * @return Pair of a boolean indicating whether the execution was successful and an optional literal containing the return value
         */
        [[nodiscard]] std::pair<bool, std::optional<core::Token::Literal>> executeFunction(const std::string &code);

        /**
         * @brief Aborts the currently running execution asynchronously
         */
        void abort();

        /**
         * @brief Sets the data source for the pattern language
         * @param baseAddress Base address of the data source
         * @param size Size of the data source
         * @param readFunction Function to read data from the data source
         * @param writerFunction Optional function to write data to the data source
         */
        void setDataSource(u64 baseAddress, u64 size, std::function<void(u64, u8*, size_t)> readFunction, std::optional<std::function<void(u64, const u8*, size_t)>> writerFunction = std::nullopt) const;

        /**
         * @brief Sets the base address of the data source
         * @param baseAddress Base address of the data source
         */
        void setDataBaseAddress(u64 baseAddress) const;

        /**
         * @brief Sets the size of the data source
         * @param size Size of the data source
         */
        void setDataSize(u64 size) const;

        /**
         * @brief Sets the default endianess used in the pattern language
         * @param endian Endianess to use
         */
        void setDefaultEndian(std::endian endian);

        /**
         * @brief Sets the initial cursor position used at the start of  execution
         * @param address Initial cursor position
         */
        void setStartAddress(u64 address);


        /**
         * @brief Adds a new pragma preprocessor instruction
         * @param name Name of the pragma
         * @param callback Callback to execute when the pragma is encountered
         */
        void addPragma(const std::string &name, const api::PragmaHandler &callback) const;

        /**
         * @brief Removes a pragma preprocessor instruction
         * @param name Name of the pragma
         */
        void removePragma(const std::string &name) const;

        /**
         * @brief Adds a define to the preprocessor
         * @param name Name of the define
         * @param value Value of the define
         */
        void addDefine(const std::string &name, const std::string &value = "") const;

        /**
         * @brief Sets the include paths for where to look for include files
         * @param paths List of paths to look in
         */
        void setIncludePaths(std::vector<std::fs::path> paths) const;

        /**
         * @brief Registers a callback to be called when a dangerous function is being executed
         * @note The callback should return true if the function should be executed, false otherwise
         * @note Returning false will cause the execution to abort
         * @note If the callback is not set, dangerous functions are disabled
         * @param callback Callback to call
         */
        void setDangerousFunctionCallHandler(std::function<bool()> callback) const;

        /**
         * @brief Sets the console log callback
         * @param callback Callback to call
         */
        void setLogCallback(const core::LogConsole::Callback &callback);

        /**
         * @brief Gets the error that occurred during the last execution
         * @return Error
         */
        [[nodiscard]] const std::optional<core::err::PatternLanguageError> &getError() const;

        /**
         * @brief Gets a map of all out variables and their values that have been defined in the last execution
         * @return Out variables
         */
        [[nodiscard]] std::map<std::string, core::Token::Literal> getOutVariables() const;

        /**
         * @brief Gets the number of created patterns during the last execution
         * @return Number of patterns
         */
        [[nodiscard]] u32 getCreatedPatternCount() const;

        /**
         * @brief Gets the maximum number of patterns allowed to be created
         * @return Maximum number of patterns
         */
        [[nodiscard]] u32 getMaximumPatternCount() const;

        /**
         * @brief Gets the memory of a custom section that was created
         * @param id ID of the section
         * @return Memory of the section
         */
        [[nodiscard]] const std::vector<u8>& getSection(u64 id) const;

        /**
         * @brief Gets all custom sections that were created
         * @return Custom sections
         */
        [[nodiscard]] const std::map<u64, api::Section>& getSections() const;

        /**
         * @brief Gets all patterns that were created in the given section
         * @param section Section Id
         * @return All patterns in the given section
         */
        [[nodiscard]] const std::vector<std::shared_ptr<ptrn::Pattern>> &getPatterns(u64 section = 0x00) const;

        /**
         * @brief Gets all patterns that overlap with the given address
         * @param address Address to check
         * @param section Section id
         * @return Patterns
         */
        [[nodiscard]] std::vector<ptrn::Pattern *> getPatternsAtAddress(u64 address, u64 section = 0x00) const;


        /**
         * @brief Get the colors of all patterns that overlap with the given address
         * @param address Address to check
         * @param section Section id
         * @return Patterns
         */
        [[nodiscard]] std::vector<u32> getColorsAtAddress(u64 address, u64 section = 0x00) const;

        /**
         * @brief Resets the runtime
         */
        void reset();

        /**
         * @brief Checks whether the runtime is currently running
         * @return True if the runtime is running, false otherwise
         */
        [[nodiscard]] bool isRunning() const {
            return this->m_running;
        }

        /**
         * @brief Gets the time the last execution took
         * @return Time the last execution took
         */
        [[nodiscard]] const std::chrono::duration<double> & getLastRunningTime() const {
            return this->m_runningTime;
        }

        /**
         * @brief Adds a new built-in function to the pattern language
         * @param ns Namespace of the function
         * @param name Name of the function
         * @param parameterCount Number of parameters the function takes
         * @param func Callback to execute when the function is called
         */
        void addFunction(const api::Namespace &ns, const std::string &name, api::FunctionParameterCount parameterCount, const api::FunctionCallback &func) const;

        /**
         * @brief Adds a new dangerous built-in function to the pattern language
         * @param ns Namespace of the function
         * @param name Name of the function
         * @param parameterCount Number of parameters the function takes
         * @param func Callback to execute when the function is called
         */
        void addDangerousFunction(const api::Namespace &ns, const std::string &name, api::FunctionParameterCount parameterCount, const api::FunctionCallback &func) const;

        /**
         * @brief Gets the internals of the pattern language
         * @warning Generally this should only be used by "IDEs" or other tools that need to access the internals of the pattern language
         * @return Internals
         */
        [[nodiscard]] const Internals& getInternals() const {
            return this->m_internals;
        }

        /**
         * Adds a new cleanup callback that is called when the runtime is reset
         * @note This is useful for built-in functions that need to clean up their state
         * @param callback Callback to call
         */
        void addCleanupCallback(const std::function<void(PatternLanguage&)> &callback) {
            this->m_cleanupCallbacks.push_back(callback);
        }

        /**
         * @brief Checks whether the patterns are valid
         * @return True if the patterns are valid, false otherwise
         */
        [[nodiscard]] bool arePatternsValid() const {
            return this->m_patternsValid;
        }

        /**
         * @brief Gets the current run id
         * @return Run id
         */
        [[nodiscard]] u64 getRunId() const {
            return this->m_runId;
        }

    private:
        void flattenPatterns();

    private:

        Internals m_internals;
        std::optional<core::err::PatternLanguageError> m_currError;

        std::map<u64, std::vector<std::shared_ptr<ptrn::Pattern>>> m_patterns;
        std::map<u64, wolv::container::IntervalTree<ptrn::Pattern*, u64, 5>> m_flattenedPatterns;
        std::vector<std::function<void(PatternLanguage&)>> m_cleanupCallbacks;
        std::vector<std::shared_ptr<core::ast::ASTNode>> m_currAST;

        std::atomic<bool> m_running = false;
        std::atomic<bool> m_patternsValid = false;
        std::atomic<bool> m_aborted = false;
        std::atomic<u64>  m_runId = 0;

        std::optional<u64> m_startAddress;
        std::endian m_defaultEndian = std::endian::little;
        std::chrono::duration<double> m_runningTime = std::chrono::duration<double>::zero();
    };

}