#pragma once

#include "Array.h"
#include "Critical.h"
#include "ReferenceCounter.h"
#include "Text.h"
#include "Thread.h"

namespace Framework
{
    class ConsoleHandler;

    enum class Color
    {
        BLACK,
        RED,
        GREEN,
        YELLOW,
        BLUE,
        MAGENTA,
        CYAN,
        WHITE,
        LIGHT_BLACK,
        LIGHT_RED,
        LIGHT_GREEN,
        LIGHT_YELLOW,
        LIGHT_BLUE,
        LIGHT_MAGENTA,
        LIGHT_CYAN,
        LIGHT_WHITE,
    };

    /**
     * simple console command interface used for autocompletion and execution of
     * commands
     */
    class ConsoleCommand : public ReferenceCounter
    {
    private:
        Text name;

    public:
        /**
         * creates a new command
         *
         * \param name the name of the command. if the user types this name into
         * the console at the beginning, the command will be executed
         */
        DLLEXPORT ConsoleCommand(Text name);
        DLLEXPORT virtual ~ConsoleCommand();
        /**
         * the name of the command
         *
         * \return the name
         */
        DLLEXPORT const Text& getName() const;
        /**
         * calculates autocomplete posibilities when the user presses the tab
         * key and wants to use this command
         *
         * \param args the current arguments that would be passed to the command
         * if it was executed
         * \param appendToLast true if the last argument is not finished
         * \param possibilities the array to which the
         * possibilities should be added
         */
        virtual void addAutocompletePossibilities(const RCArray<Text>& args,
            bool appendToLast,
            RCArray<Text>& possibilities) const
            = 0;
        /**
         * executes the command
         *
         * \param args the arguments that the user has passed to the command
         * \return true if the command was executed successfully
         */
        virtual bool execute(RCArray<Text>& args) const = 0;
    };

    /**
     * repesents console content that is sticky, meaning it stays in the console
     * even if other messages are printed or commands are executed
     */
    class StickyConsoleContent : public virtual ReferenceCounter
    {
    private:
        int length;
        char* content;
        Color* color;
        Color* backgroundColor;
        ConsoleHandler* zConsoleHandler;

    public:
        DLLEXPORT StickyConsoleContent();
        DLLEXPORT ~StickyConsoleContent();
        /**
         * the current length of the command.
         *
         * \return the length in characters including possible newlines
         */
        DLLEXPORT int getLength() const;
        /**
         * sets the current content of the command. all previous colors will be
         * removed. does not free any memory of the given arguments.
         *
         * \param length the new length in characters
         * \param content the new content
         */
        DLLEXPORT void setContent(int length, const char* content);
        /**
         * sets the current content of the command. all previous colors will be
         * removed. does not free any memory of the given arguments.
         *
         * \param length the new length in characters
         * \param content the new content
         * \param color the font color of the content
         */
        DLLEXPORT void setContent(int length, const char* content, Color color);
        /**
         * sets the current content of the command. all previous colors will be
         * removed. does not free any memory of the given arguments.
         *
         * \param length the new length in characters
         * \param content the new content
         * \param color the font color of the content
         * \param backgroundColor the background color of the content
         */
        DLLEXPORT void setContent(int length,
            const char* content,
            Color color,
            Color backgroundColor);
        /**
         * sets the current content of the command. all previous colors will be
         * removed. does not free any memory of the given arguments.
         *
         * \param length the new length in characters
         * \param content the new content
         * \param color array of font colors for each character
         */
        DLLEXPORT void setContent(
            int length, const char* content, Color* color);
        /**
         * sets the current content of the command. all previous colors will be
         * removed. does not free any memory of the given arguments.
         *
         * \param length the new length in characters
         * \param content the new content
         * \param color array of font colors for each character
         * \param backgroundColor array of background colors for each character
         */
        DLLEXPORT void setContent(int length,
            const char* content,
            Color* color,
            Color* backgroundColor);
        /**
         * replaces a part of the current content. the colors of the current
         * start position will be used for the new content. does not free any
         * memory of the given arguments.
         *
         * \param start the start position of the replacement
         * \param length the length of the content that should be replaced
         * \param newLength the length of the new content that should be
         * inserted instead of the old content
         * \param newContent the new content
         */
        DLLEXPORT void repaceContent(
            int start, int length, int newLength, const char* newContent);
        /**
         * replaces a part of the current content. the backgound color of the
         * current start position will be used for the new content. does not
         * free any memory of the given arguments.
         *
         * \param start the start position of the replacement
         * \param length the length of the content that should be replaced
         * \param newLength the length of the new content that should be
         * inserted instead of the old content
         * \param newContent the new content
         * \param color the font color of the new content
         */
        DLLEXPORT void repaceContent(int start,
            int length,
            int newLength,
            const char* newContent,
            Color color);
        /**
         * replaces a part of the current content. does not
         * free any memory of the given arguments.
         *
         * \param start the start position of the replacement
         * \param length the length of the content that should be replaced
         * \param newLength the length of the new content that should be
         * inserted instead of the old content
         * \param newContent the new content
         * \param color the font color of the new content
         * \param backgroundColor the background color of the new content
         */
        DLLEXPORT void repaceContent(int start,
            int length,
            int newLength,
            const char* newContent,
            Color color,
            Color backgroundColor);
        /**
         * replaces a part of the current content. the backgound color of the
         * current start position will be used for the new content. does not
         * free any memory of the given arguments.
         *
         * \param start the start position of the replacement
         * \param length the length of the content that should be replaced
         * \param newLength the length of the new content that should be
         * inserted instead of the old content
         * \param newContent the new content
         * \param color array the font colorc of the new content for each
         * character
         */
        DLLEXPORT void repaceContent(int start,
            int length,
            int newLength,
            const char* newContent,
            Color* color);
        /**
         * replaces a part of the current content. does not
         * free any memory of the given arguments.
         *
         * \param start the start position of the replacement
         * \param length the length of the content that should be replaced
         * \param newLength the length of the new content that should be
         * inserted instead of the old content
         * \param newContent the new content
         * \param color array the font colorc of the new content for each
         * character
         * \param backgroundColor the background color of the new content for
         * each character
         */
        DLLEXPORT void repaceContent(int start,
            int length,
            int newLength,
            const char* newContent,
            Color* color,
            Color* backgroundColor);
        /**
         * triggers reprinting of all sticky console content.
         */
        DLLEXPORT void triggerUpdate();
        /**
         * true if the content is a input line where the user can type in
         *
         * \param true if the user can edit this content
         */
        DLLEXPORT virtual bool isInput();

    public:
        /**
         * prints the content to the console
         *
         * \return number of lines printed
         */
        DLLEXPORT virtual int print() const;
        /**
         * sets the console handler instance where this content will be printed
         * to. This will automatically be done if the content is added to a
         * consolehandler.
         *
         * \param zConsoleHandler the handler instance without increasing the
         * reference counter
         */
        DLLEXPORT void setConsoleHandlerZ(ConsoleHandler* zConsoleHandler);

    protected:
        /**
         * called before all sticky console content is reprinted when isInput
         * is true. restoreCursorPos() must be called after this
         */
        DLLEXPORT virtual void setCursorToBeginning();
        /**
         * reprints all input from the user so that the user can edit the
         * content again. can only be called after setCursorToBeginning was
         * called
         */
        DLLEXPORT virtual void restoreCursorPos();

        ConsoleHandler* zConsoleHandlerRef() const;

    public:
        friend ConsoleHandler;
    };

    /**
     * displays a progress bar in the console.
     */
    class ConsoleProgressBar : public StickyConsoleContent
    {
    private:
        int progress;
        int maxProgress;
        int maxWidth;

    public:
        DLLEXPORT ConsoleProgressBar();
        /**
         * sets the max used with of the progress bar in charachters including
         * the percentage number.
         *
         * \param maxWidth the width in characters
         */
        DLLEXPORT void setMaxWidth(int maxWidth);
        /**
         * sets the current progress.
         *
         * \param progress the progress
         */
        DLLEXPORT void setProgress(int progress);
        /**
         * sets the max progress steps needed to reach 100% progress.
         *
         * \param maxProgress max progress
         */
        DLLEXPORT void setMaxProgress(int maxProgress);

        /**
         * the current progress.
         *
         * \return the progress
         */
        DLLEXPORT int getProgress() const;

    protected:
        /**
         * prints the progress bar to the console.
         *
         * \return number of lines printed
         */
        DLLEXPORT virtual int print() const override;
    };

    class ConsoleListView;

    /**
     * a command input line where the user can type in commands. the user can
     * type the tab key for autocompletion.
     * ConsoleListView is used for displaying the autocompletion possibilities.
     */
    class InputLine : public StickyConsoleContent,
                      private Thread
    {
    private:
        int cursorPos;
        RCArray<ConsoleCommand> commands;
        Critical cs;

    protected:
        Framework::Text input;
        ConsoleListView* suggestions;

    public:
        DLLEXPORT InputLine();
        DLLEXPORT ~InputLine();
        /**
         * ads a command to the list of possible commands used for
         * autocompletion and execution.
         *
         * \param command the command to add
         */
        DLLEXPORT void addPossibleCommand(ConsoleCommand* command);
        /**
         * returns true because the user can edit the content of this line.
         *
         * \return true
         */
        DLLEXPORT virtual bool isInput() override;

    protected:
        /**
         * called before all sticky console content is reprinted when isInput
         * is true. restoreCursorPos() must be called after this
         */
        DLLEXPORT virtual void setCursorToBeginning();
        /**
         * prints the input line to the console. restoreCursorPos must be called
         * later
         *
         * \return number of lines printed
         */
        DLLEXPORT virtual int print() const override;
        /**
         * reprints all input from the user so that the user can edit the
         * content again. can only be called after setCursorToBeginning was
         * called
         */
        DLLEXPORT virtual void restoreCursorPos() override;

        /**
         * writes the next autocomplete step to input or displays a list of
         * possibilities in suggestions.
         *
         */
        DLLEXPORT void applyAutocompletion();
        /**
         * executes a given command.
         *
         * \param command the command
         * \return true if the command was executed successfully
         */
        DLLEXPORT bool executeCommand(Text command);
        /**
         * splits a given command into its name and arguments.
         *
         * \param command the command to split
         * \param split the array with the name and arguments
         * \param lastFinished true if the last argument is finished (folowed by
         * a witespace or ending character)
         * \return true if the command was split successfully
         */
        DLLEXPORT bool parseCommand(
            Text command, RCArray<Text>& split, bool& lastFinished);

    private:
        DLLEXPORT void thread() override;

        friend ConsoleHandler;
    };

    /**
     * prints a list of words to the console. the list will be printed ordered
     * by ascii value. it can be organized in multiple columns.
     */
    class ConsoleListView : public StickyConsoleContent
    {
    private:
        int maxColumns;
        RCArray<Text> items;
        int maxVisibleLines;
        int lineOffset;

    public:
        DLLEXPORT ConsoleListView();
        /**
         * the number of columns used to display the complete list of words.
         *
         * \return the number of columns
         */
        DLLEXPORT int getUsedColumns() const;
        /**
         * the number of lines needed to display the complete list of words.
         *
         * \return the number of lines
         */
        DLLEXPORT int getNeededLines() const;
        /**
         * specifies the maximum amount of lines visible.
         *
         * \param maxVisibleLines the amount of lines
         */
        DLLEXPORT void setMaxVisibleLines(int maxVisibleLines);
        /**
         * specifies the offset of the first line that should be printed.
         *
         * \param lineOffset the index of the first line that is printed
         */
        DLLEXPORT void setLineOffset(int lineOffset);
        /**
         * specifies the maximum amount of columns used to display the list of
         * words.
         *
         * \param maxColumns the amount of columns
         */
        DLLEXPORT void setMaxColumns(int maxColumns);
        /**
         * adds a word to the list of words.
         *
         * \param item the word to add
         */
        DLLEXPORT void addItem(Text item);
        /**
         * removes all words from the list.
         */
        DLLEXPORT void clear();
        /**
         * the items in the list view.
         *
         * \return items
         */
        DLLEXPORT const RCArray<Text>& getItems() const;

    protected:
        /**
         * prints the list of words to the console.
         *
         * \return number of lines printed
         */
        DLLEXPORT virtual int print() const override;
    };

    enum class ConsoleContentPosition
    {
        /**
         * before all other sticky console content.
         */
        Top,
        /**
         * after all pther sticky console content.
         */
        Bottom,
    };

    class ConsoleHandler : public ReferenceCounter
    {
    private:
        StickyConsoleContent** lines;
        int* lineCounts;
        int contentCount;
        Critical cs;
#ifdef WIN32
        HANDLE hConsole;
#endif

    public:
        DLLEXPORT ConsoleHandler();
        DLLEXPORT ~ConsoleHandler();
        /**
         * adds a sticky console content to the console at a specified position.
         *
         * \param content the new content
         * \param pos the position of the new content
         */
        DLLEXPORT void addContent(
            StickyConsoleContent* content, ConsoleContentPosition pos);
        /**
         * removes a sticky console content from the list of displayed sticky
         * console content.
         *
         * \param zContent the content to remove without increasing the
         * reference counter
         */
        DLLEXPORT void removeContent(StickyConsoleContent* zContent);
        /**
         * the current with of the console window in characters.
         *
         * \return the with in characters
         */
        DLLEXPORT int getWidth() const;
        /**
         * the current height of the console window in characters.
         *
         * \return the height in characters
         */
        DLLEXPORT int getHeight() const;
        /**
         * removes all sticky console content from the list of displayed sticky
         * console content.
         */
        DLLEXPORT void clear();
        /**
         * reprints all sticky console content that is currently displayed.
         */
        DLLEXPORT void print();
        /**
         * prints something to the console at the position of the first sticky
         * console content and reprints all sticky console content below the
         * newly printed content.
         *
         * \param str the string to print
         */
        DLLEXPORT void print(Text str);

    public:
        friend InputLine;
    };
} // namespace Framework
