/*
 *  1541fs.c - 1541-Emulation im Amiga-Dateisystem
 *
 *  Copyright (C) 1994-1996 by Christian Bauer
 */

/*
 *  Anmerkungen:
 *  ------------
 *
 *  Routinen:
 *   - Die Schnittstelle zu den IEC-Routinen besteht in den Routinen
 *     FS_Init, FS_Exit, FS_Open, FS_Close, FS_Read und FS_Write:
 *       FS_Init bereitet die Emulation vor
 *       FS_Exit beendet die Emulation
 *       FS_Open öffnet einen Kanal
 *       FS_Close schließt einen Kanal
 *       FS_Read liest aus einem Kanal
 *       FS_Write schreibt in einen Kanal
 *
 *  DriveData:
 *   - lock enthält den Lock des Verzeichnisses, in dem die Emulation
 *     ablaufen soll
 *
 *  Directory-Emulation:
 *   - Wird das Directory geöffnet (Dateiname "$"), wird in T: eine
 *     temporäre Datei angelegt, die vom Aufbau genau einem 1541-Directory
 *     entspricht und diese Datei geöffnet. Sie kann dann mit den ganz
 *     normalen Lesebefehlen verarbeitet werden.
 *
 *  Inkompatibilitäten/Verbesserungen:
 *   - Keine Wildcards beim Directory-Laden
 *   - Kein "rohes" Directory-Lesen
 *   - Keine relativen Dateien
 *   - Nur 'I'- und 'UJ'-Befehle implementiert
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <string.h>

#include "IEC.h"
#include "1541fs.h"
#include "Display.h"
#define CATCOMP_NUMBERS 1
#include "LocStrings.h"


// Aus Main.asm
extern void ResetC64(void);


// Prototypes
int open_file(DriveData *drive, int channel, const char *filename);
void convert_filename(const char *filename, char *plainname, int *filemode, int *filetype, int *wildflag);
void find_first_file(char *name);
int open_directory(DriveData *drive, int channel, const char *filename);
void close_all_channels(DriveData *drive);
void execute_command(DriveData *drive, const char *command);
char conv_from_64(char c);
char conv_to_64(char c);


/**
 **  Emulation vorbereiten, Lock auf Verzeichnis holen, prefs zeigt auf den
 **    Preferences-String
 **/

void FS_Init(DriveData *drive, char *prefs)
{
  int i;

  if (drive->lock = Lock(prefs,ACCESS_READ)) {
    for (i=0; i<16; i++) drive->handle[i] = NULL;

    drive->cmd_length = 0;

    SetError(drive, ERR_STARTUP);
  }
}


/**
 **  Emulation beenden, Lock auf Verzeichnis freigeben
 **/

void FS_Exit(DriveData *drive)
{
  if (drive->lock) {
    close_all_channels(drive);

    UnLock(drive->lock);
    drive->lock = NULL;
  }
}


/**
 **  Kanal öffnen, filename ist Null-terminiert
 **/

int FS_Open(DriveData *drive, int channel, char *filename)
{
  SetError(drive, ERR_OK);

  // Kanal 15: Dateiname als Befehl ausführen
  if (channel == 15) {
    execute_command(drive, filename);
    return ST_OK;
  }

  // Vorige Datei schließen, wenn noch offen
  if (drive->handle[channel]) {
    Close(drive->handle[channel]);
    drive->handle[channel] = NULL;
  }

  if (filename[0] == '$')
    return open_directory(drive, channel, filename);

  if (filename[0] == '#') {
    SetError(drive, ERR_NOCHANNEL);
    return ST_OK;
  }

  return open_file(drive, channel, filename);
}


/*
 *  Datei wird geöffnet
 */

// Zugriffsmodi
enum {
  FMODE_READ, FMODE_WRITE, FMODE_APPEND
};

// Dateitypen
enum {
  FTYPE_PRG, FTYPE_SEQ
};

int open_file(DriveData *drive, int channel, const char *filename)
{
  BPTR olddir;
  BPTR fh = NULL;
  char plainname[256];
  int filemode = FMODE_READ;
  int filetype = FTYPE_PRG;
  int wildflag = 0;

  olddir = CurrentDir(drive->lock);

  convert_filename(filename, plainname, &filemode, &filetype, &wildflag);

  // Bei Kanal 0 immer PRG lesen, bei Kanal 1 immer PRG schreiben
  if (!channel) {
    filemode = FMODE_READ;
    filetype = FTYPE_PRG;
  }
  if (channel == 1) {
    filemode = FMODE_WRITE;
    filetype = FTYPE_PRG;
  }

  // Wildcards sind nur beim Lesen erlaubt
  if (wildflag) {
    if (filemode != FMODE_READ) {
      SetError(drive, ERR_SYNTAX33);
      CurrentDir(olddir);
      return ST_OK;
    }
    find_first_file(plainname);
  }

  if (fh = Open(plainname, filemode == FMODE_READ ? MODE_OLDFILE : MODE_NEWFILE)) {
    switch (filemode) {

      // Erstes Zeichen lesen, wenn zum Lesen geöffnet
      case FMODE_READ:
        drive->read_char = FGetC(fh);
        break;

      // Neue Datei anlegen: E-Bit bei sequentieller Datei löschen
      case FMODE_WRITE:
        if (filetype == FTYPE_SEQ) {
          Close(fh);
          SetProtection(plainname, FIBF_EXECUTE);
          fh = Open(plainname, filemode == FMODE_READ ? MODE_OLDFILE : MODE_NEWFILE);
        } else {
          Close(fh);
          SetProtection(plainname, 0);
          fh = Open(plainname, filemode == FMODE_READ ? MODE_OLDFILE : MODE_NEWFILE);
        }
        break;

      // Anhängen: Ans Ende der Datei gehen
      case FMODE_APPEND:
        Seek(fh, 0, OFFSET_END);
        break;
    }
  }

  if (!(drive->handle[channel] = fh))
    SetError(drive, ERR_FILENOTFOUND);

  CurrentDir(olddir);
  return ST_OK;
}


/*
 *  Dateibezeichnung analysieren, Dateimodus und -typ ermitteln
 */

void convert_filename(const char *srcname, char *destname, int *filemode, int *filetype, int *wildflag)
{
  char *p, *q;
  int i;

  // Nach ':' suchen, p zeigt auf erstes Zeichen hinter dem ':'
  if (p = strchr(srcname, ':'))
    p++;
  else
    p = srcname;

  // Zeichensatz des Reststrings wandeln -> destname
  q = destname;
  for (i=0; i<NAMEBUF_LENGTH && (*q++ = conv_from_64(*p++)); i++);

  // Nach Modusparametern, getrennt durch ',' suchen
  p = destname;
  while (p = strchr(p, ',')) {

    // String hinter dem ersten ',' abschneiden
    *p++ = 0;

    switch (*p) {
      case 'p':
        *filetype = FTYPE_PRG;
        break;
      case 's':
        *filetype = FTYPE_SEQ;
        break;
      case 'r':
        *filemode = FMODE_READ;
        break;
      case 'w':
        *filemode = FMODE_WRITE;
        break;
      case 'a':
        *filemode = FMODE_APPEND;
        break;
    }
  }

  // Nach Wildcards suchen, '*' durch '#?' ersetzen und alles danach abschneiden
  *wildflag = (strchr(destname, '?') != NULL);

  if (p = strchr(destname, '*')) {
    *p++ = '#';
    *p++ = '?';
    *p++ = 0;
    *wildflag = TRUE;
  }
}


/*
 *  Erste zum Muster passende Datei suchen den Dateinamen ermitteln
 */

void find_first_file(char *name)
{
  struct AnchorPath *a;

  if (a = AllocMem(sizeof(struct AnchorPath), MEMF_CLEAR|MEMF_PUBLIC)) {
    if (!MatchFirst(name, a)) {
      strncpy(name, a->ap_Info.fib_FileName, NAMEBUF_LENGTH);
    }
    MatchEnd(a);
    FreeMem(a, sizeof(struct AnchorPath));
  }
}


/*
 *  Directory wird geöffnet, temporäre Datei erstellen
 */

struct FileInfoBlock fib;

int open_directory(DriveData *drive, int channel, const char *filename)
{
  BPTR fh;
  char buf[] = "\001\004\001\001\0\0\022\042                \042 00 2A";
  char *p, *q;
  int i;

  if (!Examine(drive->lock, &fib)) return ST_OK;

  if (!(fh = Open("T:Frodo$File", MODE_NEWFILE))) return ST_OK;

  // Directory-Titel erzeugen und schreiben
  p = &buf[8];
  for (i=0; i<16 && fib.fib_FileName[i]; i++)
    *p++ = conv_to_64(fib.fib_FileName[i]);
  Write(fh, buf, 32);

  // Für jeden Verzeichniseintrag eine Zeile erzeugen und schreiben
  while (ExNext(drive->lock, &fib)) {

    // Zeile mit Leerzeichen löschen und mit Nullbyte abschließen
    memset(buf, ' ', 31);
    buf[31] = 0;

    p = buf;
    *p++ = 0x01;	// Dummy-Verkettung
    *p++ = 0x01;

    // Größe in Blocks berechnen und eintragen
    i = (fib.fib_Size + 254) / 254;
    *p++ = i & 0xff;
    *p++ = (i >> 8) & 0xff;

    p++;
    if (i < 10) p++;	// Kleiner als 10: Ein Leerzeichen dazunehmen
    if (i < 100) p++;	// Kleiner als 100: Noch ein Leerzeichen dazunehmen

    // Dateiname wandeln und eintragen
    *p++ = '\"';
    q = p;
    for (i=0; i<16 && fib.fib_FileName[i]; i++)
      if (fib.fib_FileName[i])
        *q++ = conv_to_64(fib.fib_FileName[i]);
    *q++ = '\"';
    p += 18;

    // Typ ermitteln und eintragen
    if (fib.fib_DirEntryType >= 0) {
      *p++ = 'D';
      *p++ = 'I';
      *p++ = 'R';
    } else if (fib.fib_Protection & FIBF_EXECUTE) {
        *p++ = 'S';
        *p++ = 'E';
        *p++ = 'Q';
      } else {
        *p++ = 'P';
        *p++ = 'R';
        *p++ = 'G';
      }

    // Datei geschützt?
    if (fib.fib_Protection & FIBF_DELETE) *p++ = '<';

    // Zeile schreiben
    Write(fh, buf, 32);
  }

  // Abschlußzeile
  Write(fh, "\001\001\0\0BLOCKS FREE.             \0\0", 32);
  Close(fh);

  // Datei zum Lesen öffnen und das erste Zeichen lesen
  if (drive->handle[channel] = Open("T:Frodo$File", MODE_OLDFILE))
    drive->read_char = FGetC(drive->handle[channel]);

  return ST_OK;
}


/**
 **  Kanal schließen
 **/

int FS_Close(DriveData *drive, int channel)
{
  if (channel==15) {
    close_all_channels(drive);
    return ST_OK;
  }

  if (drive->handle[channel]) {
    Close(drive->handle[channel]);
    drive->handle[channel] = NULL;
  }

  return ST_OK;
}


/*
 *  Alle Kanäle schließen
 */

void close_all_channels(DriveData *drive)
{
  int i;

  for (i=0; i<15; i++) FS_Close(drive, i);

  drive->cmd_length = 0;
}


/**
 **  Ein Byte aus Kanal lesen
 **/

int FS_Read(DriveData *drive, int channel, char *data)
{
  LONG c;

  // Kanal 15: Fehlerkanal
  if (channel == 15) {
    *data = *drive->error_ptr++;

    if (*data != '\r')
      return ST_OK;
    else {
      SetError(drive, ERR_OK);
      return ST_EOF;
    }
  }

  if (!drive->handle[channel]) return ST_READ_TIMEOUT;

  // Zeichen aus dem Puffer holen und nächstes Zeichen lesen
  *data = drive->read_char;
  c = FGetC(drive->handle[channel]);
  drive->read_char = c;

  if (c == -1)
    return ST_EOF;
  else
    return ST_OK;
}


/**
 **  Ein Byte in Kanal schreiben
 **/

int FS_Write(DriveData *drive, int channel, char data, char eof)
{
  // Kanal 15: Zeichen sammeln und bei EOF Befehl ausführen
  if (channel == 15) {
    if (drive->cmd_length >= 40)
      return ST_TIMEOUT;

    drive->cmd_buffer[drive->cmd_length++] = data;

    if (eof < 0) {
      drive->cmd_buffer[drive->cmd_length++] = 0;
      drive->cmd_length = 0;
      execute_command(drive, drive->cmd_buffer);
    }
    return ST_OK;
  }

  if (!drive->handle[channel]) {
    SetError(drive, ERR_FILENOTOPEN);
    return ST_TIMEOUT;
  }

  if (FPutC(drive->handle[channel], (unsigned char)data) < 0) {
    SetError(drive, ERR_WRITEERROR);
    return ST_TIMEOUT;
  }

  return ST_OK;
}


/*
 *  Befehlsstring ausführen
 */

void execute_command(DriveData *drive, const char *command)
{
  APTR args;

  switch (command[0]) {
    case 'B':
      args = "B-?";
      if (ShowRequester(MSG_UNSUPFLOPPYCMD, MSG_REQGADS3, &args))
        ResetC64();
      SetError(drive, ERR_SYNTAX30);
      break;

    case 'M':
      args = "M-?";
      if (ShowRequester(MSG_UNSUPFLOPPYCMD, MSG_REQGADS3, &args))
        ResetC64();
      SetError(drive, ERR_SYNTAX30);
      break;

    case 'I':
      close_all_channels(drive);
      SetError(drive, ERR_OK);
      break;

    case 'U':
      if ((command[1] & 0x0f) == 0x0a) {
        close_all_channels(drive);
        SetError(drive, ERR_STARTUP);
      } else
        SetError(drive, ERR_SYNTAX30);
      break;

    default:
      SetError(drive, ERR_SYNTAX30);
      break;
  }
}


/*
 *  Umwandlung PETSCII->ASCII
 */

char conv_from_64(char c)
{
  if ((c >= 'A') && (c <= 'Z') || (c >= 'a') && (c <= 'z'))
    return c ^ 0x20;
  if ((c == '/') && MapSlash)
    return '\\';
  return c;
}


/*
 *  Umwandlung ASCII->PETSCII
 */

char conv_to_64(char c)
{
  if ((c >= 'A') && (c <= 'Z') || (c >= 'a') && (c <= 'z'))
    return c ^ 0x20;
  if ((c == '\\') && MapSlash)
    return '/';
  return c;
}
