/* ------------------------- input2.c ---------------------------------------- *
 * Version 2.3  31.12.2021, C port: 17.01.2023                                 *
 *                                                                             *
 *  Include-File fuer Eingabebefehle auf der JOYCE unter C.                    *
 *                                                                             *
 * Abhaengigkeiten: convert.h                                                  *
 *                                                                             *
 *  Es sind enthalten:                                                         *
 *     void setDecimalChar       -> Setzt das Dezimalzeichen ('.' oder ',')    *
 *     void writeKeyMessage      -> Schreibt eine Tastendruck-Meldung          *
 *     void waitForKey           -> Wartet auf einen Tastendruck               *
 *     void getKey               -> Liest Zeichen per Tastendruck              *
 *     void beep                 -> Erzeugt einen Ton                          *
 *     void isFieldInputFinished -> Ende-Taste gedrueckt?                      *
 *     void inputString          -> Eingabe einer Textzeile                    *
 *     void home                 -> Setzt den Cursor in die linke obere Ecke   *
 *     void clearScreen          -> Loescht den Bildschirm                     *
 *     void gotoXY               -> Setzt den Cursor an Position X,Y           *
 *     void clearEol             -> Loescht vom Cursor bis zum Zeilenende      *
 *     void inverseVideo         -> Schaltet auf inverse Darstellung um        *
 *     void normalVideo          -> Schaltet inverse Darstellung aus           *
 *     void hideCursor           -> Macht den Cursor unsichtbar                *
 *     void showCursor           -> Macht den Cursor sichtbar                  *
 *     void statusBarOff         -> Schaltet die Statuszeile aus               *
 *     void statusBarOn          -> Schaltet die Statuszeile an                *
 * --------------------------------------------------------------------------- */

#include <stdio.h>
#include <string.h>
#include <conio.h>
#include "input2.h"

char input_decimalChar;
char input_lastChar;


// Sets the decimal character.
void setDecimalChar(char ch) {
    input_decimalChar = ch;
}


// Writes a 'Press a key' message to the screen.
void writeKeyMessage() {
    printf("<Press a key>");
}


// Waits for a key to be pressed.
void waitForKey() {
  getch();
}


// Waits for a key to be pressed and returns the character typed.
char getKey() {
    return getch();
}


// Emits a beep through the loudspeaker.
void beep() {
    putchar(CH_BEEP);
}


// Deletes the character at position pos.
void strDeleteChar(char *s, int pos) {
    char *dest;
    
    if (pos < strlen(s)) {
        dest = &s[pos];
        memmove(dest, dest + 1, strlen(dest) + 1);
    }
}

// Inserts a character at position pos.
void strInsertChar(char *s, int pos, char ch) {
    char *src;
    
    if (pos <= strlen(s)) {
        src = &s[pos];
        memmove(src + 1, src, strlen(src) + 1);
        *src = ch;
    }
}


// Checks if the input of a field was finished.
bool isFieldInputFinished(char ch) {
  return
        ch == CH_RETURN
     || ch == CH_TAB
     || ch == CH_DOWN
     || ch == CH_UP;
}


// Checks if the character is a digit ('0' - '9').
bool isDigit(char ch) {
    return ch >= '0' && ch <= '9';
}


// Gets the position of the character ch in the string s.
// Returns -1 if ch was not found in s.
int strpos(char ch, char *s) {
    char *spos;
    spos = strchr(s, ch);
    
    return spos == NULL ? -1 : spos - s;
}


/* Checks if the character is valid for the field type.
   ch     the character to check
   p      ch's position in the work string (0-based)
   ftype  field type
   s      work string
*/
bool isValid(char ch, int p, FieldType ftype, char *s) {
    bool result;
    int dotPos;
    int maxLen;
    
    result = false;
    
    switch (ftype) {
        case FTText:
            result = input_lastChar > 31 && input_lastChar < 126;
            break;
        case FTNumNatural:
            result = isDigit(ch);
            break;
        case FTInteger:
            if (p == 0) {
                result = isDigit(ch) || ch == '+' || ch == '-';
            }
            else {
                result = isDigit(ch);
            }
            break;
        case FTReal:
            if (p == 0) {
                result = isDigit(ch) || ch == '+' || ch == '-';
            }
            else { // p > 0
                if (isDigit(ch)) {
                    result = true;
                }
                else
                if (ch == input_decimalChar) {
                    result = (strpos(input_decimalChar, s) == -1);
                }
                else
                if (ch == 'E' || ch == 'e') {
                    result = (strpos('E', s) == -1 && strpos('e', s) == -1);
                }
                else
                if (ch == '+' || ch == '-') {
                    result = (s[p - 1] == 'E' || s[p - 1] == 'e');
                }
                else
                    result = false;
            }
            break;
        case FTDate:
            switch (p) {
                case  0: result = (ch >= '0' && ch <= '3');
                    break;
                case  1:
                case  4:
                case  6:
                case  7:
                case  8:
                case  9: result = isDigit(ch);
                    break;
                case  3: result = (ch == '0' || ch == '1');
                    break;
                default: result = (ch == '.');
            }
            break;
        case FTFileName:
            switch (ch) {
                case '.': result = (strpos('.', s) == -1);
                    break;
                case ':': result = (strpos(':', s) == -1 && p == 1);
                    break;
                default:
                    if (ch > 31 && ch < 126) {
                        if (s[1] == ':')
                            maxLen = 10;
                        else
                            maxLen = 8;
                        dotPos = strpos('.', s);
                        if (dotPos == -1) { // no dot yet
                            result = strlen(s) < maxLen; // max. 8 or 10 chars before the dot
                        }
                        else { // dot already typed *)
                            result = strlen(s) - dotPos < 4; // max. 3 chars after the dot
                        }
                    }
                    else {
                        result = false;
                    }
            }
            break;
    }
    
    return result;
}


// Writes a String starting at the position.
void writeFromPos(char *s, int position) {
    char *s2;

    s2 = s + position;
    printf(s2);
}


/* Inputs a String in a dynamically allocated character array.
   cp      character pointer to the pre-allocated input memory
   maxlen  maximum length allowed for input
   x       x position on screen (zero-based)
   y       y position on screen (zero-based)
   ftype   field type
*/
void inputString(char *cp, int maxlen, int x, int y, FieldType ftype) {
    int p;
    short b;
    
    p = strlen(cp);
    gotoXY(x + p, y);
    do {
        input_lastChar = getch();
        
        switch (input_lastChar) {
            case CH_DEL_LEFT: // <--
                if (strlen(cp) > 0 && p > 0) {
                    p--;
                    strDeleteChar(cp, p);
                    gotoXY(x + p, y);
                    writeFromPos(cp, p);
                    putchar(PLACEHOLDER); // because we deleted one char
                    gotoXY(x + p, y);
                }
                else {
                    beep();
                }
                break;
            case CH_DEL_RIGHT: // Entf
                if (strlen(cp) > 0 && p < strlen(cp)) {
                    strDeleteChar(cp, p);
                    writeFromPos(cp, p);
                    putchar(PLACEHOLDER); // because we deleted one char
                    gotoXY(x + p, y);
                }
                else {
                    beep();
                }
                break;
            case CH_LEFT: // left
                if (p > 0) {
                    p--;
                    putchar(CH_BACKSPACE); // cursor left
                }
                else {
                    beep();
                }
                break;
            case CH_RIGHT: // right
                if (p < strlen(cp)) {
                    putchar(cp[p]);
                    p++;
                }
                else {
                    beep();
                }
                break;
            case CH_CAN: // CAN button (Bild^)
                gotoXY(x, y);
                for (b = 0; b < strlen(cp); b++) putchar(PLACEHOLDER);
                gotoXY(x, y);
                *cp = '\0';
                p = 0;
                break;
            default:
                if (strlen(cp) < maxlen && input_lastChar > 31) {
                    putchar(input_lastChar);
                    if (isValid(input_lastChar, p, ftype, cp)) {
                        strInsertChar(cp, p, input_lastChar);
                        p++;
                        writeFromPos(cp, p);
                        gotoXY(x + p, y);
                    }
                    else {
                        putchar(CH_BACKSPACE); putchar(PLACEHOLDER); putchar(CH_BACKSPACE);
                        beep();
                    }
                }
                else {
                    if (!isFieldInputFinished(input_lastChar)) {
                      gotoXY(x + p, y);
                      beep();
                    }
                }
        }
    } while (!isFieldInputFinished(input_lastChar));
}

// Moves the cursor to the upper left corner.
void home() {
    putchar(27);
    putchar('H');
}

// Clears the screen.
void clearScreen() {
    home();
    putchar(27);
    putchar('E');
}

// Places the text cursor at position x (= column) and y (= row).
// x and y are zero-based, i.e. the upper left corner is x = 0, y = 0.
void gotoXY(unsigned int x, unsigned int y) {
    putchar(27);        // ESC
    putchar('Y');       // cursor to position
    putchar(y + 32);    // row + 32
    putchar(x + 32);    // column + 32
}

// Clears from the cursor until the end of the line.
void clearEol() {
    putchar(27);
    putchar('K');
}

// Switches to inverse display, i.e. exchanges font and background colour.
void inverseVideo() {
    putchar(27);
    putchar('p');
}

// Switches inverse display off.
void normalVideo() {
    putchar(27);
    putchar('q');
}

// Hides the cursor.
void hideCursor() {
    putchar(27);
    putchar('f');
}

// Shows the cursor.
void showCursor() {
    putchar(27);
    putchar('e');
}

// Switches the status bar off.
void statusBarOff() {
    putchar(27);
    putchar('0');
}

// Switches the status bar on.
void statusBarOn() {
    putchar(27);
    putchar('1');
}
