/* ------------------------- forms2.c ---------------------------------------- *
 * Version 2.4  31.12.2021, C port: 16.01.2023                                 *
 *                                                                             *
 *  Modul fuer Formulare auf der JOYCE unter C.                                *
 *                                                                             *
 *  Es sind enthalten:                                                         *
 *     setMessageLine               -> Legt die Position der Infozeile fest    *
 *     writeCharPtrString           -> Schreibt einen dynamischen String       *
 *     formCancelled                -> Formular ueber Cancel Button beendet?   *
 *     processForm                  -> Anzeige und Steuerung des Formulars     *
 *     getFormElement               -> Formularelement holen                   *
 *     getField                     -> Feld holen                              *
 *     getButton                    -> Button holen                            *
 *     isActivated                  -> Ist der Button aktiviert worden?        *
 *     addField                     -> Feld zum Formular hinzufuegen           *
 *     addButton                    -> Button zum Formular hinzufuegen         *
 *     newAndLinkWithPrevElement    -> Neues Element und mit vorigem verbinden *
 *     initForm                     -> Formular initialisieren                 *
 *     setFieldText                 -> Text eines Feldes setzen                *
 * --------------------------------------------------------------------------- */

#include <stdio.h>
#include <conio.h>
#include "forms2.h"
#include "box.h"

FormElement *form_firstElement;
FormElement *form_currElement, *form_prevElement;
uint8_t form_messageLine;


/* TEST >>>
void printField(Field *f) {
    printf("kind: ");
    switch (f->kind) {
        case ftText:       printf("ftText"); break;
        case ftNumNatural: printf("ftNumNatural"); break;
        case ftInteger:    printf("ftInteger"); break;
        case ftReal:       printf("ftReal"); break;
        case ftDate:       printf("ftDate"); break;
        case ftFileName:   printf("ftFileName"); break;
        default:           printf("unknown");
    }
    clearEol();
    printf("\nlength: %d",   f->length);   clearEol();
    printf("\ncaption: %s",  f->caption);  clearEol();
    printf("\nxCaption: %d", f->xCaption); clearEol();
    printf("\nyCaption: %d", f->yCaption); clearEol();
    printf("\nxInput: %d",   f->xInput);   clearEol();
    printf("\nyInput: %d",   f->yInput);   clearEol();
    printf("\ntxt: %s",      f->txt);      clearEol();
    printf("\nmandatory: %s", f->mandatory ? "yes" : "no"); clearEol();
    putchar('\n');
}
void printButton(Button *b) {
    printf("kind: ");
    switch (b->kind) {
        case ok:     printf("ok"); break;
        case cancel: printf("cancel"); break;
        case other:  printf("other"); break;
        default:     printf("unknown");
    }
    clearEol();
    printf("\nx: %d",       b->x);       clearEol();
    printf("\ny: %d",       b->y);       clearEol();
    printf("\nwidth: %d",   b->width);   clearEol();
    printf("\ncaption: %s", b->caption); clearEol();
    printf("\nactivated: %s", b->activated ? "yes" : "no"); clearEol();
    putchar('\n');
}
void printFormElement(FormElement *e) {
    printf("Address of FormElement: %x\n", e); clearEol();
    printf("next: %x\n", e->next);             clearEol();
    printf("prev: %x\n", e->prev);             clearEol();
    printf("elementType: ");
    switch (e->elementType) {
        case feField:
            printf("feField");
            printField(e->field);
            break;
        case feButton:
            printf("feButton");
            printButton(e->button);
            break;
        default:
            printf("unknown");
    }
    putchar('\n');
    clearEol();
}
void printFormElements() {
    FormElement *fe;
    
    fe = form_firstElement;
    if (fe == NULL) {
        puts("No form elements available.");
    }
    else {
        do {
            printFormElement(fe);
            waitForKey();
            fe = fe->next;
        } while (false); // DEBUG
//      } while (fe != NULL);
    }
}
<<< TEST */


// Sets the position of the message line (Y coordinate).
void setMessageLine(uint8_t line) {
  form_messageLine = line;
}


// Was form processing finished by a cancel button?
bool formCancelled() {
    FormElement *currentElement;
    Button *but;
    bool found;

    currentElement = form_firstElement;
    found = false;
    
    do {
      if (currentElement->elementType == feButton) {
        but = currentElement->button;
          if (but->activated && but->kind == cancel) {
            found = true;
          }
      }
      if (!found) {
        currentElement = currentElement->next;
      }
    } while (currentElement != NULL && !found);
    
    return found;
}


// Draws a button.
void drawButton(int x, int y, int width, const char *caption) {
  drawBox(x, y, width, 3);
  gotoXY(x + (width - strlen(caption)) / 2, y + 1);
  printf(caption);
}


// Writes the button's caption centered inside the button frame.
void writeButtonCaption(Button *button) {
  uint8_t len, i;

  gotoXY(button->x + 1, button->y + 1);
  for (i = 2; i < button->width; i++) {
      putchar(' ');
  }
  len = strlen(button->caption);
  gotoXY(button->x + (button->width - len) / 2, button->y + 1);
  printf(button->caption);
}


/* Handles a button. When entered, the button caption gets reversed; when a
   finish key is pressed, the caption is switched to normal again.
   The Return key activates the button, i.e. the activated property is set
   to true.
   Returns the key pressed.
*/
char handleButton(Button *button) {
  char ch;

  inverseVideo();
  hideCursor();
  writeButtonCaption(button);
  do {
      ch = getch();
  } while (!isFieldInputFinished(ch) && ch != CH_LEFT && ch != CH_RIGHT);
  if (ch == CH_RETURN) {
      button->activated = true;
  }
  normalVideo();
  writeButtonCaption(button);
  showCursor();

  return ch;
}


/* Check if all mandatory fields are filled out.
   Returns true if all mandatory fields are filled out, false if at least one
   mandatory field is not filled out.
*/
bool checkMandatoryFields() {
  FormElement *currentElement;
  Field *currentField;
  bool finished;

  currentElement = form_firstElement;
  finished = false;
  do {
      if (currentElement->elementType == feField) {
          currentField = currentElement->field;
          if (currentField->mandatory) {
              if ( *(currentField->txt) == '\0' ) { // empty string?
                  finished = true;
                  gotoXY(1, form_messageLine);
                  printf("\"%s\" ist ein Pflichtfeld: bitte ausf%cllen.", currentField->caption, CH_LC_UUML);
                  gotoXY(currentField->xInput, currentField->yInput);
                  break;
              }
          }
      }
    
      currentElement = currentElement->next;
  } while (currentElement != NULL);
  
  return !finished;
}
  
  
// Clears the message line.
void clearMessageLine() {
  gotoXY(1, form_messageLine);
  clearEol();
}
  
  
// Displays the form.
void displayForm() {
    FormElement *currentElement;
    Field *currentField;
    Button *currentButton;
    uint8_t j;
    
    currentElement = form_firstElement;
    while (currentElement != NULL) {
        switch (currentElement->elementType) {
            case feField:
                currentField = currentElement->field;
                gotoXY(currentField->xCaption, currentField->yCaption);
                if (currentField->mandatory) putchar('*');
                printf(currentField->caption);
                putchar(':');
                gotoXY(currentField->xInput, currentField->yInput);
                printf(currentField->txt);
                for (j = strlen(currentField->txt); j < currentField->length; j++) {
                    putchar('_');
                }
                break;
            case feButton:
                currentButton = currentElement->button;
                drawButton(currentButton->x, currentButton->y, currentButton->width, currentButton->caption);
                break;
            default:
                printf("ERROR: unknown elementType: %d\n", currentElement->elementType);
        }
        currentElement = currentElement->next;
    };
}


// Processes the form.
void processForm() {
    FormElement *currentElement;
    Field *currentField;
    Button *currentButton;
    bool finished;
    
    if (form_firstElement != NULL) {
        displayForm();

        // Process the form.
        currentElement = form_firstElement;
        finished = false;
        do {
            switch (currentElement->elementType) {
                case feField:
                    currentField = currentElement->field;
                    inputString(currentField->txt, currentField->length, 
                                currentField->xInput, currentField->yInput, currentField->kind);
                    break;
                case feButton:
                    currentButton = currentElement->button;
                    input_lastChar = handleButton(currentButton);
                    if (input_lastChar == CH_RETURN) finished = true;
                    break;
            }
            clearMessageLine();
            if (finished) {
                if (formCancelled())
                    finished = true;
                else
                    finished = checkMandatoryFields();
            }
            else {
                if (input_lastChar == CH_UP) { // Up
                    if (currentElement != form_firstElement) {
                        currentElement = currentElement->prev;
                    }
                }
                else { // Enter, Tab, or Down
                    if (currentElement->elementType == feButton) {
                        switch (input_lastChar) {
                            case CH_RETURN:
                                currentElement = NULL;
                                break;
                            case CH_LEFT:
                                if (currentElement->prev != NULL) {
                                  currentElement = currentElement->prev;
                                }
                                break;
                            default:
                                currentElement = currentElement->next;
                        }
                    }
                    else {
                        currentElement = currentElement->next;
                    }
                }
            }
            if (currentElement == NULL) currentElement = form_firstElement;
        } while (!finished);
    }
}


/* Gets the nth form element from the element list.
   Returns a pointer to element n if found or nil if not found. */
FormElement *getFormElement(uint8_t n) {
    FormElement *currentElement;
    uint8_t count;
  
    currentElement = form_firstElement;
    count = 1;
    
    while (currentElement != NULL && n != count) {
        count++;
        currentElement = currentElement->next;
    }
    
    return currentElement;
}


/* Gets the nth field from the element list.
   Returns a pointer to the field n if found or nil if not found. */
Field *getField(uint8_t n) {
    FormElement *currentElement;
    uint8_t fieldsFound;
    Field *result;

    currentElement = form_firstElement;
    fieldsFound = 0;
    
    do {
        if (currentElement->elementType == feField) {
            fieldsFound++;
            if (fieldsFound == n) {
              break;
            }
        }
        currentElement = currentElement->next;
    } while(currentElement != NULL);
    
    if (currentElement == NULL) {
        result = NULL;
    }
    else {
        result = currentElement->field;
    }
    
    return result;
}


/* Gets the nth button from the element list.
   Returns a pointer to the button n if found or nil if not found. */
Button *getButton(uint8_t n) {
    FormElement *currentElement;
    uint8_t buttonsFound;
    Button *result;

    currentElement = form_firstElement;
    buttonsFound = 0;
    
    do {
        if (currentElement->elementType == feButton) {
            buttonsFound++;
            if (buttonsFound == n) {
              break;
            }
        }
        currentElement = currentElement->next;
    } while(currentElement != NULL);
    
    if (currentElement == NULL) {
        result = NULL;
    }
    else {
        result = currentElement->button;
    }
    
    return result;
}


// Is the button activated?
bool isActivated(uint8_t n) {
    Button *button;
    button = getButton(n);
    
    return button == NULL ? false : button->activated;
}


// Adds a field to the form.
void addField(fieldType pKind,
              int pFieldLength, const char *pCaption,
              int pXCaption, int pYCaption,
              int pXInput, int pYInput,
              bool pMandatory) {

    Field *fld;
    char *newtxt;
    
    fld = malloc(sizeof(Field));
    
    form_currElement->elementType = feField;
    form_currElement->prev = form_prevElement;
    form_currElement->next = NULL;
    form_currElement->field = fld;
    
    fld->kind = pKind;
    newtxt = malloc(pFieldLength + 1);
    *newtxt = '\0'; // initial length is 0 (empty String)
    fld->txt = newtxt;
    fld->length = pFieldLength;
    fld->caption   = pCaption;
    fld->xCaption  = pXCaption;
    fld->yCaption  = pYCaption;
    fld->xInput    = pXInput;
    fld->yInput    = pYInput;
    fld->mandatory = pMandatory;
}


// Adds a button to the form.
void addButton(buttonType pKind, int pX, int pY, int pWidth, const char *pCaption) {

    Button *but;
    
    form_currElement->elementType = feButton;
    form_currElement->prev = form_prevElement;
    form_currElement->next = NULL;
    but = malloc(sizeof(Button));
    form_currElement->button = but;
    
    but->kind = pKind;
    but->x = pX;
    but->y = pY;
    but->width = pWidth;
    but->caption = pCaption;
    but->activated = false;
}

// Creates a new form element and links it with the previous one.
void newAndLinkWithPrevElement() {
    form_prevElement = form_currElement;
    form_currElement = malloc(sizeof(FormElement));
    form_prevElement->next = form_currElement;
}


/* Initializes the form. Calls InitFormElements, a procedure to be
   implemented in the main application. */
void initForm(uint8_t formIdx) {
    form_firstElement = malloc(sizeof(FormElement));
    form_currElement = form_firstElement;
    form_prevElement = NULL;
    initFormElements(formIdx);
}


/* Destroys the form and releases allocated memory.
   Only txt members of fields and the fields themselves are dynamically
   allocated, so we have to call free() on these. */
void destroyForm(uint8_t formIdx) {
    FormElement *fe, *nextfe;
    Field *f;
    
    fe = form_firstElement;
    while (fe != NULL) {
        if (fe->elementType == feField) {
            f = fe->field;
            if (f->txt != NULL) {
                free(f->txt);
            }
            free(f);
        }
        else { // feButton
            free(fe->button);
        }
        nextfe = fe->next;
        free(fe);
        fe = nextfe;
    }
}


// Sets the text of field n.
void setFieldText(uint8_t n, char *s) {
    Field *fld;

    fld = getField(n);
    fld->txt = s;
}

// Gets the first element of the form.
FormElement *getFormFirstElement() {
    return form_firstElement;
}
