/** @file
  Main file for NULL named library for level 1 shell command functions.

  (C) Copyright 2013 Hewlett-Packard Development Company, L.P.<BR>
  Copyright (c) 2009 - 2018, Intel Corporation. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "UefiShellLevel1CommandsLib.h"

STATIC CONST CHAR16 mFileName[] = L"ShellCommands";
EFI_HANDLE gShellLevel1HiiHandle = NULL;

/**
  Return the help text filename.  Only used if no HII information found.

  @retval the filename.
**/
CONST CHAR16*
EFIAPI
ShellCommandGetManFileNameLevel1 (
  VOID
  )
{
  return (mFileName);
}

/**
  Constructor for the Shell Level 1 Commands library.

  Install the handlers for level 1 UEFI Shell 2.0 commands.

  @param ImageHandle    the image handle of the process
  @param SystemTable    the EFI System Table pointer

  @retval EFI_SUCCESS        the shell command handlers were installed sucessfully
  @retval EFI_UNSUPPORTED    the shell level required was not found.
**/
EFI_STATUS
EFIAPI
ShellLevel1CommandsLibConstructor (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  //
  // if shell level is less than 2 do nothing
  //
  if (PcdGet8(PcdShellSupportLevel) < 1) {
    return (EFI_SUCCESS);
  }

  gShellLevel1HiiHandle = HiiAddPackages (&gShellLevel1HiiGuid, gImageHandle, UefiShellLevel1CommandsLibStrings, NULL);
  if (gShellLevel1HiiHandle == NULL) {
    return (EFI_DEVICE_ERROR);
  }

  //
  // install our shell command handlers that are always installed
  //
  ShellCommandRegisterCommandName(L"stall",  ShellCommandRunStall   , ShellCommandGetManFileNameLevel1, 1, L"", FALSE, gShellLevel1HiiHandle, (EFI_STRING_ID)(PcdGet8(PcdShellSupportLevel) < 3 ? 0 : STRING_TOKEN(STR_GET_HELP_STALL) ));
  ShellCommandRegisterCommandName(L"for",    ShellCommandRunFor     , ShellCommandGetManFileNameLevel1, 1, L"", FALSE, gShellLevel1HiiHandle, (EFI_STRING_ID)(PcdGet8(PcdShellSupportLevel) < 3 ? 0 : STRING_TOKEN(STR_GET_HELP_FOR)   ));
  ShellCommandRegisterCommandName(L"goto",   ShellCommandRunGoto    , ShellCommandGetManFileNameLevel1, 1, L"", FALSE, gShellLevel1HiiHandle, (EFI_STRING_ID)(PcdGet8(PcdShellSupportLevel) < 3 ? 0 : STRING_TOKEN(STR_GET_HELP_GOTO)  ));
  ShellCommandRegisterCommandName(L"if",     ShellCommandRunIf      , ShellCommandGetManFileNameLevel1, 1, L"", FALSE, gShellLevel1HiiHandle, (EFI_STRING_ID)(PcdGet8(PcdShellSupportLevel) < 3 ? 0 : STRING_TOKEN(STR_GET_HELP_IF)    ));
  ShellCommandRegisterCommandName(L"shift",  ShellCommandRunShift   , ShellCommandGetManFileNameLevel1, 1, L"", FALSE, gShellLevel1HiiHandle, (EFI_STRING_ID)(PcdGet8(PcdShellSupportLevel) < 3 ? 0 : STRING_TOKEN(STR_GET_HELP_SHIFT) ));
  ShellCommandRegisterCommandName(L"exit",   ShellCommandRunExit    , ShellCommandGetManFileNameLevel1, 1, L"", TRUE , gShellLevel1HiiHandle, (EFI_STRING_ID)(PcdGet8(PcdShellSupportLevel) < 3 ? 0 : STRING_TOKEN(STR_GET_HELP_EXIT)  ));
  ShellCommandRegisterCommandName(L"else",   ShellCommandRunElse    , ShellCommandGetManFileNameLevel1, 1, L"", FALSE, gShellLevel1HiiHandle, (EFI_STRING_ID)(PcdGet8(PcdShellSupportLevel) < 3 ? 0 : STRING_TOKEN(STR_GET_HELP_ELSE)  ));
  ShellCommandRegisterCommandName(L"endif",  ShellCommandRunEndIf   , ShellCommandGetManFileNameLevel1, 1, L"", FALSE, gShellLevel1HiiHandle, (EFI_STRING_ID)(PcdGet8(PcdShellSupportLevel) < 3 ? 0 : STRING_TOKEN(STR_GET_HELP_ENDIF) ));
  ShellCommandRegisterCommandName(L"endfor", ShellCommandRunEndFor  , ShellCommandGetManFileNameLevel1, 1, L"", FALSE, gShellLevel1HiiHandle, (EFI_STRING_ID)(PcdGet8(PcdShellSupportLevel) < 3 ? 0 : STRING_TOKEN(STR_GET_HELP_ENDFOR)));

  return (EFI_SUCCESS);
}

/**
  Destructor for the library.  free any resources.

  @param ImageHandle            The image handle of the process.
  @param SystemTable            The EFI System Table pointer.
**/
EFI_STATUS
EFIAPI
ShellLevel1CommandsLibDestructor (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  if (gShellLevel1HiiHandle != NULL) {
    HiiRemovePackages(gShellLevel1HiiHandle);
  }
  return (EFI_SUCCESS);
}

/**
  Test a node to see if meets the criterion.

  It functions so that count starts at 1 and it increases or decreases when it
  hits the specified tags.  when it hits zero the location has been found.

  DecrementerTag and IncrementerTag are used to get around for/endfor and
  similar paired types where the entire middle should be ignored.

  If label is used it will be used instead of the count.

  @param[in] Function          The function to use to enumerate through the
                               list.  Normally GetNextNode or GetPreviousNode.
  @param[in] DecrementerTag    The tag to decrement the count at.
  @param[in] IncrementerTag    The tag to increment the count at.
  @param[in] Label             A label to look for.
  @param[in, out] ScriptFile   The pointer to the current script file structure.
  @param[in] MovePast          TRUE makes function return 1 past the found
                               location.
  @param[in] FindOnly          TRUE to not change the ScriptFile.
  @param[in] CommandNode       The pointer to the Node to test.
  @param[in, out] TargetCount  The pointer to the current count.
**/
BOOLEAN
TestNodeForMove (
  IN CONST LIST_MANIP_FUNC      Function,
  IN CONST CHAR16               *DecrementerTag,
  IN CONST CHAR16               *IncrementerTag,
  IN CONST CHAR16               *Label OPTIONAL,
  IN OUT SCRIPT_FILE            *ScriptFile,
  IN CONST BOOLEAN              MovePast,
  IN CONST BOOLEAN              FindOnly,
  IN CONST SCRIPT_COMMAND_LIST  *CommandNode,
  IN OUT UINTN                  *TargetCount
  )
{
  BOOLEAN             Found;
  CHAR16              *CommandName;
  CHAR16              *CommandNameWalker;
  CHAR16              *TempLocation;

  Found = FALSE;

  //
  // get just the first part of the command line...
  //
  CommandName   = NULL;
  CommandName   = StrnCatGrow(&CommandName, NULL, CommandNode->Cl, 0);
  if (CommandName == NULL) {
    return (FALSE);
  }

  CommandNameWalker = CommandName;

  //
  // Skip leading spaces and tabs.
  //
  while ((CommandNameWalker[0] == L' ') || (CommandNameWalker[0] == L'\t')) {
    CommandNameWalker++;
  }
  TempLocation  = StrStr(CommandNameWalker, L" ");

  if (TempLocation != NULL) {
    *TempLocation = CHAR_NULL;
  }

  //
  // did we find a nested item ?
  //
  if (gUnicodeCollation->StriColl(
      gUnicodeCollation,
      (CHAR16*)CommandNameWalker,
      (CHAR16*)IncrementerTag) == 0) {
    (*TargetCount)++;
  } else if (gUnicodeCollation->StriColl(
      gUnicodeCollation,
      (CHAR16*)CommandNameWalker,
      (CHAR16*)DecrementerTag) == 0) {
    if (*TargetCount > 0) {
      (*TargetCount)--;
    }
  }

  //
  // did we find the matching one...
  //
  if (Label == NULL) {
    if (*TargetCount == 0) {
      Found = TRUE;
      if (!FindOnly) {
        if (MovePast) {
          ScriptFile->CurrentCommand = (SCRIPT_COMMAND_LIST *)(*Function)(&ScriptFile->CommandList, &CommandNode->Link);
        } else {
          ScriptFile->CurrentCommand = (SCRIPT_COMMAND_LIST *)CommandNode;
        }
      }
    }
  } else {
    if (gUnicodeCollation->StriColl(
      gUnicodeCollation,
      (CHAR16*)CommandNameWalker,
      (CHAR16*)Label) == 0
      && (*TargetCount) == 0) {
      Found = TRUE;
      if (!FindOnly) {
        //
        // we found the target label without loops
        //
        if (MovePast) {
          ScriptFile->CurrentCommand = (SCRIPT_COMMAND_LIST *)(*Function)(&ScriptFile->CommandList, &CommandNode->Link);
        } else {
          ScriptFile->CurrentCommand = (SCRIPT_COMMAND_LIST *)CommandNode;
        }
      }
    }
  }

  //
  // Free the memory for this loop...
  //
  FreePool(CommandName);
  return (Found);
}

/**
  Move the script pointer from 1 tag (line) to another.

  It functions so that count starts at 1 and it increases or decreases when it
  hits the specified tags.  when it hits zero the location has been found.

  DecrementerTag and IncrementerTag are used to get around for/endfor and
  similar paired types where the entire middle should be ignored.

  If label is used it will be used instead of the count.

  @param[in] Function          The function to use to enumerate through the
                               list.  Normally GetNextNode or GetPreviousNode.
  @param[in] DecrementerTag    The tag to decrement the count at.
  @param[in] IncrementerTag    The tag to increment the count at.
  @param[in] Label             A label to look for.
  @param[in, out] ScriptFile   The pointer to the current script file structure.
  @param[in] MovePast          TRUE makes function return 1 past the found
                               location.
  @param[in] FindOnly          TRUE to not change the ScriptFile.
  @param[in] WrapAroundScript  TRUE to wrap end-to-begining or vise versa in
                               searching.
**/
BOOLEAN
MoveToTag (
  IN CONST LIST_MANIP_FUNC      Function,
  IN CONST CHAR16               *DecrementerTag,
  IN CONST CHAR16               *IncrementerTag,
  IN CONST CHAR16               *Label OPTIONAL,
  IN OUT SCRIPT_FILE            *ScriptFile,
  IN CONST BOOLEAN              MovePast,
  IN CONST BOOLEAN              FindOnly,
  IN CONST BOOLEAN              WrapAroundScript
  )
{
  SCRIPT_COMMAND_LIST *CommandNode;
  BOOLEAN             Found;
  UINTN               TargetCount;

  if (Label == NULL) {
    TargetCount       = 1;
  } else {
    TargetCount       = 0;
  }

  if (ScriptFile == NULL) {
    return FALSE;
  }

  for (CommandNode = (SCRIPT_COMMAND_LIST *)(*Function)(&ScriptFile->CommandList, &ScriptFile->CurrentCommand->Link), Found = FALSE
    ;  !IsNull(&ScriptFile->CommandList, &CommandNode->Link)&& !Found
    ;  CommandNode = (SCRIPT_COMMAND_LIST *)(*Function)(&ScriptFile->CommandList, &CommandNode->Link)
   ){
    Found = TestNodeForMove(
      Function,
      DecrementerTag,
      IncrementerTag,
      Label,
      ScriptFile,
      MovePast,
      FindOnly,
      CommandNode,
      &TargetCount);
  }

  if (WrapAroundScript && !Found) {
    for (CommandNode = (SCRIPT_COMMAND_LIST *)GetFirstNode(&ScriptFile->CommandList), Found = FALSE
      ;  CommandNode != ScriptFile->CurrentCommand && !Found
      ;  CommandNode = (SCRIPT_COMMAND_LIST *)(*Function)(&ScriptFile->CommandList, &CommandNode->Link)
     ){
      Found = TestNodeForMove(
        Function,
        DecrementerTag,
        IncrementerTag,
        Label,
        ScriptFile,
        MovePast,
        FindOnly,
        CommandNode,
        &TargetCount);
    }
  }
  return (Found);
}