/*
 * ETHFLOP SERVER FOR DOS
 *
 * http://ethflop.sourceforge.net
 *
 * Copyright (C) 2019-2024 Mateusz Viste
 * MIT license
 */

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

#include "mdr\bios.h"
#include "mdr\dos.h"
#include "mdr\cout.h"
#include "mdr\pktdrv.h"

/* core ethflop server logic */
#include "core.h"
#include "version.h"  /* program version */


struct PKTFRAME {
  unsigned short len;
  unsigned char payload[1];
};


/* writes into buff the human (ASCIZ) representation of mac. buff must be
 * at least 18 bytes long */
static void mactostring(char *buff, const unsigned char *mac) {
  sprintf(buff, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}


static int init_pkt_ethflop(unsigned short *handles, unsigned char *pktint, unsigned char *mymac) {
  char buff[32];

  *pktint = mdr_pktdrv_init(0);
  if (*pktint == 0) {
    mdr_coutraw_puts("ERROR: packet driver not found");
    return(-1);
  }

  mdr_coutraw_str("Packet driver found at int 0x");
  sprintf(buff, "%02X", *pktint);
  mdr_coutraw_puts(buff);

  handles[0] = mdr_pktdrv_accesstype(0xEFDC);
  handles[1] = mdr_pktdrv_accesstype(0xEFDD);
  if ((handles[0] == 0xffff) || (handles[1] == 0xffff)) {
    mdr_coutraw_puts("ERROR: Packet driver initialization (accesstype) failed");
    return(-2);
  }

  mdr_coutraw_str("My MAC address is ");
  mdr_pktdrv_getmac(mymac, handles[0]);
  mactostring(buff, mymac);
  mdr_coutraw_puts(buff);
  ethflop_init(mymac);

  return(0);
}


/* returns the amount of clients connected */
static unsigned char display_clients_mac(unsigned char maxlines) {
  char buff[20];
  unsigned char line;
  struct cliententry *ce = ethflop_getclients();
  unsigned char count = 0;

  for (line = 1; line < maxlines; line++) {

    mdr_cout_char(line, 0, ' ', 0x07);

    if (ce == NULL) {
      if (line == 1) {
        mdr_cout_str(line, 1, "<NO CLIENTS YET>  ", 0x07, 18);
      } else {
        mdr_cout_str(line, 1, "                  ", 0x07, 18);
      }
    } else {
      count++;
      mactostring(buff, ce->mac);
      buff[17] = ' ';
      mdr_cout_str(line, 1, buff, 0x07, 18);
    }

    if (ce != NULL) ce = ce->next;
  }

  return(count);
}


static struct cliententry *display_clients_flops(unsigned char maxlines, unsigned char selected_client) {
  unsigned char line;
  unsigned char attr;
  struct cliententry *ce = ethflop_getclients();
  struct cliententry *res = NULL;

  if (ce == NULL) return(NULL);

  for (line = 1; line < maxlines; line++) {
    if (line == selected_client + 1) {
      attr = 0x70;
      res = ce;
    } else {
      attr = 0x07;
    }

    if (ce == NULL) {
      mdr_cout_str(line, 19, "               ", attr, 15);
    } else if (ce->curflopid[0] == 0) {
      mdr_cout_str(line, 19, " < NO FLOPPY > ", attr, 15);
    } else {
      unsigned char l;
      mdr_cout_char(line, 19, ' ', attr);
      l = mdr_cout_str(line, 20, ce->curflopid, attr, 8);
      mdr_cout_char_rep(line, 20 + l, ' ', attr, 9 - l);
      if (ce->ro != 0) {
        mdr_cout_str(line, 29, "[RO] ", attr, 5);
      } else {
        mdr_cout_str(line, 29, "     ", attr, 5);
      }
    }

    if (ce != NULL) ce = ce->next;
  }
  return(res);
}


static void ui_initialscreen(unsigned char screen_w, unsigned char screen_h) {
  unsigned char i;

  mdr_cout_cls(0x07);
  mdr_cout_str(0, 1, "Connected clients:", 0x07, 0xff);
  mdr_cout_str(screen_h - 5, 0, "=== DEBUG LOGS", 0x07, 0xff);
  mdr_cout_char_rep(screen_h - 5, 15, '=', 0x07, screen_w - 15);

  for (i = 0; i < screen_h - 5; i++) {
    mdr_cout_char(i, 34, '|', 0x07);
  }

  mdr_cout_str(0, 36, "ETHFLOP SERVER for DOS ver " PVER, 0x07, 0xff);
  mdr_cout_str(2, 36, "E = Eject floppy  ENTER = Insert new floppy", 0x07, 0xff);
  mdr_cout_str(3, 36, "ESC = QUIT", 0x07, 0xff);
  mdr_cout_str(5, 36, "Available floppy image files:", 0x07, 0xff);
}


static void logmsg(char *msg, char screen_w, char screen_h) {
  unsigned char l;

  if (msg[0] == 0) return;

  /* move the current 4 log lines up by 1 line */
  mdr_cout_scrollup(0x07, screen_h - 4, 0, screen_h - 1, screen_w - 1, 1);

  /* print the new log */
  l = mdr_cout_str(screen_h - 1, 0, msg, 0x07, 80);
  mdr_cout_char_rep(screen_h - 1, l, ' ', 0x07, 80 - l);

  /* reset the log string */
  msg[0] = 0;
}


static const char *ui_refresh_imglist(char *bigbuff, unsigned short bigbuffsz, unsigned char *selected) {
  unsigned short imgcount;
  unsigned short i;
  unsigned char attr;
  static unsigned short imgcount_last;
  const char *res = NULL;

  imgcount = ethflop_imgfilelist(bigbuff, bigbuffsz);

  if (imgcount == 0) {
    bigbuff[0] = 0; /* mark the list as empty (UI needs this) */
  }

  if ((selected != NULL) && (*selected >= imgcount) & (imgcount > 0)) *selected = imgcount - 1;

  if (imgcount > 56) {
    memcpy(bigbuff + 550, "...", 4);
    imgcount = 56;
  }

  for (i = 0; i < imgcount; i++) {
    unsigned char len;
    if ((selected != NULL) && (i == *selected)) {
      attr = 0x70;
      res = bigbuff + i * 10;
    } else {
      attr = 0x07;
    }
    len = mdr_cout_str(6 + (i / 4), 36 + (i & 3) * 10, bigbuff + i * 10, attr, 8);
    if (len < 8) {
      mdr_cout_char_rep(6 + (i / 4), 36 + len + (i & 3) * 10, ' ', 0x07, 8 - len);
    }
  }

  /* print as many empty entries as necessary to cover up entries that were
   * displayed last time and no longer present */
  for (;i < imgcount_last; i++) {
    mdr_cout_char_rep(6 + (i / 4), 36 + (i & 3) * 10, ' ', 0x07, 8);
  }

  imgcount_last = imgcount;
  return(res);
}


static void idle(void);
#pragma aux idle = \
  "mov ax, 0x1680" \
  "int 0x2F" \
  "test al, al" /* AL is zeroed if 1680h (release cycle) suceeded */ \
  "jz DONE" \
  "sti"         /* otherwise do a hlt myself */ \
  "hlt" \
  "DONE:" \
modify [ax];


#define FLG_QUIT 128
#define FLG_NOUI 64
#define FLG_HLT 32

/* parses command line and returns a set of FLG_* flags */
static unsigned char parse_cmdline(int argc, char **argv) {
  unsigned char i;
  unsigned char flags = 0;

  for (i = 1; i < argc; i++) {
    if (strcasecmp(argv[i], "/noui") == 0) {
      flags |= FLG_NOUI;
    } else if (strcasecmp(argv[i], "/hlt") == 0) {
      flags |= FLG_HLT;
    } else { /* HELP SCREEN */
      mdr_coutraw_puts("ETHFLOPD ver " PVER " (C) " PDATE " Mateusz Viste");
      mdr_coutraw_crlf();
      mdr_coutraw_puts("usage: ethflopd [options]");
      mdr_coutraw_crlf();
      mdr_coutraw_puts("options:");
      mdr_coutraw_crlf();
      mdr_coutraw_puts("/noui  disable the user interface");
      mdr_coutraw_puts("/hlt   enable power saving (might cause bad performances)");
      mdr_coutraw_crlf();
      mdr_coutraw_puts("MIT license. http://ethflop.sourceforge.net");
      return(FLG_QUIT);
    }
  }
  return(flags);
}


int main(int argc, char **argv) {
  unsigned char pktint;
  unsigned char mymac[6];
  unsigned short pkt_handles[2] = {0xffff, 0xffff}; /* the two handles obtained from the pkt driver */
  struct PKTFRAME *frame;
  const struct FRAME *answer;
  static unsigned char ui_refresh; /* what element of UI to refresh */
  static unsigned char selected_client;
  static unsigned char exitflag;
  static unsigned char screen_w, screen_h, screen_c;
  static unsigned short last_ui_time;
  static struct cliententry *selected_client_ptr;
  static char log[128];
  static char imglistbuff[600]; /* must hold 56 floppy names */
  static const char *imglistbuffptr;
  static unsigned char clientscount;
  static unsigned char selectionmode;  /* 0=clients 1=images */
  static unsigned char selectedimage;  /* selected floppy image (when selectionmode=1) */
  static unsigned char flags;

  flags = parse_cmdline(argc, argv);
  if (flags & FLG_QUIT) return(1);

  mdr_coutraw_crlf();
  mdr_coutraw_puts("ETHFLOP server for DOS ver " PVER " starting...");
  mdr_coutraw_crlf();
  if (init_pkt_ethflop(pkt_handles, &pktint, mymac) != 0) return(2);

  frame = mdr_pktdrv_getbufptr();

  sprintf(log, "Ready, waiting for clients...");


  /*** simplified (no UI) version ********************************************/

  if (flags & FLG_NOUI) {
    mdr_coutraw_puts("[PRESS ANY KEY TO QUIT]");

    for (;;) {

      /* log pending? */
      if (log[0] != 0) {
        mdr_coutraw_puts(log);
        log[0] = 0;
      }

      if ((frame->len == 0) || (frame->len & 0x8000)) {
        if (mdr_dos_keypending() != 0) {
          mdr_coutraw_puts("Aborted by user");
          mdr_dos_flushkeyb();
          break;
        }
        if (flags & FLG_HLT) idle();
        continue;
      }

      answer = ethflop_process(frame->payload, frame->len, log);

      /* processing done - mark the input pktdrv buffer as empty */
      frame->len = 0;

      /* send the answer (if any) */
      if (answer != NULL) {
        mdr_pktdrv_send(answer, sizeof(struct FRAME));
      }
    }

    goto GOODBYE_NOUI;
  }


  /*** full user interface version *******************************************/

  screen_c = mdr_cout_init(&screen_w, &screen_h);
  ui_initialscreen(screen_w, screen_h);

  mdr_cout_cursor_hide();

  while (exitflag == 0) {
    /* display pending log, if any */
    logmsg(log, screen_w, screen_h);

    /* wait for a request... */

    /* if nothing received then loop for keyboard actions and signal IDLE time */
    if ((frame->len == 0) || (frame->len & 0x8000) || (last_ui_time != mdr_bios_ticks() & 0xf0)) {

      /* force a UI check at least once a second */
      last_ui_time = mdr_bios_ticks() & 0xf0;

      switch (ui_refresh & 7) {
        case 0: /* keyb polling */
          if (mdr_dos_keypending() != 0) {
            unsigned short k = mdr_dos_getkey2();
            switch (k) {
              case 0x1B: /* ESC */
                if (selectionmode != 0) {
                  selectionmode = 0;
                } else {
                  exitflag = 1;
                }
                break;
              case 0x148: /* up */
                if (selectionmode == 0) {
                  if (selected_client > 0) selected_client--;
                } else {
                  if (selectedimage > 4) {
                    selectedimage -= 4;
                  } else {
                    selectedimage = 0;
                  }
                }
                break;
              case 0x150: /* down */
                if (selectionmode == 0) {
                  if ((selected_client < screen_h - 7) && (selected_client + 1 < clientscount)) selected_client++;
                } else {
                  if (selectedimage <= 52) selectedimage += 4;
                }
                break;
              case 0x14B: /* left */
                if ((selectionmode != 0) && (selectedimage > 0)) selectedimage--;
                break;
              case 0x14D: /* right */
                if ((selectionmode != 0) && (selectedimage < 56)) selectedimage++;
                break;
              case 'e':
                if ((selectionmode == 0) && (selected_client_ptr != NULL)) ethflop_eject(selected_client_ptr, log);
                break;
              case '\r':
                if (selectionmode != 0) {
                  if ((imglistbuffptr != NULL) && (selected_client_ptr != NULL)) {
                    /* eject floppy if present */
                    if (selected_client_ptr->curflopid[0] != 0) {
                      ethflop_eject(selected_client_ptr, log);
                      logmsg(log, screen_w, screen_h);
                    }
                    /* insert new floppy */
                    ethflop_insert(selected_client_ptr, imglistbuffptr, log, 0);
                  }
                  selectionmode = 0;
                } else {
                  if (imglistbuff[0] == 0) {
                    sprintf(log, "No floppy images available (create some using ethflop)");
                  } else if (selected_client_ptr != NULL) {
                    selectionmode = 1;
                  }
                }
                break;
            }
          }
          break;
        case 1: /* list of clients (MAC) */
          clientscount = display_clients_mac(screen_h - 5);
          break;
        case 2: /* list of clients (floppies names) */
          if (selectionmode == 0) {
            selected_client_ptr = display_clients_flops(screen_h - 5, selected_client);
          } else {
            display_clients_flops(screen_h - 5, 0xff);
          }
          break;
        case 3: /* if *really* idle then try to do some power saving or refresh
                   the list of available floppy images */
          if (frame->len == 0) {
            static unsigned char imglist;
            if (imglist++ & 0x03) {
              if (flags & FLG_HLT) idle();
            } else { /* refresh list of floppy images */
              if (selectionmode != 0) {
                imglistbuffptr = ui_refresh_imglist(imglistbuff, sizeof(imglistbuff), &selectedimage);
              } else {
                ui_refresh_imglist(imglistbuff, sizeof(imglistbuff), NULL);
              }
            }
          }
          break;
      }
      ui_refresh++;
      continue;
    }

    /* ask ethflop core for the answer frame */
    answer = ethflop_process(frame->payload, frame->len, log);
    frame->len = 0;

    /* send the answer (if any) */
    if (answer != NULL) {
      mdr_pktdrv_send(answer, sizeof(struct FRAME));
    }
  }

  mdr_cout_close();
  mdr_coutraw_puts("ETHFLOPD ver " PVER " (C) " PDATE " Mateusz Viste");

  GOODBYE_NOUI:

  mdr_coutraw_puts("Goodbye");
  if (pkt_handles[0] != 0xffff) mdr_pktdrv_free(pkt_handles[0]);
  if (pkt_handles[1] != 0xffff) mdr_pktdrv_free(pkt_handles[1]);

  return(0);
}
