/** @file
  Copyright (C) 2021, Mike Beaton. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-3-Clause
**/

#ifndef LINUX_BOOT_INTERNAL_H
#define LINUX_BOOT_INTERNAL_H

#if !defined (OC_TRACE_GRUB_VARS)
#define OC_TRACE_GRUB_VARS  DEBUG_VERBOSE
#endif

/*
  Standard attached drives on OVMF appear as MBR, so it can be convenient when
  debugging to allow entries with incorrect (i.e. specifies no/every drive)
  root=... .
*/
// #define LINUX_ALLOW_MBR

#include <Uefi.h>
#include <Library/OcBootManagementLib.h>
#include <Library/OcMiscLib.h>

#define IS_DIGIT(c)  ((c) >= '0' && (c) <= '9')
#define IS_ALPHA(c)  (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z'))

/*
  Allow scan of ESP.
*/
#define LINUX_BOOT_SCAN_ESP  BIT0

/*
  Allow scan of XBOOTLDR.
*/
#define LINUX_BOOT_SCAN_XBOOTLDR  BIT1

/*
  Allow scan of Linux Root filesystems.
*/
#define LINUX_BOOT_SCAN_LINUX_ROOT  BIT2

/*
  Allow scan of Linux Data filesystems.
*/
#define LINUX_BOOT_SCAN_LINUX_DATA  BIT3

/*
  Some space for additional file systems.
*/
/*
  Allow scan of any filesystem not explicitly mentioned
  (including but not limited FAT other than ESP, and NTFS).
*/
#define LINUX_BOOT_SCAN_OTHER  BIT7

/*
  Allow autodetect of vmlinuz-{version} and matching init*-{version},
  if scan for usable /loader/entries fails.
*/
#define LINUX_BOOT_ALLOW_AUTODETECT  BIT8

/*
  Define entry id by the first part (to dash) of the filename
  from which it was created. Results in the first matching entry (after sorting)
  always being the default entry, which results in updated Linux becoming the
  new default automatically.
*/
#define LINUX_BOOT_USE_LATEST  BIT9

/*
  If set, add "ro" as initial option to all distros. Can be specified per
  FS by using argument autoopts:{partuuid}+=ro instead.
*/
#define LINUX_BOOT_ADD_RO  BIT10

/*
  If set, add "rw" as initial option to all distros. Can be specified per
  FS by using argument autoopts:{partuuid}+=rw instead.
*/
#define LINUX_BOOT_ADD_RW  BIT11

/*
  TODO: Both blspec-style and autodetect can make use of grub.cfg info if this flag is set.
*/
// #define LINUX_BOOT_ALLOW_PARSE_GRUB     BIT12

/*
  Add root= option if missing in loader/entries *.conf options.
*/
#define LINUX_BOOT_ALLOW_CONF_AUTO_ROOT  BIT13

/*
  Add some additional log info.
*/
#define LINUX_BOOT_LOG_VERBOSE  BIT14

/*
  Prepend filesystem type and first 8 hex digits of PARTUUID to discovered
  entry titles, to help in debugging where entries came from.
*/
#define LINUX_BOOT_ADD_DEBUG_INFO  BIT15

#define LINUX_BOOT_ALL  (           \
  LINUX_BOOT_SCAN_ESP             | \
  LINUX_BOOT_SCAN_XBOOTLDR        | \
  LINUX_BOOT_SCAN_LINUX_ROOT      | \
  LINUX_BOOT_SCAN_LINUX_DATA      | \
  LINUX_BOOT_SCAN_OTHER           | \
  LINUX_BOOT_ALLOW_AUTODETECT     | \
  LINUX_BOOT_USE_LATEST           | \
  LINUX_BOOT_ADD_RO               | \
  LINUX_BOOT_ADD_RW               | \
  LINUX_BOOT_ALLOW_CONF_AUTO_ROOT | \
  LINUX_BOOT_LOG_VERBOSE          | \
  LINUX_BOOT_ADD_DEBUG_INFO       \
  )

/*
  GRUB var error codes.
*/
#define VAR_ERR_NONE      (0)
#define VAR_ERR_INDENTED  BIT0    // Naive detection of GRUB conditional logic
#define VAR_ERR_HAS_VARS  BIT1    // We do not support nested vars (in name or value), even though GRUB does

/*
  Global flags for this instance of OpenLinuxBoot.efi.
*/
extern UINTN  gLinuxBootFlags;

/*
  Boot picker context.
*/
extern OC_PICKER_CONTEXT  *gPickerContext;

/*
  The array of loader entries, either really from *.conf files or generated by autodetect.
*/
extern OC_FLEX_ARRAY  *gLoaderEntries;

/*
  The current partuuid.
*/
extern EFI_GUID  gPartuuid;

/*
  Human readable ascii name of current file system type.
*/
extern CHAR8  *gFileSystemType;

// TODO: Are all of the below types used outside a single file?
// TODO: Is this file sensibly ordered?

/*
  Forward declaration of GRUB_VAR structure.
*/
typedef struct GRUB_VAR_ GRUB_VAR;

/*
  Forward declaration of LOADER_ENTRY structure.
*/
typedef struct LOADER_ENTRY_ LOADER_ENTRY;

/*
  GRUB vars.
*/
EFI_STATUS
InternalInitGrubVars (
  VOID
  );

VOID
InternalFreeGrubVars (
  VOID
  );

EFI_STATUS
InternalSetGrubVar (
  CHAR8  *Key,
  CHAR8  *Value,
  UINTN  Errors
  );

BOOLEAN
InternalHasGrubVars (
  CHAR8  *Options
  );

GRUB_VAR *
InternalGetGrubVar (
  IN     CONST CHAR8  *Key
  );

EFI_STATUS
InternalExpandGrubVarsForArray (
  IN OUT       OC_FLEX_ARRAY  *Options
  );

EFI_STATUS
InternalExpandGrubVars (
  IN     CONST CHAR8  *Options,
  IN OUT       CHAR8  **Result
  );

/*
  Process grubenv file.
*/
EFI_STATUS
InternalProcessGrubEnv (
  IN OUT       CHAR8  *Content,
  IN     CONST UINTN  Length
  );

/*
  Process grub.cfg file.
*/
EFI_STATUS
InternalProcessGrubCfg (
  IN OUT        CHAR8  *Content
  );

LOADER_ENTRY *
InternalAllocateLoaderEntry (
  VOID
  );

VOID
InternalFreeLoaderEntry (
  LOADER_ENTRY  *Entry
  );

EFI_STATUS
InternalProcessLoaderEntryFile (
  IN     CONST CHAR16     *FileName,
  IN OUT       CHAR8      *Content,
  OUT       LOADER_ENTRY  *Entry,
  IN     CONST BOOLEAN    Grub2
  );

/*
  GRUB variable.
*/
struct GRUB_VAR_ {
  //
  // Points within loaded file memory.
  //
  CHAR8    *Key;
  //
  // Points within loaded file memory, may be empty string.
  //
  CHAR8    *Value;
  //
  // GRUB var error code flags.
  //
  UINTN    Errors;
};

/*
  Loader entries.
*/

VOID
InternalFreePickerEntry (
  IN   OC_PICKER_ENTRY  *Entry
  );

EFI_STATUS
InternalConvertLoaderEntriesToBootEntries (
  IN   EFI_FILE_PROTOCOL  *RootDirectory,
  OUT  OC_PICKER_ENTRY    **Entries,
  OUT  UINTN              *NumEntries
  );

EFI_STATUS
InternalScanLoaderEntries (
  IN   EFI_FILE_PROTOCOL  *RootDirectory,
  OUT  OC_PICKER_ENTRY    **Entries,
  OUT  UINTN              *NumEntries
  );

EFI_STATUS
InternalIdVersionFromFileName (
  IN OUT LOADER_ENTRY  *Entry,
  IN     CHAR16        *FileName
  );

/*
  BLSpec / blscfg loader entry.
  Some items within here probably don't need to be allocated and could stay
  pointing within the source file as long as that is in memory (specifically
  Version and Linux, which are probably never going to be modified), but to
  keep things sane everything is (re)allocated.
*/
struct LOADER_ENTRY_ {
  //
  // Version.
  // Must come first since used for sorting.
  //
  CHAR8            *Version;
  //
  // Title.
  //
  CHAR8            *Title;
  //
  // Linux kernel filename.
  //
  CHAR8            *Linux;
  //
  // Options.
  //
  OC_FLEX_ARRAY    *Options;
  //
  // Initrds.
  //
  OC_FLEX_ARRAY    *Initrds;
  //
  // OpenCore entry id.
  // 'id' line is not read from .conf files even if present.
  //
  CHAR8            *OcId;
  //
  // Flavour.
  //
  CHAR8            *OcFlavour;
  //
  // Is this an auxiliary entry for OpenCore?
  //
  BOOLEAN          OcAuxiliary;
  //
  // Has this already been scanned whilst assigned main and auxiliary entries?
  //
  BOOLEAN          DuplicateIdScanned;
};

typedef struct VMLINUZ_FILE_ {
  CHAR16    *FileName;
  CHAR16    *Version;
  UINTN     StrLen;
} VMLINUZ_FILE;

/*
  Autodetect options.
*/
typedef struct AUTOOPTS_ {
  EFI_GUID    Guid;
  CHAR16      *Opts;
  BOOLEAN     PlusOpts;
} AUTOOPTS;

/*
  Load autoopts.
*/
EFI_STATUS
InternalPreloadAutoOpts (
  IN           OC_FLEX_ARRAY  *Options
  );

/*
  Autodetect.
*/
EFI_STATUS
InternalAutodetectLinux (
  IN   EFI_FILE_PROTOCOL  *RootDirectory,
  OUT  OC_PICKER_ENTRY    **Entries,
  OUT  UINTN              *NumEntries
  );

/*
  Insert root=PARTUUID=... option.
*/
EFI_STATUS
InsertRootOption (
  IN           OC_FLEX_ARRAY  *Options
  );

/*
  Sorts versions low to high.
*/
INTN
EFIAPI
InternalVersionCompare (
  IN CONST VOID  *Version1,
  IN CONST VOID  *Version2
  );

/*
  Sorts versions high to low.
*/
INTN
EFIAPI
InternalReverseVersionCompare (
  IN CONST VOID  *Version1,
  IN CONST VOID  *Version2
  );

#endif // LINUX_BOOT_INTERNAL_H
