/** @file
  Implements inputbar interface functions.

  Copyright (c) 2005 - 2018, Intel Corporation. All rights reserved. <BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "EditInputBar.h"
#include "UefiShellDebug1CommandsLib.h"

CHAR16                             *mPrompt;       // Input bar mPrompt string.
CHAR16                             *mReturnString; // The returned string.
UINTN                              StringSize;     // Size of mReturnString space size.
EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL  *mTextInEx;

/**
  Initialize the input bar.

  @param[in] TextInEx  Pointer to SimpleTextInEx instance in System Table.
**/
VOID
InputBarInit (
  IN EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL  *TextInEx
  )
{
  mPrompt       = NULL;
  mReturnString = NULL;
  StringSize    = 0;
  mTextInEx     = TextInEx;
}

/**
  Cleanup function for input bar.
**/
VOID
InputBarCleanup (
  VOID
  )
{
  //
  // free input bar's prompt and input string
  //
  SHELL_FREE_NON_NULL (mPrompt);
  SHELL_FREE_NON_NULL (mReturnString);
  mPrompt       = NULL;
  mReturnString = NULL;
}

/**
  Display the prompt.
  Do the requesting of input.

  @param[in]  LastColumn   The last printable column.
  @param[in]  LastRow      The last printable row.
**/
VOID
InputBarPrintInput (
  IN UINTN  LastColumn,
  IN UINTN  LastRow
  )
{
  UINTN   Limit;
  UINTN   Size;
  CHAR16  *Buffer;
  UINTN   Index;
  UINTN   mPromptLen;

  mPromptLen = StrLen (mPrompt);
  Limit      = LastColumn - mPromptLen - 1;
  Size       = StrLen (mReturnString);

  //
  // check whether the mPrompt length and input length will
  // exceed limit
  //
  if (Size <= Limit) {
    Buffer = mReturnString;
  } else {
    Buffer = mReturnString + Size - Limit;
  }

  gST->ConOut->EnableCursor (gST->ConOut, FALSE);

  ShellPrintEx (((INT32)mPromptLen), ((INT32)LastRow) - 1, L"%s", Buffer);
  Size = StrLen (Buffer);

  //
  // print " " after mPrompt
  //
  for (Index = Size; Index < Limit; Index++) {
    ShellPrintEx ((INT32)(mPromptLen + Size), ((INT32)LastRow) - 1, L" ");
  }

  gST->ConOut->EnableCursor (gST->ConOut, TRUE);
  gST->ConOut->SetCursorPosition (gST->ConOut, Size + mPromptLen, LastRow - 1);
}

typedef struct {
  UINT32    Foreground : 4;
  UINT32    Background : 3;
} INPUT_BAR_COLOR_ATTRIBUTES;

typedef union {
  INPUT_BAR_COLOR_ATTRIBUTES    Colors;
  UINTN                         Data;
} INPUT_BAR_COLOR_UNION;

/**
  The refresh function for InputBar, it will wait for user input

  @param[in] LastRow            The last printable row.
  @param[in] LastColumn         The last printable column.

  @retval EFI_SUCCESS           The operation was successful.
**/
EFI_STATUS
InputBarRefresh (
  UINTN  LastRow,
  UINTN  LastColumn
  )
{
  INPUT_BAR_COLOR_UNION   Orig;
  INPUT_BAR_COLOR_UNION   New;
  EFI_KEY_DATA            KeyData;
  UINTN                   Size;
  EFI_STATUS              Status;
  BOOLEAN                 NoDisplay;
  UINTN                   EventIndex;
  UINTN                   CursorRow;
  UINTN                   CursorCol;
  BOOLEAN                 ShiftPressed;
  BOOLEAN                 ModifiersPressed;

  //
  // variable initialization
  //
  Size   = 0;
  Status = EFI_SUCCESS;

  //
  // back up the old screen attributes
  //
  CursorCol             = gST->ConOut->Mode->CursorColumn;
  CursorRow             = gST->ConOut->Mode->CursorRow;
  Orig.Data             = gST->ConOut->Mode->Attribute;
  New.Data              = 0;
  New.Colors.Foreground = Orig.Colors.Background & 0xF;
  New.Colors.Background = Orig.Colors.Foreground & 0x7;

  gST->ConOut->SetAttribute (gST->ConOut, New.Data & 0x7F);

  //
  // clear input bar
  //
  EditorClearLine (LastRow, LastColumn, LastRow);

  gST->ConOut->SetCursorPosition (gST->ConOut, 0, LastRow - 1);
  ShellPrintHiiEx (-1, -1, NULL, STRING_TOKEN (STR_EDIT_LIBINPUTBAR_MAININPUTBAR), gShellDebug1HiiHandle, mPrompt);

  //
  // this is a selection mPrompt, cursor will stay in edit area
  // actually this is for search , search/replace
  //
  if (StrStr (mPrompt, L"Yes/No")) {
    NoDisplay = TRUE;
    gST->ConOut->SetCursorPosition (gST->ConOut, CursorCol, CursorRow);
    gST->ConOut->SetAttribute (gST->ConOut, Orig.Data);
  } else {
    NoDisplay = FALSE;
  }

  //
  // wait for user input
  //
  for ( ; ;) {
    Status = gBS->WaitForEvent (1, &mTextInEx->WaitForKeyEx, &EventIndex);
    if (EFI_ERROR (Status) || (EventIndex != 0)) {
      continue;
    }

    Status = mTextInEx->ReadKeyStrokeEx (mTextInEx, &KeyData);
    if (EFI_ERROR (Status)) {
      continue;
    }

    ModifiersPressed = ((KeyData.KeyState.KeyShiftState & EFI_SHIFT_STATE_VALID) != 0)
                    && (KeyData.KeyState.KeyShiftState != EFI_SHIFT_STATE_VALID);

    //
    // TRUE if Shift is pressed and no other modifiers are pressed
    //
    ShiftPressed = ModifiersPressed &&
                   ((KeyData.KeyState.KeyShiftState &
                     ~(EFI_SHIFT_STATE_VALID | EFI_LEFT_SHIFT_PRESSED | EFI_RIGHT_SHIFT_PRESSED)) == 0);

    if (ModifiersPressed && !ShiftPressed) {
      continue;
    }

    //
    // pressed ESC
    //
    if (!ModifiersPressed && KeyData.Key.ScanCode == SCAN_ESC) {
      Size    = 0;
      Status  = EFI_NOT_READY;
      break;
    }

    //
    // return pressed
    //
    if (!ModifiersPressed
      && (KeyData.Key.UnicodeChar == CHAR_LINEFEED || KeyData.Key.UnicodeChar == CHAR_CARRIAGE_RETURN)) {
      break;
    } else if (!ModifiersPressed && KeyData.Key.UnicodeChar == CHAR_BACKSPACE) {
      //
      // backspace
      //
      if (Size > 0) {
        Size--;
        mReturnString[Size] = CHAR_NULL;
        if (!NoDisplay) {
          InputBarPrintInput (LastColumn, LastRow);
        }
      }
    } else if ((!ModifiersPressed || ShiftPressed)
            && KeyData.Key.UnicodeChar <= 127 && KeyData.Key.UnicodeChar >= 32) {
      //
      // VALID ASCII char pressed
      //
      mReturnString[Size] = KeyData.Key.UnicodeChar;

      //
      // should be less than specified length
      //
      if (Size >= StringSize) {
        continue;
      }

      Size++;

      mReturnString[Size] = CHAR_NULL;

      if (!NoDisplay) {
        InputBarPrintInput (LastColumn, LastRow);
      } else {
        //
        // if just choose yes/no
        //
        break;
      }
    }
  }

  mReturnString[Size] = CHAR_NULL;

  //
  // restore screen attributes
  //
  gST->ConOut->SetCursorPosition (gST->ConOut, CursorCol, CursorRow);
  gST->ConOut->SetAttribute (gST->ConOut, Orig.Data);

  return Status;
}

/**
  SetPrompt and wait for input.

  @param[in] Str                The prompt string.

  @retval EFI_SUCCESS           The operation was successful.
  @retval EFI_OUT_OF_RESOURCES  A memory allocation failed.
**/
EFI_STATUS
InputBarSetPrompt (
  IN CONST CHAR16  *Str
  )
{
  //
  // FREE the old mPrompt string
  //
  SHELL_FREE_NON_NULL (mPrompt);

  mPrompt = CatSPrint (NULL, L"%s ", Str);
  if (mPrompt == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  return EFI_SUCCESS;
}

/**
  Set the size of the string in characters.

  @param[in] Size               The max number of characters to accept.

  @retval EFI_SUCCESS           The operation was successful.
  @retval EFI_OUT_OF_RESOURCES  A memory allocation failed.
**/
EFI_STATUS
InputBarSetStringSize (
  UINTN  Size
  )
{
  //
  // free the old ReturnStirng
  //
  SHELL_FREE_NON_NULL (mReturnString);

  StringSize    = Size;
  mReturnString = AllocateZeroPool ((StringSize + 1) * sizeof (mReturnString[0]));
  if (mReturnString == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  return EFI_SUCCESS;
}

/**
  Function to retrieve the input from the user.

  @retval NULL                  No input has been received.
  @return The string that was input.
**/
CONST CHAR16 *
InputBarGetString (
  VOID
  )
{
  return (mReturnString);
}
