/*
 * Copyright (c) 2015-2017 Carsten Sonne Larsen  <cs@innolan.dk>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include "clib.h"
#include "lib/charbuf.h"
#include "lib/charval.h"
#include "lib/aengine.h"

AnsiConoleEngine::AnsiConoleEngine(const char *prompt, CharValidator *validator)
{
    this->validator = validator;
    AllocAndCopy(&this->prompt, prompt);
    linebuf = new CharBuffer();
    out = new CharBuffer();

    lines = new char*[maxLines];

    for (int i = 0; i < maxLines; i++) {
        lines[i] = NOMEM;
    }

    editline = NOMEM;
    curline = -1;
}

AnsiConoleEngine::~AnsiConoleEngine()
{
    for (int i = 0; i < maxLines; i++) {
        if (lines[i] != NOMEM) {
            delete [] lines[i];
        }
    }

    delete [] lines;
    delete linebuf;
    delete out;
    delete prompt;
}

void AnsiConoleEngine::StartInput()
{
    linebuf->ClearAndAlloc(lineSize + 1);
    len = lineSize;
    cursor = linebuf->buf;
    endpos = cursor;
    *endpos = '\0';

    lineswap = false;
    escmode = false;
    csimode = false;
    delmode = false;
    linedone = false;
}

//#include <real.h>
//#include <numb.h>
//#include <ntext.h>

const char* AnsiConoleEngine::ProcessChar(const unsigned char character)
{
    unsigned char ch = character;
    out->Empty();

    /*
    // -------------- DEUG ------------------
    Number *d = new RealNumber((int)ch);
    NumeralSystem *ns = new DecimalSystem(0);
    const char *dtext = ns->GetText(d);
    StrCopy(out->buf, dtext);
    StrConcat(out->buf, SPACE);
    delete ns;
    delete d;
    return out->buf;
    // -------------- DEUG ------------------
    */

    if (len == 0) {
        // TODO: double buffer
    }

    bool processed = false;

    if (ch == 0) {
        processed = true;
    } else if (ch == 27) {
        escmode = true;
        processed = true;
    } else if (ch == 155 || (escmode && ch == 91)) {
        csimode = true;
        processed = true;
    } else if (csimode) {
        switch (ch) {
        case 65: // Arrow up (27 91 65)
            ShowLast();
            break;
        case 66: // Arrow down (27 91 66)
            ShowNext();
            break;
        case 67: // Arrow right (27 91 67)
            if (cursor != endpos) {
                cursor++;
                out->Append(CURSORFORWARD);
            }
            break;
        case 68: // Arrow left (27 91 68)
            if (cursor != linebuf->buf) {
                cursor--;
                out->Append(CURSORBACKWARD);
            }
            break;
        case 51: // DEL         27 91 51 126
            delmode = true;
        default:
            // F1          27 79 80
            // F2          27 79 81
            break;
        }

        escmode = false;
        csimode = false;
        processed = true;
    } else {
        escmode = false;
        csimode = false;
    }

    // Delete one character to the right
    if (delmode && ch == 126) {
        if (cursor != endpos) {
            char *i = cursor;
            do {
                *i = *(i + 1);
                i++;
            } while (i != endpos);

            len++;
            out->Append(DELETE1CHAR);
            endpos--;
            linebuf->ptr = endpos;
        }

        processed = true;
        delmode = false;
    }

    if (processed) {
        return out->GetString();
    }

    if (ch == 13 || ch == 10) {
        out->Append(NEWLINE);
        linebuf->ptr = endpos;
        CopyLine();
        linedone = true;
    } else if (cursor != linebuf->buf && (ch == 8 || ch == 127)) {
        // Deleting in middle of line
        if (cursor != endpos) {
            char *i = cursor - 1;
            do {
                *i = *(i + 1);
                i++;
            } while (i != endpos);

        }

        len++;
        out->Append(CURSORBACKWARD);
        out->Append(DELETE1CHAR);
        cursor--;
        endpos--;
        linebuf->ptr = endpos;
    } else if (validator->Validate(ch)) {
        // Insert in middle of line
        if (cursor != endpos) {
            char *i = endpos;
            do {
                *i = *(i - 1);
                i--;
            } while (i != cursor);
            out->Append(INSERT1CHAR);
        }

        len--;
        out->Append(ch);
        *cursor++ = ch;
        endpos++;
        linebuf->ptr = endpos;
    }

    return out->GetString();
}

void AnsiConoleEngine::CopyLine()
{
    curline++;

    if (curline == maxLines) {
        curline--;

        delete [] lines[0];
        for (int i = 0; i < maxLines - 1; i++) {
            lines[i] = lines[i + 1];
        }
    }

    AllocAndCopy(&(lines[curline]), linebuf->GetString());

    if (editline != NOMEM) {
        delete [] editline;
        editline = NOMEM;
    }
}

void AnsiConoleEngine::ShowLast()
{
    if (curline == -1) {
        return;
    }

    if (!lineswap) {
        AllocAndCopy(&editline, linebuf->GetString());
        lineswap = true;
        showline = curline + 1;
    } else if (showline == curline + 1) {
        delete editline;
        AllocAndCopy(&editline, linebuf->GetString());
    }

    showline--;
    if (showline < 0) {
        showline = 0;
    }

    out->Empty();
    out->EnsureSize(
        StrLen(DELETELINE) +
        StrLen(prompt) +
        StrLen(lines[showline]) + 1);

    out->Append(DELETELINE);
    out->Append(prompt);
    out->Append(lines[showline]);

    linebuf->Empty();
    linebuf->EnsureSize(StrLen(lines[showline]));
    linebuf->Append(lines[showline]);

    unsigned int linelen = StrLen(linebuf->GetString());
    cursor = linebuf->buf + linelen;
    endpos = cursor;
    len = lineSize - linelen;
}

void AnsiConoleEngine::ShowNext()
{
    if (!lineswap) {
        return;
    }

    showline++;
    if (showline > curline + 1) {
        showline = curline + 1;
        return;
    }

    out->Empty();
    out->Append(DELETELINE);
    out->Append(prompt);

    if (showline > curline) {
        out->EnsureGrowth(StrLen(editline) + 1);
        out->Append(editline);

        linebuf->Empty();
        linebuf->EnsureSize(StrLen(editline));
        linebuf->Append(editline);
    } else {
        out->EnsureGrowth(StrLen(lines[showline]) + 1);
        out->Append(lines[showline]);

        linebuf->Empty();
        linebuf->EnsureSize(StrLen(lines[showline]));
        linebuf->Append(lines[showline]);
    }

    unsigned int linelen = StrLen(linebuf->GetString());
    cursor = linebuf->buf + linelen;
    endpos = cursor;
    len = lineSize - linelen;
}

bool AnsiConoleEngine::InputDone()
{
    return linedone;
}

const char* AnsiConoleEngine::GetLine()
{
    return linebuf->GetString();
}

void AnsiConoleEngine::SetPrompt(const char* string)
{
    delete prompt;
    AllocAndCopy(&prompt, string);
}
