#include <EDK.h>
#include "General.h"
#include "CC64.h"
#include "CVC1541.h"
#include <direct.h>


void CBMtoASCII(char* pcDest, byte* pbSource, int iCount) {
  for (int i = 0; i < iCount; i++) {
    byte b = pbSource[i];
    switch (b & ~0x1F) {
    case 0x00:
    case 0x40:
      pcDest[i] = "@abcdefghijklmnopqrstuvwxyz[¤].."[b & 0x1F];
      break;
    case 0x60:
    case 0x80:
    case 0xC0:
      pcDest[i] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ...¦."[b & 0x1F];
      break;
    case 0xA0:
    case 0xE0:
      pcDest[i] = " ..­_¦.¦./¦....­....¦¦¦­­­......"[b & 0x1F];
      break;
    default:
      pcDest[i] = (char)b;
      break;
    }
  }
  pcDest[i] = 0;
}


char* GetType(char cType) {
  switch (cType) {
  case 0:
    return "del";
  case 1:
    return "seq";
  case 2:
    return "prg";
  case 3:
    return "usr";
  case 4:
    return "rel";
  case 5:
    return "cbm";
  default:
    return "???";
  }
}


void ReadDirectory(HWND hwndListBox, HANDLE hFile) {
  
  // read directory track 18
  byte* pbBuffer = MemAlloc(19 * 256);
  SetFilePointer(hFile, (18 - 1) * 21 * 256, NULL, FILE_BEGIN);
  ReadFile(hFile, pbBuffer, 19 * 256);

  // disk name and ID
  char acName[18];
  CBMtoASCII(acName, pbBuffer + 144, 16);
  char acID[6];
  CBMtoASCII(acID, pbBuffer + 162, 5);
  char ac[40];
  wsprintf(ac, "0 \"%s\" %s", acName, acID);
  SendMessage(hwndListBox, LB_ADDSTRING, 0, (LONG)&ac);

  // check directory sector
  int iSector = 0;
  while (pbBuffer[iSector] == 18) {
    pbBuffer[iSector] = 0;
    iSector = pbBuffer[iSector + 1] * 256;
    if (iSector >= 19 * 256) {
      break;
    }

    // add file names
    for (int i = iSector + 2; i < iSector + 256; i += 32) {
      if (pbBuffer[i] != 0) {
        char* pcType = GetType((byte)(pbBuffer[i] & 0x0F));
        pbBuffer[i + 3 + 16] = (byte)0xA0;
        byte* pb = pbBuffer + i + 3;
        while (*pb != '"' && *pb != (byte)0xA0) {
          pb++;
        }
        *pb = '"';
        CBMtoASCII(acName, pbBuffer + i + 3, 17);
        wsprintf(ac, "%-4d \"%s%c%s%c", *(word*)(pbBuffer + i + 28), acName, pbBuffer[i] & 0x80 ? ' ' : '*', pcType, pbBuffer[i] & 0x40 ? '<' : ' ');
        SendMessage(hwndListBox, LB_ADDSTRING, 0, (LONG)&ac);
      }
    }
  }

  // free blocks
  word wFree = 0;
  for (int i = 1; i <= 35; i++) {
    if (i != 18) {
      wFree = (word)(wFree + pbBuffer[i * 4]);
    }
  }
  wsprintf(ac, "%u blocks free.", wFree);
  SendMessage(hwndListBox, LB_ADDSTRING, 0, (LONG)&ac);
  MemFree(pbBuffer);
}



static char gacC64Name[17];

BOOL CALLBACK DiskImageDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
  static CIECDevice* pDevice;
  static HFONT hfont;

  switch (msg) {
  case WM_INITDIALOG:

    pDevice = (CIECDevice*)lParam;
    CenterWindow(hwnd);
    SetWindowText(hwnd, pDevice->GetDisk());

    hfont = CreateFont(14, 8, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, FIXED_PITCH | FF_MODERN, "Courier");
    SendDlgItemMessage(hwnd, IDC_Dir, WM_SETFONT, (WPARAM)hfont, 0);
    ReadDirectory(GetDlgItem(hwnd, IDC_Dir), ((CVC1541*)pDevice)->FDC.hFile);
    SendDlgItemMessage(hwnd, IDC_Dir, LB_SETCURSEL, 1, 0);

    return TRUE;

  case WM_COMMAND:

    switch (wParam) {

    case IDC_SetDisk:
    case IDC_Load:
    case IDC_Run:
    case IDCANCEL:
      {
        strcpy(gacC64Name, "*");
        int iSel = SendDlgItemMessage(hwnd, IDC_Dir, LB_GETCARETINDEX, 0, 0);
        char ac[256];
        SendDlgItemMessage(hwnd, IDC_Dir, LB_GETTEXT, iSel, (LONG)ac);
        char* pcStart = strchr(ac, '"');
        if (pcStart != NULL) {
          char* pcEnd = strchr(pcStart + 1, '"');
          if (pcEnd != NULL) {
            *pcEnd = 0;
            strncpy(gacC64Name, pcStart + 1, 17);
          }
        }

        SendDlgItemMessage(hwnd, IDC_Dir, WM_SETFONT, (WPARAM)GetStockObject(SYSTEM_FONT), 0);
        DeleteObject(hfont);

        EndDialog(hwnd, wParam);
        return TRUE;
      }
    }
  }

  return 0;
}


static byte gabAutotype[128];
static byte* gpbAutotype;
static byte gbAutotypeTrap;


byte OnAutotype() {
  CC64* pC64;
  __asm mov pC64,ECX
  while (pC64->pbRAM[0xC6] < 10) {

    // remove the trap when autotype is finished
    if (*gpbAutotype == 0) {
      pC64->pCPU->SetTrap(pC64->pbKernal + 0x05CD, 0);
      pC64->pCPU->FreeTrap(gbAutotypeTrap);
      break;
    }

    // switch upper case and lower case letters
    if (isalpha(*gpbAutotype)) {
      *gpbAutotype ^= 32;
    }

    // copy next character into C64 keyboard buffer
    pC64->pbRAM[0x0277 + pC64->pbRAM[0xC6]++] = *gpbAutotype++;

  }
  return pC64->pbKernal[0x05CD];
}


int HexToBin(char cHex) {
  if (cHex >= 'A') {
    cHex += 9;
  }
  return cHex & 15;
}


// get the next word from command line and remove quotes
static flag GetNextWord(char** ppcCmd, char* pcBuffer, int iBufSize) {

  // skip spaces
  char* pcStart = *ppcCmd;
  while (*pcStart == ' ') {
    pcStart++;
  }

  // stop if there are no more parameters
  if (*pcStart == 0) {
    return false;
  }

  // get end character (space or quote)
  char cEnd = ' ';
  if (*pcStart == '"') {
    cEnd = '"';
    pcStart++;
  }

  // search for end of word
  char* pcEnd = strchr(pcStart, cEnd);
  if (pcEnd == NULL) {
    pcEnd = pcStart + strlen(pcStart);
  }

  // copy the word
  int iLength = pcEnd - pcStart;
  if (iLength >= iBufSize) {
    error("limit of command line word is %d chars", iBufSize);
  }
  memcpy(pcBuffer, pcStart, iLength);
  pcBuffer[iLength] = 0;

  // set pointer to next word
  if (cEnd == '"') {
    pcEnd++;
  }
  *ppcCmd = pcEnd;

  return true;

}


HMODULE gahExtension[16];
int giExtensions;


void UnloadExtensions() {
  while (giExtensions > 0) {
    giExtensions--;
    win(FreeLibrary(gahExtension[giExtensions]));
    gahExtension[giExtensions] = NULL;
  }
}

void ParseCommandLine(CC64* pC64, char* pcCmd) {

  char acFileName[256];
  acFileName[0] = 0;

  char ac[256];
  while (GetNextWord(&pcCmd, ac, sizeof ac)) {

    // load extensions
    if (stricmp(ac, "-extension") == 0) {
      char acExtension[256];
      if (GetNextWord(&pcCmd, acExtension, sizeof acExtension)) {
        try {
          trace("Loading extension %s", acExtension);
          #ifndef _DEBUG
            extern flag IsValidLicense();
            if (!IsValidLicense()) {
              error("The shareware version doesn't support extensions!");
            }
          #endif
          win(gahExtension[giExtensions] = LoadLibrary(acExtension));
          giExtensions++;
        } catch (...) {
          report();
        }
      }

    // get autotype string
    } else if (stricmp(ac, "-autotype") == 0) {
      if (GetNextWord(&pcCmd, ac, sizeof ac)) {
        char* pcSrc = ac;
        byte* pbDest = gabAutotype;
        while (*pcSrc != 0) {
          if (*pcSrc == '$' && pcSrc[1] != 0 && pcSrc[2] != 0) {
            *pbDest++ = (byte)(HexToBin(pcSrc[1]) * 16 + HexToBin(pcSrc[2]));
            pcSrc += 3;
          } else {
            *pbDest++ = (byte)*pcSrc++;
          }
        }
        *pbDest = 0;
      }

    // get file name
    } else {
      strcpy(acFileName, ac);
    }
  }

  if (acFileName[0] != 0) {

    // check for type of file
    char* pcExt = strrchr(acFileName, '.');
    if (pcExt && stricmp(pcExt, ".d64") == 0) {
  
      // it is a disk image
      pC64->IECMaster.apDevice[8] = new CVC1541(8, &pC64->IECMaster);
      pC64->IECMaster.apDevice[8]->SetDisk(acFileName);

      // let the user select a file name
      if (gabAutotype[0] == 0) {
        switch (DialogBoxParam(ghinst, MAKEINTRESOURCE(IDD_DiskImage), ghwnd, DiskImageDlgProc, (DWORD)pC64->IECMaster.apDevice[8])) {
        case IDCANCEL:
          throw "";
        case IDC_SetDisk:
          break;
        case IDC_Load:
          wsprintf((char*)gabAutotype, "load\"%s\",8,1\r", gacC64Name);
          break;
        case IDC_Run:
          wsprintf((char*)gabAutotype, "load\"%s\",8,1\rrun\r", gacC64Name);
          break;
        }
      }
  
    } else { 
  
      // it is a c64 or p00 file
      pC64->IECMaster.apDevice[8] = new CHarddisk();

      if (_chdir(acFileName) == 0) {
        pC64->IECMaster.apDevice[8]->SetDisk(CString(acFileName));
      } else {

        // get directory
        char* pcDOSName = acFileName;
        char* pcSlash = strrchr(acFileName, '\\');
        if (pcSlash != NULL) {
          pcDOSName = pcSlash + 1;
          int iLength = pcSlash - acFileName;
          if (iLength >= 1 && strchr(":\\/,;", acFileName[iLength - 1]) != NULL) {
            iLength++;
          }
          pC64->IECMaster.apDevice[8]->SetDisk(CString(acFileName, iLength));
        }
  
        // get C64 name
        extern char* GetC64ExtAndCutDOSName(char* pcDOSName);
        char* pcExt = GetC64ExtAndCutDOSName(pcDOSName);
        extern char* DOSNameToC64Name(char* pcDOS, int iMaxLength);
        char* pcC64Name = DOSNameToC64Name(pcDOSName, 70);
        for (char* pc = pcC64Name; *pc != 0; pc++) {
          if (isalpha(*pc)) {
            *pc ^= 32;
          }
        }
        wsprintf((char*)gabAutotype, "load\"%s\",8,1\rrun\r", pcC64Name);

      }
    }
  } else {
    pC64->IECMaster.apDevice[8] = new CHarddisk();
  }

  // set code trap for autotype at $E5CD
  if (gabAutotype[0] != 0) {
    gbAutotypeTrap = pC64->pCPU->AllocCodeTrap(pC64, make_bpfn((pfnv)OnAutotype));
    pC64->pCPU->SetTrap(pC64->pbKernal + 0x05CD, gbAutotypeTrap);
    gpbAutotype = gabAutotype;
  }
}
