/*
 * refit/scan/tool.c
 *
 * Copyright (c) 2006-2010 Christoph Pfisterer
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the
 *    distribution.
 *
 *  * Neither the name of Christoph Pfisterer nor the names of the
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "entry_scan.h"

//
// Clover File location to boot from on removable media devices
//
#define CLOVER_MEDIA_FILE_NAME_IA32    L"\\EFI\\CLOVER\\CLOVERIA32.EFI"
#define CLOVER_MEDIA_FILE_NAME_IA64    L"\\EFI\\CLOVER\\CLOVERIA64.EFI"
#define CLOVER_MEDIA_FILE_NAME_X64     L"\\EFI\\CLOVER\\CLOVERX64.EFI"
#define CLOVER_MEDIA_FILE_NAME_ARM     L"\\EFI\\CLOVER\\CLOVERARM.EFI"

#if   defined (MDE_CPU_IA32)
#define CLOVER_MEDIA_FILE_NAME   CLOVER_MEDIA_FILE_NAME_IA32
#elif defined (MDE_CPU_IPF)
#define CLOVER_MEDIA_FILE_NAME   CLOVER_MEDIA_FILE_NAME_IA64
#elif defined (MDE_CPU_X64)
#define CLOVER_MEDIA_FILE_NAME   CLOVER_MEDIA_FILE_NAME_X64
#elif defined (MDE_CPU_EBC)
#elif defined (MDE_CPU_ARM)
#define CLOVER_MEDIA_FILE_NAME   CLOVER_MEDIA_FILE_NAME_ARM
//#else
//#error Unknown Processor Type
#endif

#ifndef DEBUG_ALL
#define DEBUG_SCAN_TOOL 1
#else
#define DEBUG_SCAN_TOOL DEBUG_ALL
#endif

#if DEBUG_SCAN_TOOL == 0
#define DBG(...)
#else
#define DBG(...) DebugLog(DEBUG_SCAN_TOOL, __VA_ARGS__)
#endif

extern EMU_VARIABLE_CONTROL_PROTOCOL *gEmuVariableControl;

STATIC BOOLEAN AddToolEntry(IN CHAR16 *LoaderPath, IN CHAR16 *FullTitle, IN CHAR16 *LoaderTitle,
                            IN REFIT_VOLUME *Volume, IN EG_IMAGE *Image,
                            IN CHAR16 ShortcutLetter, IN CHAR16 *Options)
{
  LOADER_ENTRY *Entry;
  // Check the loader exists
  if ((LoaderPath == NULL) || (Volume == NULL) || (Volume->RootDir == NULL) ||
      !FileExists(Volume->RootDir, LoaderPath)) {
    return FALSE;
  }
  // Allocate the entry
  Entry = AllocateZeroPool(sizeof(LOADER_ENTRY));
  if (Entry == NULL) {
    return FALSE;
  }

  if (FullTitle) {
    Entry->me.Title = EfiStrDuplicate(FullTitle);
  } else {
    Entry->me.Title = PoolPrint(L"Start %s", LoaderTitle);
  }
  Entry->me.Tag = TAG_TOOL;
  Entry->me.Row = 1;
  Entry->me.ShortcutLetter = ShortcutLetter;
  Entry->me.Image = Image;
//  Entry->me.ImageHover = ImageHover;
  Entry->LoaderPath = EfiStrDuplicate(LoaderPath);
  Entry->DevicePath = FileDevicePath(Volume->DeviceHandle, Entry->LoaderPath);
  Entry->DevicePathString = FileDevicePathToStr(Entry->DevicePath);
  Entry->LoadOptions = Options ? Options : NULL;
  //actions
  Entry->me.AtClick = ActionSelect;
  Entry->me.AtDoubleClick = ActionEnter;
  Entry->me.AtRightClick = ActionHelp;

  DBG("found tool %s\n", LoaderPath);
  AddMenuEntry(&MainMenu, (REFIT_MENU_ENTRY *)Entry);
  return TRUE;
}

STATIC VOID AddCloverEntry(IN CHAR16 *LoaderPath, IN CHAR16 *LoaderTitle, IN REFIT_VOLUME *Volume)
{
  LOADER_ENTRY      *Entry, *SubEntry;
  REFIT_MENU_SCREEN *SubScreen;
//  EFI_STATUS        Status;

  // prepare the menu entry
  Entry = AllocateZeroPool(sizeof(LOADER_ENTRY));
  Entry->me.Title          = LoaderTitle;
  Entry->me.Tag            = TAG_CLOVER;
  Entry->me.Row            = 1;
  Entry->me.ShortcutLetter = 'C';
  Entry->me.Image          = BuiltinIcon(BUILTIN_ICON_FUNC_CLOVER);
  Entry->Volume = Volume;
  Entry->LoaderPath      = EfiStrDuplicate(LoaderPath);
  Entry->VolName         = Volume->VolName;
  Entry->DevicePath      = FileDevicePath(Volume->DeviceHandle, Entry->LoaderPath);
  Entry->DevicePathString = FileDevicePathToStr(Entry->DevicePath);
  Entry->Flags           = 0;
  Entry->LoadOptions     = NULL;
  Entry->LoaderType      = OSTYPE_OTHER;

  //actions
  Entry->me.AtClick = ActionEnter;
  Entry->me.AtDoubleClick = ActionDetails;
  Entry->me.AtRightClick = ActionDetails;

  // create the submenu
  SubScreen = AllocateZeroPool(sizeof(REFIT_MENU_SCREEN));
  SubScreen->Title = EfiStrDuplicate(LoaderTitle);
  SubScreen->TitleImage = Entry->me.Image;
  SubScreen->ID = SCREEN_BOOT;
  SubScreen->AnimeRun = GetAnime(SubScreen);
  AddMenuInfoLine(SubScreen, FileDevicePathToStr(Volume->DevicePath));

  if (gEmuVariableControl != NULL) {
    gEmuVariableControl->UninstallEmulation(gEmuVariableControl);
  }

//always add and always remove menu entries
  SubEntry = DuplicateLoaderEntry(Entry);
  if (SubEntry) {
    SubEntry->me.Title        = L"Add Clover boot options for all entries";
    SubEntry->LoadOptions     = L"BO-ADD";
    AddMenuEntry(SubScreen, (REFIT_MENU_ENTRY *)SubEntry);
  }

  SubEntry = DuplicateLoaderEntry(Entry);
  if (SubEntry) {
    SubEntry->me.Title        = L"Remove all Clover boot options";
    SubEntry->LoadOptions     = L"BO-REMOVE";
    AddMenuEntry(SubScreen, (REFIT_MENU_ENTRY *)SubEntry);
  }

  SubEntry = DuplicateLoaderEntry(Entry);
  if (SubEntry) {
    SubEntry->me.Title        = L"Print all UEFI boot options to log";
    SubEntry->LoadOptions     = L"BO-PRINT";
    AddMenuEntry(SubScreen, (REFIT_MENU_ENTRY *)SubEntry);
  }

  AddMenuEntry(SubScreen, &MenuEntryReturn);
  Entry->me.SubScreen = SubScreen;
  AddMenuEntry(&MainMenu, (REFIT_MENU_ENTRY *)Entry);
}

VOID ScanTool(VOID)
{
  EFI_STATUS              Status;
  UINTN                   VolumeIndex;
  REFIT_VOLUME            *Volume;
  VOID                    *Interface;

  if (GlobalConfig.DisableFlags & HIDEUI_FLAG_TOOLS)
    return;

  //    Print(L"Scanning for tools...\n");

  // look for the EFI shell
  if (!(GlobalConfig.DisableFlags & HIDEUI_FLAG_SHELL)) {
#if defined(MDE_CPU_X64)
//    if (gFirmwareClover) {
//      AddToolEntry(L"\\EFI\\CLOVER\\tools\\Shell64U.efi", NULL, L"EFI Shell 64", SelfVolume, BuiltinIcon(BUILTIN_ICON_TOOL_SHELL), 'S');
//    } else
    //there seems to be the best version
      if (!AddToolEntry(L"\\EFI\\CLOVER\\tools\\Shell64U.efi", NULL, L"UEFI Shell 64", SelfVolume, BuiltinIcon(BUILTIN_ICON_TOOL_SHELL), 'S', NULL)) {
        AddToolEntry(L"\\EFI\\CLOVER\\tools\\Shell64.efi", NULL, L"EFI Shell 64", SelfVolume, BuiltinIcon(BUILTIN_ICON_TOOL_SHELL), 'S', NULL);
      }
//  }
#else
    AddToolEntry(L"\\EFI\\CLOVER\\tools\\Shell32.efi", NULL, L"EFI Shell 32", SelfVolume, BuiltinIcon(BUILTIN_ICON_TOOL_SHELL), 'S', NULL);
#endif
  }

//  if (!gFirmwareClover) { //Slice: I wish to extend functionality on emulated nvram
    for (VolumeIndex = 0; VolumeIndex < VolumesCount; VolumeIndex++) {
      Volume = Volumes[VolumeIndex];
      if (!Volume->RootDir || !Volume->DeviceHandle) {
        continue;
      }

      Status = gBS->HandleProtocol (Volume->DeviceHandle, &gEfiPartTypeSystemPartGuid, &Interface);
      if (Status == EFI_SUCCESS) {
        DBG("Checking EFI partition Volume %d for Clover\n", VolumeIndex);

        // OSX adds label "EFI" to EFI volumes and some UEFIs see that
        // as a file. This file then blocks access to the /EFI directory.
        // We will delete /EFI file here and leave only /EFI directory.
        if (DeleteFile(Volume->RootDir, L"EFI")) {
          DBG(" Deleted /EFI label\n");
        }

        if (FileExists(Volume->RootDir, CLOVER_MEDIA_FILE_NAME)) {
          DBG(" Found Clover\n");
          // Volume->BootType = BOOTING_BY_EFI;
          AddCloverEntry(CLOVER_MEDIA_FILE_NAME, L"Clover Boot Options", Volume);
          break;
        }
      }
    }
//  }
}

// Add custom tool entries
VOID AddCustomTool(VOID)
{
  UINTN             VolumeIndex;
  REFIT_VOLUME      *Volume;
  CUSTOM_TOOL_ENTRY *Custom;
  EG_IMAGE          *Image;
  UINTN              i = 0;

//  DBG("Custom tool start\n");
  DbgHeader("AddCustomTool");
  // Traverse the custom entries
  for (Custom = gSettings.CustomTool; Custom; ++i, Custom = Custom->Next) {
    if (OSFLAG_ISSET(Custom->Flags, OSFLAG_DISABLED)) {
      DBG("Custom tool %d skipped because it is disabled.\n", i);
      continue;
    }
    if (!gSettings.ShowHiddenEntries && OSFLAG_ISSET(Custom->Flags, OSFLAG_HIDDEN)) {
      DBG("Custom tool %d skipped because it is hidden.\n", i);
      continue;
    }

    if (Custom->Volume) {
      DBG("Custom tool %d matching \"%s\" ...\n", i, Custom->Volume);
    }
    for (VolumeIndex = 0; VolumeIndex < VolumesCount; ++VolumeIndex) {
      Volume = Volumes[VolumeIndex];

      DBG("   Checking volume \"%s\" (%s) ... ", Volume->VolName, Volume->DevicePathString);

      // Skip Whole Disc Boot
      if (Volume->RootDir == NULL) {
        DBG("skipped because volume is not readable\n");
        continue;
      }

      // skip volume if its kind is configured as disabled
      if ((Volume->DiskKind == DISK_KIND_OPTICAL && (GlobalConfig.DisableFlags & VOLTYPE_OPTICAL)) ||
          (Volume->DiskKind == DISK_KIND_EXTERNAL && (GlobalConfig.DisableFlags & VOLTYPE_EXTERNAL)) ||
          (Volume->DiskKind == DISK_KIND_INTERNAL && (GlobalConfig.DisableFlags & VOLTYPE_INTERNAL)) ||
          (Volume->DiskKind == DISK_KIND_FIREWIRE && (GlobalConfig.DisableFlags & VOLTYPE_FIREWIRE)))
      {
        DBG("skipped because media is disabled\n");
        continue;
      }

      if (Custom->VolumeType != 0) {
        if ((Volume->DiskKind == DISK_KIND_OPTICAL && ((Custom->VolumeType & VOLTYPE_OPTICAL) == 0)) ||
            (Volume->DiskKind == DISK_KIND_EXTERNAL && ((Custom->VolumeType & VOLTYPE_EXTERNAL) == 0)) ||
            (Volume->DiskKind == DISK_KIND_INTERNAL && ((Custom->VolumeType & VOLTYPE_INTERNAL) == 0)) ||
            (Volume->DiskKind == DISK_KIND_FIREWIRE && ((Custom->VolumeType & VOLTYPE_FIREWIRE) == 0))) {
          DBG("skipped because media is ignored\n");
          continue;
        }
      }

      if (Volume->Hidden) {
        DBG("skipped because volume is hidden\n");
        continue;
      }

      // Check for exact volume matches
      if (Custom->Volume) {
        if ((StrStr(Volume->DevicePathString, Custom->Volume) == NULL) &&
            ((Volume->VolName == NULL) || (StrStr(Volume->VolName, Custom->Volume) == NULL))) {
          DBG("skipped\n");
          continue;
        }
      }
      // Check the tool exists on the volume
      if (!FileExists(Volume->RootDir, Custom->Path)) {
        DBG("skipped because path does not exist\n");
        continue;
      }
      // Change to custom image if needed
      Image = Custom->Image;
      if ((Image == NULL) && Custom->ImagePath) {
        Image = egLoadImage(Volume->RootDir, Custom->ImagePath, TRUE);
        if (Image == NULL) {
          Image = egLoadImage(ThemeDir, Custom->ImagePath, TRUE);
          if (Image == NULL) {
            Image = egLoadImage(SelfDir, Custom->ImagePath, TRUE);
            if (Image == NULL) {
              Image = egLoadImage(SelfRootDir, Custom->ImagePath, TRUE);
            }
          }
        }
      }
      if (Image == NULL) {
        Image = BuiltinIcon(BUILTIN_ICON_TOOL_SHELL);
      }

      // Create a legacy entry for this volume

      AddToolEntry(Custom->Path, Custom->FullTitle, Custom->Title, Volume, Image, Custom->Hotkey, Custom->Options);

      DBG("match!\n");
//      break; // break scan volumes, continue scan entries -- why?
    }
  }
//  DBG("Custom tool end\n");
}
