/** @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;

  //
  // 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;
    }
    if (((KeyData.KeyState.KeyShiftState & EFI_SHIFT_STATE_VALID) != 0) &&
        (KeyData.KeyState.KeyShiftState != EFI_SHIFT_STATE_VALID)) {
      //
      // Shift key pressed.
      //
      continue;
    }
    //
    // pressed ESC
    //
    if (KeyData.Key.ScanCode == SCAN_ESC) {
      Size    = 0;
      Status  = EFI_NOT_READY;
      break;
    }
    //
    // return pressed
    //
    if (KeyData.Key.UnicodeChar == CHAR_LINEFEED || KeyData.Key.UnicodeChar == CHAR_CARRIAGE_RETURN) {
      break;
    } else if (KeyData.Key.UnicodeChar == CHAR_BACKSPACE) {
      //
      // backspace
      //
      if (Size > 0) {
        Size--;
        mReturnString[Size] = CHAR_NULL;
        if (!NoDisplay) {

          InputBarPrintInput (LastColumn, LastRow);

        }
      }
    } else if (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);
}
