/** @file
  Copyright (c) 2022, Mikhail Krichanov. All rights reserved.
  SPDX-License-Identifier: BSD-3-Clause
**/

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <stdbool.h>

#include <UserFile.h>

#define DEFAULT_MC_ALIGNMENT       16
#define DEFAULT_MC_PAD_BYTE_VALUE  0xFF

typedef struct {
  UINT32  HeaderVersion;
  UINT32  PatchId;
  UINT32  Date;
  UINT32  CpuId;
  UINT32  Checksum;
  UINT32  LoaderVersion;
  UINT32  PlatformId;
  UINT32  DataSize;   // if 0, then TotalSize = 2048, and TotalSize field is invalid
  UINT32  TotalSize;  // number of bytes
  UINT32  Reserved[3];
} MICROCODE_HEADER;

static
RETURN_STATUS
TxtToBin (
  IN const char *TxtName,
  IN const char *BinName
  )
{
  char              *Txt;
  char              *TxtStart;
  uint32_t          TxtSize;
  UINT32            *Buffer;
  UINT32            *BufferStart;
  UINT32            FileLength;
  UINT32            Index;
  MICROCODE_HEADER  *Micro;
  UINT32            CheckSum;

  assert (TxtName != NULL);
  assert (BinName != NULL);

  Txt = (char *)UserReadFile (TxtName, &TxtSize);
  if (Txt == NULL) {
    fprintf (stderr, "MicroTool: Could not open %s: %s\n", TxtName, strerror (errno));
    return RETURN_ABORTED;
  }

  Buffer = calloc (1, TxtSize);
  if (Buffer == NULL) {
    fprintf (stderr, "MicroTool: Could not allocate memory for Buffer\n");
    free (Txt);
    return RETURN_OUT_OF_RESOURCES;
  }

  BufferStart = Buffer;
  TxtStart    = Txt;
  FileLength  = 0;
  for (Index = 0; Index < TxtSize; ++Index, ++Txt) {
    //
    // Skip Blank Lines and Comment Lines
    //
    if (isspace ((int)*Txt)) {
      continue;
    }

    if (*Txt == ';') {
      while ((Index < TxtSize) && (*Txt != '\n')) {
        ++Index;
        ++Txt;
      }

      if (Index == TxtSize) {
        break;
      }

      ++Index;
      ++Txt;
    }
    //
    // Look for
    // dd 000000001h ; comment
    // dd XXXXXXXX
    // DD  XXXXXXXXX
    //  DD XXXXXXXXX
    //
    if (((Index + 2) < TxtSize) && (tolower ((int)Txt[0]) == 'd') && (tolower ((int)Txt[1]) == 'd') && isspace ((int)Txt[2])) {
      //
      // Skip blanks and look for a hex digit
      //
      Txt   += 3;
      Index += 3;
      while ((Index < TxtSize) && isspace ((int)*Txt)) {
        ++Index;
        ++Txt;
      }

      if (Index == TxtSize) {
        break;
      }

      if (isxdigit ((int)*Txt)) {
        if (sscanf (Txt, "%X", Buffer) != 1) {
          fprintf (stderr, "MicroTool: Could not write into Buffer\n");
          free (TxtStart);
          free (BufferStart);
          return RETURN_ABORTED;
        }

        while ((Index < TxtSize) && (*Txt != '\n')) {
          ++Index;
          ++Txt;
        }
      }

      ++Buffer;
      FileLength += sizeof (*Buffer);
      continue;
    }

    fprintf (stderr, "MicroTool: Corrupted input file\n");
    break;
  }

  free (TxtStart);

  if (FileLength == 0) {
    fprintf (stderr, "MicroTool: No parseable data found in file %s\n", TxtName);
    free (BufferStart);
    return RETURN_INVALID_PARAMETER;
  }

  if (FileLength < sizeof (MICROCODE_HEADER)) {
    fprintf (stderr, "MicroTool: Amount of parsable data in %s is insufficient to contain a microcode header\n", TxtName);
    free (BufferStart);
    return RETURN_VOLUME_CORRUPTED;
  }

  //
  // Can't do much checking on the header because, per the spec, the
  // DataSize field may be 0, which means DataSize = 2000 and TotalSize = 2K,
  // and the TotalSize field is invalid (actually missing). Thus we can't
  // even verify the Reserved fields are 0.
  //
  Micro = (MICROCODE_HEADER *)BufferStart;
  if (Micro->DataSize == 0) {
    Index = 2048;
  } else {
    Index = Micro->TotalSize;
  }

  if (Index != FileLength) {
    fprintf (stderr, "MicroTool: File length of %s (0x%x) does not equal expected TotalSize: 0x%04X\n", TxtName, FileLength, Index);
    free (BufferStart);
    return RETURN_VOLUME_CORRUPTED;
  }

  //
  // Checksum the contents
  //
  Buffer = BufferStart;
  CheckSum  = 0;
  Index     = 0;
  while (Index < FileLength) {
    CheckSum += *Buffer;

    ++Buffer;
    Index += sizeof (*Buffer);
  }

  if (CheckSum != 0) {
    fprintf (stderr, "MicroTool: Checksum (0x%x) failed on file %s\n", CheckSum, TxtName);
    free (BufferStart);
    return RETURN_VOLUME_CORRUPTED;
  }

  UserWriteFile (BinName, BufferStart, FileLength);

  free (BufferStart);

  return RETURN_SUCCESS;
}

static
RETURN_STATUS
Merge (
  IN const char *Output,
  IN const char *FileNames[],
  IN UINT32     NumOfFiles
  )
{
  void      *File;
  uint32_t  FileSize;
  UINT32    Index;
  UINT32    Total;
  char      *Buffer;
  char      *BufferStart;
  UINT32    Addend;

  assert (Output    != NULL);
  assert (FileNames != NULL);

  Total = 0;

  for (Index = 0; Index < NumOfFiles; ++Index) {
    File = UserReadFile (FileNames[Index], &FileSize);
    if (File == NULL) {
      fprintf (stderr, "MicroTool: Could not open %s: %s\n", FileNames[Index], strerror (errno));
      return RETURN_ABORTED;
    }

    Total += ALIGN_VALUE (FileSize, DEFAULT_MC_ALIGNMENT);

    free (File);
  }

  Buffer = calloc (1, Total);
  if (Buffer == NULL) {
    fprintf (stderr, "MicroTool: Could not allocate memory for Buffer\n");
    return RETURN_OUT_OF_RESOURCES;
  }

  BufferStart = Buffer;

  for (Index = 0; Index < NumOfFiles; ++Index) {
    File = UserReadFile (FileNames[Index], &FileSize);
    if (File == NULL) {
      fprintf (stderr, "MicroTool: Could not reopen %s: %s\n", FileNames[Index], strerror (errno));
      free (BufferStart);
      return RETURN_ABORTED;
    }

    memcpy (Buffer, File, FileSize);
    Buffer += FileSize;

    Addend = ALIGN_VALUE_ADDEND (FileSize, DEFAULT_MC_ALIGNMENT);
    memset (Buffer, DEFAULT_MC_PAD_BYTE_VALUE, Addend);
    Buffer += Addend;

    free (File);
  }

  UserWriteFile (Output, BufferStart, Total);

  free (BufferStart);

  return RETURN_SUCCESS;
}

int main (int argc, const char *argv[])
{
  RETURN_STATUS Status;
  UINT32        NumOfFiles;

  if (argc < 2) {
    fprintf (stderr, "MicroTool: No command is specified\n");
    assert (false);
    return -1;
  }

  if (strcmp (argv[1], "TxtToBin") == 0) {
    if (argc < 4) {
      fprintf (stderr, "MicroTool: Command arguments are missing\n");
      fprintf (stderr, "    Usage: MicroTool TxtToBin InputFile OutputFile\n");
      assert (false);
      return -1;
    }

    Status = TxtToBin (argv[2], argv [3]);
    if (RETURN_ERROR (Status)) {
      assert (false);
      return -1;
    }
  } else if (strcmp (argv[1], "Merge") == 0) {
    if (argc < 4) {
      fprintf (stderr, "MicroTool: Command arguments are missing\n");
      fprintf (stderr, "    Usage: MicroTool Merge OutputFile InputFile1 InputFile2 ...\n");
      assert (false);
      return -1;
    }

    NumOfFiles = (UINT32)argc - 3U;

    Status = Merge (argv[2], &argv[3], NumOfFiles);
    if (RETURN_ERROR (Status)) {
      assert (false);
      return -1;
    }
  }

  return 0;
}
