#include "Console.h"

#include <iostream>

#ifndef WIN32
#    include <termios.h>
#    include <unistd.h>
#endif

using namespace Framework;

ConsoleCommand::ConsoleCommand(Text name)
    : ReferenceCounter(),
      name(name)
{}

ConsoleCommand::~ConsoleCommand() {}

const Text& Framework::ConsoleCommand::getName() const
{
    return name;
}

Framework::StickyConsoleContent::StickyConsoleContent()
    : ReferenceCounter(),
      length(0),
      content(0),
      color(0),
      backgroundColor(0),
      zConsoleHandler(0)
{}

Framework::StickyConsoleContent::~StickyConsoleContent()
{
    delete[] content;
    delete[] color;
    delete[] backgroundColor;
}

int Framework::StickyConsoleContent::getLength() const
{
    return length;
}

void Framework::StickyConsoleContent::setContent(
    int length, const char* content)
{
    delete[] this->content;
    this->content = new char[length];
    memcpy(this->content, content, length);
    this->length = length;
    delete[] color;
    delete[] backgroundColor;
    color = 0;
    backgroundColor = 0;
}

void Framework::StickyConsoleContent::setContent(
    int length, const char* content, Color color)
{
    setContent(length, content);
    this->color = new Color[length];
    for (int i = 0; i < length; i++)
    {
        this->color[i] = color;
    }
}

void Framework::StickyConsoleContent::setContent(
    int length, const char* content, Color color, Color backgroundColor)
{
    setContent(length, content, color);
    this->backgroundColor = new Color[length];
    for (int i = 0; i < length; i++)
    {
        this->backgroundColor[i] = backgroundColor;
    }
}

void Framework::StickyConsoleContent::setContent(
    int length, const char* content, Color* color)
{
    setContent(length, content);
    this->color = new Color[length];
    for (int i = 0; i < length; i++)
    {
        this->color[i] = color[i];
    }
}

void Framework::StickyConsoleContent::setContent(
    int length, const char* content, Color* color, Color* backgroundColor)
{
    setContent(length, content, color);
    this->backgroundColor = new Color[length];
    for (int i = 0; i < length; i++)
    {
        this->backgroundColor[i] = backgroundColor[i];
    }
}

void Framework::StickyConsoleContent::repaceContent(
    int start, int length, int newLength, const char* newContent)
{
    int resultLength = this->length + newLength - length;
    char* resultContent = new char[resultLength];
    memcpy(resultContent, content, start);
    memcpy(resultContent + start, newContent, newLength);
    memcpy(resultContent + start + newLength,
        content + start + length,
        this->length - start - length);
    delete[] content;
    content = resultContent;
    if (color)
    {
        Color* resultColor = new Color[resultLength];
        memcpy(resultColor, color, start);
        for (int i = 0; i < newLength; i++)
        {
            resultColor[start + i] = color[start];
        }
        memcpy(resultColor + start + newLength,
            color + start + length,
            sizeof(Color) * (this->length - start - length));
        delete[] color;
        color = resultColor;
    }
    if (backgroundColor)
    {
        Color* resultBgColor = new Color[resultLength];
        memcpy(resultBgColor, backgroundColor, start);
        for (int i = 0; i < newLength; i++)
        {
            resultBgColor[start + i] = backgroundColor[start];
        }
        memcpy(resultBgColor + start + newLength,
            backgroundColor + start + length,
            sizeof(Color) * (this->length - start - length));
        delete[] backgroundColor;
        backgroundColor = resultBgColor;
    }
    this->length = resultLength;
}

void Framework::StickyConsoleContent::repaceContent(
    int start, int length, int newLength, const char* newContent, Color color)
{
    repaceContent(start, length, newLength, newContent);
    if (!this->color)
    {
        this->color = new Color[this->length];
        for (int i = 0; i < this->length; i++)
        {
            this->color[i] = (Color)-1;
        }
    }
    for (int i = start; i < start + newLength; i++)
    {
        this->color[i] = color;
    }
}

void Framework::StickyConsoleContent::repaceContent(int start,
    int length,
    int newLength,
    const char* newContent,
    Color color,
    Color backgroundColor)
{
    repaceContent(start, length, newLength, newContent, color);
    if (!this->backgroundColor)
    {
        this->backgroundColor = new Color[this->length];
        for (int i = 0; i < this->length; i++)
        {
            this->backgroundColor[i] = (Color)-1;
        }
    }
    for (int i = start; i < start + newLength; i++)
    {
        this->backgroundColor[i] = backgroundColor;
    }
}

void Framework::StickyConsoleContent::repaceContent(
    int start, int length, int newLength, const char* newContent, Color* color)
{
    repaceContent(start, length, newLength, newContent);
    if (!this->color)
    {
        this->color = new Color[this->length];
        for (int i = 0; i < this->length; i++)
        {
            this->color[i] = (Color)-1;
        }
    }
    memcpy(this->color + start, color, sizeof(Color) * newLength);
}

void Framework::StickyConsoleContent::repaceContent(int start,
    int length,
    int newLength,
    const char* newContent,
    Color* color,
    Color* backgroundColor)
{
    repaceContent(start, length, newLength, newContent, color);
    if (!this->backgroundColor)
    {
        this->backgroundColor = new Color[this->length];
        for (int i = 0; i < this->length; i++)
        {
            this->backgroundColor[i] = (Color)-1;
        }
    }
    memcpy(this->backgroundColor + start,
        backgroundColor,
        sizeof(Color) * newLength);
}

void Framework::StickyConsoleContent::triggerUpdate()
{
    if (zConsoleHandler != 0)
    {
        zConsoleHandler->print();
    }
}

bool Framework::StickyConsoleContent::isInput()
{
    return false;
}

void Framework::StickyConsoleContent::setConsoleHandlerZ(
    ConsoleHandler* zConsoleHandler)
{
    this->zConsoleHandler = zConsoleHandler;
}

void Framework::StickyConsoleContent::setCursorToBeginning() {}

int Framework::StickyConsoleContent::print() const
{
    int lineCount = 0;
    int maxWidth = zConsoleHandler->getWidth();
    std::cout << "\r\33[0K"; // erase current line
    int lineLength = 0;
    int lastColor = -1;
    int lastBgColor = -1;
    for (int i = 0; i < length; i++)
    {
        if (color && (int)color[i] != lastColor)
        {
            if ((int)color[i] == -1)
            {
                std::cout << "\033[0m";
                if (backgroundColor && (int)backgroundColor[i] != -1)
                {
                    if (backgroundColor[i] < Color::LIGHT_BLACK)
                    {
                        std::cout << Text("\033[1;")
                                         + Text(40 + (int)backgroundColor[i])
                                         + "m";
                    }
                    else
                    {
                        std::cout
                            << Text("\033[1;")
                                   + Text(100 + (int)backgroundColor[i] - 8)
                                   + "m";
                    }
                }
            }
            else if (color[i] < Color::LIGHT_BLACK)
            {
                std::cout << Text("\033[1;") + Text(30 + (int)color[i]) + "m";
            }
            else
            {
                std::cout << Text("\033[1;") + Text(90 + (int)color[i] - 8)
                                 + "m";
            }
        }
        if (backgroundColor && (int)backgroundColor[i] != lastBgColor)
        {
            if ((int)backgroundColor[i] == -1)
            {
                std::cout << "\033[0m";
                if (color && (int)color[i] != -1)
                {
                    if (color[i] < Color::LIGHT_BLACK)
                    {
                        std::cout
                            << Text("\033[1;") + Text(30 + (int)color[i]) + "m";
                    }
                    else
                    {
                        std::cout << Text("\033[1;")
                                         + Text(90 + (int)color[i] - 8) + "m";
                    }
                }
            }
            else if (backgroundColor[i] < Color::LIGHT_BLACK)
            {
                std::cout << Text("\033[1;")
                                 + Text(40 + (int)backgroundColor[i]) + "m";
            }
            else
            {
                std::cout << Text("\033[1;")
                                 + Text(100 + (int)backgroundColor[i] - 8)
                                 + "m";
            }
        }
        std::cout << content[i];
        lastColor = color ? (int)color[i] : -1;
        lastBgColor = backgroundColor ? (int)backgroundColor[i] : -1;
        if ((content[i] == '\n' || lineLength == maxWidth) && i < length - 1)
        {
            if (lineLength == maxWidth && content[i] != '\n')
            {
                std::cout << "\n";
            }
            lineLength = 0;
            lineCount++;
        }
        else
        {
            lineLength++;
        }
    }
    if (lastColor != -1 || lastBgColor != -1) std::cout << "\033[0m";
    if (lineCount == 0 && lineLength > 0)
    {
        lineCount++;
    }
    return lineCount;
}

void Framework::StickyConsoleContent::restoreCursorPos() {}

ConsoleHandler* Framework::StickyConsoleContent::zConsoleHandlerRef() const
{
    return zConsoleHandler;
}

Framework::ConsoleProgressBar::ConsoleProgressBar()
    : StickyConsoleContent(),
      progress(0),
      maxProgress(1),
      maxWidth(-1)
{}

void Framework::ConsoleProgressBar::setMaxWidth(int maxWidth)
{
    this->maxWidth = maxWidth;
}

void Framework::ConsoleProgressBar::setProgress(int progress)
{
    if (progress < 0) progress = 0;
    this->progress = progress;
}

void Framework::ConsoleProgressBar::setMaxProgress(int maxProgress)
{
    if (maxProgress < 0) maxProgress = 0;
    this->maxProgress = maxProgress;
}

int Framework::ConsoleProgressBar::getProgress() const
{
    return progress;
}

int Framework::ConsoleProgressBar::print() const
{
    int maxWidth = zConsoleHandlerRef()->getWidth();
    int width = this->maxWidth == -1 ? maxWidth : this->maxWidth;
    if (width > maxWidth)
    {
        width = maxWidth;
    }
    if (width < 7)
    {
        return 0;
    }
    std::cout << "\r\33[0K"; // erase current line
    int progress = this->progress > maxProgress ? maxProgress : this->progress;
    int progressChars = width - 7;
    std::cout << "[\033[1;47m";
    int progressWidth = (int)(((double)progress / maxProgress) * progressChars);
    if (progressWidth > progressChars)
    {
        progressWidth = progressChars;
    }
    for (int i = 0; i < progressWidth; i++)
    {
        std::cout << " ";
    }
    std::cout << "\033[0m";
    for (int i = 0; i < progressChars - progressWidth; i++)
    {
        std::cout << " ";
    }
    std::cout << "] ";
    Text str((int)(((double)progress / maxProgress) * 100));
    for (int i = 0; i < 3 - str.getLength(); i++)
    {
        std::cout << " ";
    }
    std::cout << str.getText() << "%";
    return 1;
}

Framework::InputLine::InputLine()
    : StickyConsoleContent(),
      Thread(),
      input(""),
      cursorPos(0),
      suggestions(0)
{}

Framework::InputLine::~InputLine()
{
    suggestions->release();
}

void Framework::InputLine::addPossibleCommand(ConsoleCommand* command)
{
    commands.add(command);
}

bool Framework::InputLine::isInput()
{
    return true;
}

void Framework::InputLine::setCursorToBeginning()
{
    cs.lock();
    std::cout << "\r";
}

int Framework::InputLine::print() const
{
    std::cout << "\33[0K" // clear current line
              << input.getText();
    if (suggestions)
    {
        std::cout << "\n";
        return 1 + dynamic_cast<StickyConsoleContent*>(suggestions)->print();
    }
    return 1;
}

void Framework::InputLine::restoreCursorPos()
{
    if (cursorPos > 0)
    {
        std::cout << Text("\33[") + Text(cursorPos) + "C";
    }
    cs.unlock();
}

void Framework::InputLine::thread()
{
#ifdef WIN32
    INPUT_RECORD inputRecord;
    DWORD eventsRead;
    HANDLE handle = GetStdHandle(STD_INPUT_HANDLE);
    while (true)
    {
        if (ReadConsoleInput(handle, &inputRecord, 1, &eventsRead))
        {
            cs.lock();
            if (eventsRead > 0 && inputRecord.EventType == KEY_EVENT
                && inputRecord.Event.KeyEvent.bKeyDown)
            {
                if (inputRecord.Event.KeyEvent.wVirtualKeyCode == VK_LEFT)
                {
                    cursorPos--;
                    if (cursorPos < 0)
                    {
                        cursorPos = 0;
                    }
                    else
                    {
                        std::cout << "\33[1D" << std::flush;
                    }
                }
                else if (inputRecord.Event.KeyEvent.wVirtualKeyCode == VK_HOME)
                { // Pos1 key moves cursor to beginning of line
                    std::cout << "\r" << std::flush;
                    cursorPos = 0;
                }
                else if (inputRecord.Event.KeyEvent.wVirtualKeyCode == VK_RIGHT)
                {
                    cursorPos++;
                    if (cursorPos > input.getLength())
                    {
                        cursorPos = input.getLength();
                    }
                    else
                    {
                        std::cout << "\33[1C" << std::flush;
                    }
                }
                else if (inputRecord.Event.KeyEvent.wVirtualKeyCode == VK_END)
                { // Pos1 key moves cursor to beginning of line
                    cursorPos = input.getLength();
                    std::cout << Text("\r\33[") + Text(cursorPos) + "C"
                              << std::flush;
                }
                else if (inputRecord.Event.KeyEvent.wVirtualKeyCode == VK_TAB)
                {
                    applyAutocompletion();
                }
                else if (inputRecord.Event.KeyEvent.wVirtualKeyCode
                         == VK_RETURN)
                {
                    std::cout << "\r\33[0K" << std::flush; // eraze line
                    Text cmd = input;
                    cs.unlock();
                    if (executeCommand(cmd))
                    {
                        cs.lock();
                        input = "";
                        cursorPos = 0;
                        cs.unlock();
                        triggerUpdate();
                    }
                    continue; // skip the unlock
                }
                else if (inputRecord.Event.KeyEvent.wVirtualKeyCode == VK_BACK)
                {
                    if (cursorPos > 0)
                    {
                        input.remove(cursorPos - 1, cursorPos);
                        cursorPos--;
                        std::cout << "\r\33[0K"      // eraze line
                                  << input.getText() // output current input
                                  // move cursor to current position
                                  << Text("\r\33[") + Text(cursorPos) + "C"
                                  << std::flush;
                    }
                }
                else if (inputRecord.Event.KeyEvent.uChar.AsciiChar)
                {
                    input.insert(
                        cursorPos, inputRecord.Event.KeyEvent.uChar.AsciiChar);
                    cursorPos++;
                    std::cout << "\r\33[0K"      // eraze line
                              << input.getText() // output current input
                              // move cursor to current position
                              << Text("\r\33[") + Text(cursorPos) + "C"
                              << std::flush;
                }
            }
            cs.unlock();
        }
    }
#else
    char c;
    while (read(STDIN_FILENO, &c, 1) == 1)
    {
        if (c == 27)
        { // Check for the escape key (27 is the ASCII code for escape)
            char seq[2];
            if (read(STDIN_FILENO, &seq, 2) == 2)
            {
                cs.lock();
                if (seq[0] == '[')
                {
                    if (seq[1] == 'D')
                    { // left arrow key
                        cursorPos--;
                        if (cursorPos < 0)
                        {
                            cursorPos = 0;
                        }
                        else
                        {
                            std::cout << "\33[1D" << std::flush;
                        }
                    }
                    else if (seq[1] == 'H')
                    { // Pos1 key moves cursor to beginning of line
                        std::cout << "\r" << std::flush;
                        cursorPos = 0;
                    }
                    else if (seq[1] == 'C')
                    { // right arrow key
                        cursorPos++;
                        if (cursorPos > input.getLength())
                        {
                            cursorPos = input.getLength();
                        }
                        else
                        {
                            std::cout << "\33[1C" << std::flush;
                        }
                    }
                    else if (seq[1] == 'F')
                    { // End key moves cursor to end of line
                        cursorPos = input.getLength();
                        std::cout << Text("\r\33[") + Text(cursorPos) + "C"
                                  << std::flush;
                    }
                }
                cs.unlock();
            }
        }
        else if (c == 8)
        { // backspace
            if (cursorPos > 0)
            {
                cs.lock();
                input.remove(cursorPos - 1, cursorPos);
                cursorPos--;
                std::cout << "\r\33[0K"      // eraze line
                          << input.getText() // output current input
                          // move cursor to current position
                          << Text("\r\33[") + Text(cursorPos) + "C"
                          << std::flush;
                cs.unlock();
            }
        }
        else if (c == 9)
        { // tab
            cs.lock();
            applyAutocompletion();
            cs.unlock();
        }
        else if (c == 13)
        { // enter
            cs.lock();
            std::cout << "\r\33[0K" << std::flush; // eraze line
            Text cmd = input;
            cs.unlock();
            if (executeCommand(cmd))
            {
                cs.lock();
                input = "";
                cursorPos = 0;
                cs.unlock();
            }
        }
        else
        {
            cs.lock();
            input.insert(cursorPos, c);
            cursorPos++;
            std::cout << "\r\33[0K"      // eraze line
                      << input.getText() // output current input
                      // move cursor to current position
                      << Text("\r\33[") + Text(cursorPos) + "C" << std::flush;
            cs.unlock();
        }
    }
#endif
}

void Framework::InputLine::applyAutocompletion()
{
    RCArray<Text> params;
    bool lastArgFinished = 0;
    Text* cmd = input.getTeilText(0, cursorPos);
    parseCommand(*cmd, params, lastArgFinished);
    cmd->release();
    bool paramsStarted = params.getEintragAnzahl() > 1 || lastArgFinished;
    Text* name;
    if (params.getEintragAnzahl() > 0)
    {
        name = params.get(0);
        params.remove(0);
    }
    else
    {
        name = new Text("");
    }
    RCArray<Text> suggestions;
    for (ConsoleCommand* command : commands)
    {
        if ((!paramsStarted && command->getName().hatAt(0, *name))
            || (paramsStarted && command->getName().istGleich(*name)))
        {
            if (paramsStarted)
            {
                command->addAutocompletePossibilities(
                    params, !lastArgFinished, suggestions);
                break;
            }
            else
            {
                suggestions.add(new Text(command->getName()));
            }
        }
    }
    if (this->suggestions)
    {
        this->suggestions->clear();
    }
    else
    {
        this->suggestions = new ConsoleListView();
        this->suggestions->setConsoleHandlerZ(zConsoleHandlerRef());
    }
    for (Text* suggestion : suggestions)
    {
        this->suggestions->addItem(*suggestion);
    }
    if (this->suggestions->getItems().getEintragAnzahl() > 0)
    {
        bool commonChar = true;
        for (int i
             = lastArgFinished
                 ? 0
                 : (params.getEintragAnzahl() > 0
                         ? params.z(params.getEintragAnzahl() - 1)->getLength()
                         : name->getLength());
             true;
             i++)
        {
            if (i >= this->suggestions->getItems().z(0)->getLength())
            {
                commonChar = false;
                break;
            }
            for (int j = 1;
                 j < this->suggestions->getItems().getEintragAnzahl();
                 j++)
            {
                if (i >= this->suggestions->getItems().z(j)->getLength())
                {
                    commonChar = false;
                    break;
                }
                if (this->suggestions->getItems().z(j)->getText()[i]
                    != this->suggestions->getItems().z(0)->getText()[i])
                {
                    commonChar = false;
                    break;
                }
            }
            if (!commonChar)
            {
                break;
            }
            input.insert(
                cursorPos, this->suggestions->getItems().z(0)->getText()[i]);
            cursorPos++;
        }
    }
    name->release();
    if (this->suggestions->getItems().getEintragAnzahl() <= 1)
    {
        this->suggestions->release();
        this->suggestions = 0;
    }
    triggerUpdate();
}

bool Framework::InputLine::executeCommand(Text command)
{
    RCArray<Text> params;
    bool lastArgFinished = 0;
    Text* cmd = input.getTeilText(0, cursorPos);
    if (!parseCommand(*cmd, params, lastArgFinished))
    {
        cmd->release();
        return false;
    }
    cmd->release();
    Text* name;
    if (params.getEintragAnzahl() > 0)
    {
        name = params.get(0);
        params.remove(0);
    }
    else
    {
        name = new Text("");
    }
    RCArray<Text> suggestions;
    for (ConsoleCommand* command : commands)
    {
        if (command->getName().istGleich(*name))
        {
            name->release();
            return command->execute(params);
        }
    }
    name->release();
    return false;
}

bool Framework::InputLine::parseCommand(
    Text command, RCArray<Text>& split, bool& lastFinished)
{
    const char* cmd = command.getText();
    Text str;
    bool insideString = false;
    bool insideString2 = false;
    bool lastEscape = false;
    lastFinished = false;
    while (*cmd)
    {
        if (*cmd == ' ' && !insideString && !insideString2 && !lastEscape)
        {
            if (str.getLength() > 0)
            {
                split.add(new Text(str));
                str = "";
                lastFinished = true;
            }
        }
        else if (*cmd == '"' && !insideString2 && !lastEscape)
        {
            insideString = !insideString;
            lastFinished = !insideString;
        }
        else if (*cmd == '\'' && !insideString && !lastEscape)
        {
            insideString2 = !insideString2;
            lastFinished = !insideString2;
        }
        else if (*cmd == '\\' && !lastEscape)
        {
            lastEscape = true;
            lastFinished = false;
        }
        else
        {
            lastEscape = false;
            str.append(*cmd);
            lastFinished = false;
        }
        cmd++;
    }
    if (str.getLength() > 0)
    {
        split.add(new Text(str));
    }
    return !insideString && !insideString2 && !lastEscape;
}

Framework::ConsoleListView::ConsoleListView()
    : StickyConsoleContent(),
      maxColumns(-1),
      maxVisibleLines(5),
      lineOffset(0)
{}

int Framework::ConsoleListView::getUsedColumns() const
{
    if (!zConsoleHandlerRef()) return 0;
    int maxWidth = zConsoleHandlerRef()->getWidth();
    int columnSize = 0;
    for (Text* item : items)
    {
        if (item->getLength() > columnSize)
        {
            columnSize = item->getLength();
        }
    }
    columnSize += 4;
    if (maxWidth < columnSize)
    {
        return 0;
    }
    int columns = maxWidth / columnSize;
    if (maxColumns > 0 && columns > maxColumns)
    {
        columns = maxColumns;
    }
    if (columns > items.getEintragAnzahl())
    {
        columns = items.getEintragAnzahl();
    }
    return columns;
}

int Framework::ConsoleListView::getNeededLines() const
{
    int columns = getUsedColumns();
    if (!columns)
    {
        return 0;
    }
    if (maxColumns > 0 && columns > maxColumns)
    {
        columns = maxColumns;
    }
    int lines = items.getEintragAnzahl() / columns;
    if (items.getEintragAnzahl() % columns != 0)
    {
        lines++;
    }
    return lines;
}

void Framework::ConsoleListView::setMaxVisibleLines(int maxVisibleLines)
{
    this->maxVisibleLines = maxVisibleLines;
}

void Framework::ConsoleListView::setLineOffset(int lineOffset)
{
    this->lineOffset = lineOffset;
    int maxLines = getNeededLines();
    if (this->lineOffset > maxLines - maxVisibleLines)
    {
        this->lineOffset = maxLines - maxVisibleLines;
    }
    if (this->lineOffset < 0)
    {
        this->lineOffset = 0;
    }
}

void Framework::ConsoleListView::setMaxColumns(int maxColumns)
{
    this->maxColumns = maxColumns;
}

void Framework::ConsoleListView::addItem(Text item)
{
    item.ersetzen("\n", " ");
    int index = 0;
    for (Text* curreent : items)
    {
        int i = 0;
        while (i < curreent->getLength() && i < item.getLength())
        {
            if (curreent->getText()[i] != item.getText()[i])
            {
                break;
            }
            i++;
        }
        if (i == curreent->getLength() && i == item.getLength())
        {
            return; // item already exists
        }
        if (i < curreent->getLength() && i == item.getLength())
        {
            items.add(new Text(item), index);
            return;
        }
        if (i < curreent->getLength() && i < item.getLength()
            && curreent->getText()[i] > item.getText()[i])
        {
            items.add(new Text(item), index);
            return;
        }
        index++;
    }
    items.add(new Text(item));
}

void Framework::ConsoleListView::clear()
{
    items.leeren();
}

const RCArray<Text>& Framework::ConsoleListView::getItems() const
{
    return items;
}

int Framework::ConsoleListView::print() const
{
    int lines = lineOffset;
    int maxLines = getNeededLines();
    if (lines > maxLines - maxVisibleLines)
    {
        lines = maxLines - maxVisibleLines;
    }
    if (lines < 0)
    {
        lines = 0;
    }
    int columns = getUsedColumns();
    if (!columns)
    {
        return 0;
    }
    int columnSize = zConsoleHandlerRef()->getWidth() / columns;
    int printetLines = 0;
    int currentColumn = 0;
    std::cout << "\33[0K"; // clear current line
    for (int i = columns * lines; i < items.getEintragAnzahl(); i++)
    {
        if (i >= columns * (lines + maxVisibleLines))
        {
            break;
        }
        if (currentColumn >= columns)
        {
            std::cout << "\n\r\33[0K";
            printetLines++;
            currentColumn++;
        }
        if (!printetLines)
        {
            printetLines++;
        }
        std::cout << items.z(i)->getText();
        for (int j = items.z(i)->getLength(); j < columnSize; j++)
        {
            std::cout << " ";
        }
        currentColumn++;
    }
    return printetLines;
}

Framework::ConsoleHandler::ConsoleHandler()
    : ReferenceCounter(),
      lines(0),
      lineCounts(0),
      contentCount(0)
{
#ifdef WIN32
    hConsole = GetStdHandle(STD_INPUT_HANDLE);
    SetConsoleMode(hConsole, ENABLE_PROCESSED_INPUT); // set raw mode
    hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
#else
    struct termios t;
    tcgetattr(STDIN_FILENO, &t);
    t.c_lflag &= ~(ICANON | ECHO); // set raw mode
    tcsetattr(STDIN_FILENO, TCSANOW, &t);
#endif
}

Framework::ConsoleHandler::~ConsoleHandler()
{
    for (int i = 0; i < contentCount; i++)
    {
        lines[i]->release();
    }
    delete[] lines;
    delete[] lineCounts;
}

void Framework::ConsoleHandler::addContent(
    StickyConsoleContent* content, ConsoleContentPosition pos)
{
    cs.lock();
    if (content->isInput())
    {
        for (int i = 0; i < contentCount; i++)
        {
            if (lines[i]->isInput())
            {
                content->release();
                cs.unlock();
                throw "There can only be one input line";
            }
        }
    }
    if (content->zConsoleHandler)
    {
        cs.unlock();
        throw "A console content can only be added to one console handler "
              "at "
              "the same time.";
    }
    content->zConsoleHandler = this;
    contentCount++;
    int* lineCounts = new int[contentCount];
    StickyConsoleContent** lines = new StickyConsoleContent*[contentCount];
    if (pos == ConsoleContentPosition::Top)
    {
        lines[0] = content;
        lineCounts[0] = 0;
        for (int i = 1; i < contentCount; i++)
        {
            lines[i] = this->lines[i - 1];
            lineCounts[i] = this->lineCounts[i - 1];
        }
    }
    else
    {
        lines[contentCount - 1] = content;
        lineCounts[contentCount - 1] = content->print();
        for (int i = 0; i < contentCount - 1; i++)
        {
            lines[i] = this->lines[i];
            lineCounts[i] = this->lineCounts[i];
        }
    }
    delete[] this->lines;
    delete[] this->lineCounts;
    this->lines = lines;
    this->lineCounts = lineCounts;
    InputLine* input = dynamic_cast<InputLine*>(content);
    if (input)
    {
        input->start();
    }
    cs.unlock();
}

void Framework::ConsoleHandler::removeContent(StickyConsoleContent* zContent)
{
    cs.lock();
    int index = -1;
    for (int i = 0; i < contentCount; i++)
    {
        if (lines[i] == zContent)
        {
            index = i;
            break;
        }
    }
    if (index == -1)
    {
        cs.unlock();
        return;
    }
    zContent->zConsoleHandler = 0;
    contentCount--;
    int* lineCounts = new int[contentCount];
    StickyConsoleContent** lines = new StickyConsoleContent*[contentCount];
    for (int i = 0; i < index; i++)
    {
        lines[i] = this->lines[i];
        lineCounts[i] = this->lineCounts[i];
    }
    for (int i = index; i < contentCount; i++)
    {
        lines[i] = this->lines[i + 1];
        lineCounts[i] = this->lineCounts[i + 1];
    }
    delete[] this->lines;
    delete[] this->lineCounts;
    this->lines = lines;
    this->lineCounts = lineCounts;
    if (zContent->isInput())
    {
        InputLine* input = dynamic_cast<InputLine*>(zContent);
        if (input)
        {
            input->start();
        }
    }
    zContent->release();
    cs.unlock();
}

int Framework::ConsoleHandler::getWidth() const
{
#ifdef WIN32
    CONSOLE_SCREEN_BUFFER_INFO csbi;
    GetConsoleScreenBufferInfo(hConsole, &csbi);
    return csbi.dwSize.X;
#else
    struct winsize ws;
    if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
    {
        return 0;
    }
    return ws.ws_col;
#endif
}

int Framework::ConsoleHandler::getHeight() const
{
#ifdef WIN32
    CONSOLE_SCREEN_BUFFER_INFO csbi;
    GetConsoleScreenBufferInfo(hConsole, &csbi);
    return csbi.dwSize.Y;
#else
    struct winsize ws;
    if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
    {
        return 0;
    }
    return ws.ws_row;
#endif
}

void Framework::ConsoleHandler::clear()
{
    cs.lock();
    for (int i = 0; i < contentCount; i++)
    {
        lines[i]->release();
    }
    delete[] lines;
    delete[] lineCounts;
    lines = 0;
    lineCounts = 0;
    contentCount = 0;
    cs.unlock();
}

void Framework::ConsoleHandler::print()
{
    cs.lock();
    int totalLines = 0;
    bool adittionalLine = 0;
    for (int i = 0; i < contentCount; i++)
    {
        if (lines[i]->isInput())
        {
            lines[i]->setCursorToBeginning();
            break;
        }
        totalLines += lineCounts[i];
    }
    if (totalLines > 0)
    {
        std::cout << "\33[" << totalLines << "A";
    }
    totalLines = 0;
    for (int i = 0; i < contentCount; i++)
    {
        lineCounts[i] = lines[i]->print();
        if (lineCounts[i] > 0)
        {
            adittionalLine = 0;
            if (i < contentCount - 1)
            {
                std::cout << "\n";
                adittionalLine = 1;
            }
        }
        totalLines += lineCounts[i];
    }
    if (adittionalLine)
    {
        totalLines++;
    }
    std::cout << "\33[" << (totalLines - 1) << "A\r";
    for (int i = 0; i < contentCount; i++)
    {
        if (lines[i]->isInput())
        {
            lines[i]->restoreCursorPos();
            break;
        }
        if (i < contentCount - 1 && lineCounts[i])
        {
            std::cout << "\33[" << lineCounts[i] << "B";
        }
    }
    std::cout << std::flush;
    cs.unlock();
}

void Framework::ConsoleHandler::print(Text str)
{
    std::cout << "\n";
    cs.lock();
    int totalLines = 0;
    bool adittionalLine = 0;
    for (int i = 0; i < contentCount; i++)
    {
        if (lines[i]->isInput())
        {
            lines[i]->setCursorToBeginning();
            break;
        }
        totalLines += lineCounts[i];
    }
    std::cout << "\33[" << totalLines + 1 << "A";
    std::cout << "\33[0K" // clear current line
              << str.getText();
    if (str.getText()[str.getLength() - 1] != '\n')
    {
        std::cout << "\n";
    }
    totalLines = 0;
    for (int i = 0; i < contentCount; i++)
    {
        lineCounts[i] = lines[i]->print();
        if (lineCounts[i] > 0)
        {
            adittionalLine = 0;
            if (i < contentCount - 1)
            {
                std::cout << "\n";
                adittionalLine = 1;
            }
        }
        totalLines += lineCounts[i];
    }
    if (adittionalLine)
    {
        totalLines++;
    }
    std::cout << "\33[" << (totalLines - 1) << "A\r";
    for (int i = 0; i < contentCount; i++)
    {
        if (lines[i]->isInput())
        {
            lines[i]->restoreCursorPos();
            break;
        }
        if (i < contentCount - 1 && lineCounts[i])
        {
            std::cout << "\33[" << lineCounts[i] << "B";
        }
    }
    std::cout << std::flush;
    cs.unlock();
}
