/** @file

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


**/

#include "Edb.h"

/**
  Set the current coordinates of the cursor position.

  @param  ConOut        Point to EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.
  @param  Column        The position to set the cursor to.
  @param  Row           The position to set the cursor to.
  @param  LineLength    Length of a line.
  @param  TotalRow      Total row of a screen.
  @param  Str           Point to the string.
  @param  StrPos        The position of the string.
  @param  Len           The length of the string.

**/
VOID
EFIAPI
SetCursorPosition (
  IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *ConOut,
  IN  UINTN                           Column,
  IN  INTN                            Row,
  IN  UINTN                           LineLength,
  IN  UINTN                           TotalRow,
  IN  CHAR16                          *Str,
  IN  UINTN                           StrPos,
  IN  UINTN                           Len
  );

/**

  Function waits for a given event to fire, or for an optional timeout to expire.

  @param  Event            - The event to wait for
  @param  Timeout          - An optional timeout value in 100 ns units.

  @retval EFI_SUCCESS       - Event fired before Timeout expired.
  @retval EFI_TIME_OUT     - Timout expired before Event fired..

**/
EFI_STATUS
EFIAPI
WaitForSingleEvent (
  IN EFI_EVENT  Event,
  IN UINT64     Timeout OPTIONAL
  )
{
  EFI_STATUS  Status;
  UINTN       Index;
  EFI_EVENT   TimerEvent;
  EFI_EVENT   WaitList[2];

  if (Timeout != 0) {
    //
    // Create a timer event
    //
    Status = gBS->CreateEvent (EVT_TIMER, 0, NULL, NULL, &TimerEvent);
    if (!EFI_ERROR (Status)) {
      //
      // Set the timer event
      //
      gBS->SetTimer (
             TimerEvent,
             TimerRelative,
             Timeout
             );

      //
      // Wait for the original event or the timer
      //
      WaitList[0] = Event;
      WaitList[1] = TimerEvent;
      Status      = gBS->WaitForEvent (2, WaitList, &Index);
      gBS->CloseEvent (TimerEvent);

      //
      // If the timer expired, change the return to timed out
      //
      if (!EFI_ERROR (Status) && (Index == 1)) {
        Status = EFI_TIMEOUT;
      }
    }
  } else {
    //
    // No timeout... just wait on the event
    //
    Status = gBS->WaitForEvent (1, &Event, &Index);
    ASSERT (!EFI_ERROR (Status));
    ASSERT (Index == 0);
  }

  return Status;
}

/**

  Move the cursor position one character backward.

  @param  LineLength       Length of a line. Get it by calling QueryMode
  @param  Column           Current column of the cursor position
  @param  Row              Current row of the cursor position

**/
VOID
EFIAPI
ConMoveCursorBackward (
  IN     UINTN  LineLength,
  IN OUT UINTN  *Column,
  IN OUT UINTN  *Row
  )
{
  ASSERT (Column != NULL);
  ASSERT (Row != NULL);
  //
  // If current column is 0, move to the last column of the previous line,
  // otherwise, just decrement column.
  //
  if (*Column == 0) {
    (*Column) = LineLength - 1;
    //
    //   if (*Row > 0) {
    //
    (*Row)--;
    //
    // }
    //
  } else {
    (*Column)--;
  }
}

/**

  Move the cursor position one character backward.

  @param  LineLength       Length of a line. Get it by calling QueryMode
  @param  TotalRow         Total row of a screen, get by calling QueryMode
  @param  Column           Current column of the cursor position
  @param  Row              Current row of the cursor position

**/
VOID
EFIAPI
ConMoveCursorForward (
  IN     UINTN  LineLength,
  IN     UINTN  TotalRow,
  IN OUT UINTN  *Column,
  IN OUT UINTN  *Row
  )
{
  ASSERT (Column != NULL);
  ASSERT (Row != NULL);
  //
  // If current column is at line end, move to the first column of the nest
  // line, otherwise, just increment column.
  //
  (*Column)++;
  if (*Column >= LineLength) {
    (*Column) = 0;
    if ((*Row) < TotalRow - 1) {
      (*Row)++;
    }
  }
}

CHAR16  mBackupSpace[EFI_DEBUG_INPUS_BUFFER_SIZE];
CHAR16  mInputBufferHistory[EFI_DEBUG_INPUS_BUFFER_SIZE];

/**

  Get user input.

  @param  Prompt       The prompt string.
  @param  InStr        Point to the input string.
  @param  StrLength    The max length of string user can input.

**/
VOID
EFIAPI
Input (
  IN CHAR16   *Prompt OPTIONAL,
  OUT CHAR16  *InStr,
  IN UINTN    StrLength
  )
{
  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *ConOut;
  EFI_SIMPLE_TEXT_INPUT_PROTOCOL   *ConIn;
  BOOLEAN                          Done;
  UINTN                            Column;
  UINTN                            Row;
  UINTN                            StartColumn;
  UINTN                            Update;
  UINTN                            Delete;
  UINTN                            Len;
  UINTN                            StrPos;
  UINTN                            Index;
  UINTN                            LineLength;
  UINTN                            TotalRow;
  UINTN                            SkipLength;
  UINTN                            OutputLength;
  UINTN                            TailRow;
  UINTN                            TailColumn;
  EFI_INPUT_KEY                    Key;
  BOOLEAN                          InsertMode;
  BOOLEAN                          NeedAdjust;
  UINTN                            SubIndex;
  CHAR16                           *CommandStr;

  ConOut = gST->ConOut;
  ConIn  = gST->ConIn;

  ASSERT (ConOut != NULL);
  ASSERT (ConIn != NULL);
  ASSERT (InStr != NULL);

  if (Prompt != NULL) {
    ConOut->OutputString (ConOut, Prompt);
  }

  //
  // Read a line from the console
  //
  Len          = 0;
  StrPos       = 0;
  OutputLength = 0;
  Update       = 0;
  Delete       = 0;
  InsertMode   = TRUE;
  NeedAdjust   = FALSE;

  //
  // If buffer is not large enough to hold a CHAR16, do nothing.
  //
  if (StrLength < 1) {
    return;
  }

  //
  // Get the screen setting and the current cursor location
  //
  StartColumn = ConOut->Mode->CursorColumn;
  Column      = StartColumn;
  Row         = ConOut->Mode->CursorRow;
  ConOut->QueryMode (ConOut, ConOut->Mode->Mode, &LineLength, &TotalRow);
  if (LineLength == 0) {
    return;
  }

  SetMem (InStr, StrLength * sizeof (CHAR16), 0);
  Done = FALSE;
  do {
    //
    // Read a key
    //
    WaitForSingleEvent (ConIn->WaitForKey, 0);
    ConIn->ReadKeyStroke (ConIn, &Key);

    switch (Key.UnicodeChar) {
      case CHAR_CARRIAGE_RETURN:
        //
        // All done, print a newline at the end of the string
        //
        TailRow    = Row + (Len - StrPos + Column) / LineLength;
        TailColumn = (Len - StrPos + Column) % LineLength;
        Done       = TRUE;
        break;

      case CHAR_BACKSPACE:
        if (StrPos != 0) {
          //
          // If not move back beyond string beginning, move all characters behind
          // the current position one character forward
          //
          StrPos -= 1;
          Update  = StrPos;
          Delete  = 1;
          CopyMem (InStr + StrPos, InStr + StrPos + 1, sizeof (CHAR16) * (Len - StrPos));

          //
          // Adjust the current column and row
          //
          ConMoveCursorBackward (LineLength, &Column, &Row);

          NeedAdjust = TRUE;
        }

        break;

      default:
        if (Key.UnicodeChar >= ' ') {
          //
          // If we are at the buffer's end, drop the key
          //
          if ((Len == StrLength - 1) && (InsertMode || (StrPos == Len))) {
            break;
          }

          //
          // If in insert mode, move all characters behind the current position
          // one character backward to make space for this character. Then store
          // the character.
          //
          if (InsertMode) {
            for (Index = Len; Index > StrPos; Index -= 1) {
              InStr[Index] = InStr[Index - 1];
            }
          }

          InStr[StrPos] = Key.UnicodeChar;
          Update        = StrPos;

          StrPos      += 1;
          OutputLength = 1;
        }

        break;

      case 0:
        switch (Key.ScanCode) {
          case SCAN_DELETE:
            //
            // Move characters behind current position one character forward
            //
            if (Len != 0) {
              Update = StrPos;
              Delete = 1;
              CopyMem (InStr + StrPos, InStr + StrPos + 1, sizeof (CHAR16) * (Len - StrPos));

              NeedAdjust = TRUE;
            }

            break;

          case SCAN_LEFT:
            //
            // Adjust current cursor position
            //
            if (StrPos != 0) {
              StrPos -= 1;
              ConMoveCursorBackward (LineLength, &Column, &Row);
            }

            break;

          case SCAN_RIGHT:
            //
            // Adjust current cursor position
            //
            if (StrPos < Len) {
              StrPos += 1;
              ConMoveCursorForward (LineLength, TotalRow, &Column, &Row);
            }

            break;

          case SCAN_HOME:
            //
            // Move current cursor position to the beginning of the command line
            //
            Row   -= (StrPos + StartColumn) / LineLength;
            Column = StartColumn;
            StrPos = 0;
            break;

          case SCAN_END:
            //
            // Move current cursor position to the end of the command line
            //
            TailRow    = Row + (Len - StrPos + Column) / LineLength;
            TailColumn = (Len - StrPos + Column) % LineLength;
            Row        = TailRow;
            Column     = TailColumn;
            StrPos     = Len;
            break;

          case SCAN_ESC:
            //
            // Prepare to clear the current command line
            //
            InStr[0]     = 0;
            Update       = 0;
            Delete       = Len;
            Row         -= (StrPos + StartColumn) / LineLength;
            Column       = StartColumn;
            OutputLength = 0;

            NeedAdjust = TRUE;
            break;

          case SCAN_INSERT:
            //
            // Toggle the SEnvInsertMode flag
            //
            InsertMode = (BOOLEAN) !InsertMode;
            break;

          case SCAN_UP:
          case SCAN_DOWN:
            //
            // show history
            //
            CopyMem (InStr, mInputBufferHistory, StrLength * sizeof (CHAR16));
            StrPos       = StrLen (mInputBufferHistory);
            Update       = 0;
            Delete       = 0;
            OutputLength = 0;

            TailRow    = Row + (StrPos + StartColumn) / LineLength;
            TailColumn = (StrPos + StartColumn) % LineLength;
            Row        = TailRow;
            Column     = TailColumn;
            NeedAdjust = FALSE;

            ConOut->SetCursorPosition (ConOut, StartColumn, Row);
            for (SubIndex = 0; SubIndex < EFI_DEBUG_INPUS_BUFFER_SIZE - (StartColumn - EFI_DEBUG_PROMPT_COLUMN); SubIndex++) {
              mBackupSpace[SubIndex] = L' ';
            }

            EDBPrint (mBackupSpace);
            SetMem (mBackupSpace, (EFI_DEBUG_INPUS_BUFFER_SIZE - (StartColumn - EFI_DEBUG_PROMPT_COLUMN)) * sizeof (CHAR16), 0);

            ConOut->SetCursorPosition (ConOut, StartColumn, Row);
            Len = StrPos;

            break;

          case SCAN_F1:
          case SCAN_F2:
          case SCAN_F3:
          case SCAN_F4:
          case SCAN_F5:
          case SCAN_F6:
          case SCAN_F7:
          case SCAN_F8:
          case SCAN_F9:
          case SCAN_F10:
          case SCAN_F11:
          case SCAN_F12:
            CommandStr = GetCommandNameByKey (Key);
            if (CommandStr != NULL) {
              StrnCpyS (InStr, StrLength, CommandStr, StrLength - 1);
              return;
            }

            break;
        }
    }

    if (Done) {
      break;
    }

    //
    // If we need to update the output do so now
    //
    if (Update != -1) {
      if (NeedAdjust) {
        ConOut->SetCursorPosition (ConOut, Column, Row);
        for (SubIndex = 0; SubIndex < EFI_DEBUG_INPUS_BUFFER_SIZE - (Column - EFI_DEBUG_PROMPT_COLUMN); SubIndex++) {
          mBackupSpace[SubIndex] = L' ';
        }

        EDBPrint (mBackupSpace);
        SetMem (mBackupSpace, (EFI_DEBUG_INPUS_BUFFER_SIZE - (Column - EFI_DEBUG_PROMPT_COLUMN)) * sizeof (CHAR16), 0);
        ConOut->SetCursorPosition (ConOut, Column, Row);
        NeedAdjust = FALSE;
      }

      EDBPrint (InStr + Update);
      Len = StrLen (InStr);

      if (Delete != 0) {
        SetMem (InStr + Len, Delete * sizeof (CHAR16), 0x00);
      }

      if (StrPos > Len) {
        StrPos = Len;
      }

      Update = (UINTN)-1;

      //
      // After using print to reflect newly updates, if we're not using
      // BACKSPACE and DELETE, we need to move the cursor position forward,
      // so adjust row and column here.
      //
      if ((Key.UnicodeChar != CHAR_BACKSPACE) && !((Key.UnicodeChar == 0) && (Key.ScanCode == SCAN_DELETE))) {
        //
        // Calulate row and column of the tail of current string
        //
        TailRow    = Row + (Len - StrPos + Column + OutputLength) / LineLength;
        TailColumn = (Len - StrPos + Column + OutputLength) % LineLength;

        //
        // If the tail of string reaches screen end, screen rolls up, so if
        // Row does not equal TailRow, Row should be decremented
        //
        // (if we are recalling commands using UPPER and DOWN key, and if the
        // old command is too long to fit the screen, TailColumn must be 79.
        //
        if ((TailColumn == 0) && (TailRow >= TotalRow) && ((UINTN)Row != TailRow)) {
          Row--;
        }

        //
        // Calculate the cursor position after current operation. If cursor
        // reaches line end, update both row and column, otherwise, only
        // column will be changed.
        //
        if (Column + OutputLength >= LineLength) {
          SkipLength = OutputLength - (LineLength - Column);

          Row += SkipLength / LineLength + 1;
          if ((UINTN)Row > TotalRow - 1) {
            Row = TotalRow - 1;
          }

          Column = SkipLength % LineLength;
        } else {
          Column += OutputLength;
        }
      }

      Delete = 0;
    }

    //
    // Set the cursor position for this key
    //
    SetCursorPosition (ConOut, Column, Row, LineLength, TotalRow, InStr, StrPos, Len);
  } while (!Done);

  CopyMem (mInputBufferHistory, InStr, StrLength * sizeof (CHAR16));

  //
  // Return the data to the caller
  //
  return;
}

/**
  Set the current coordinates of the cursor position.

  @param  ConOut        Point to EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.
  @param  Column        The position to set the cursor to.
  @param  Row           The position to set the cursor to.
  @param  LineLength    Length of a line.
  @param  TotalRow      Total row of a screen.
  @param  Str           Point to the string.
  @param  StrPos        The position of the string.
  @param  Len           The length of the string.

**/
VOID
EFIAPI
SetCursorPosition (
  IN  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *ConOut,
  IN  UINTN                            Column,
  IN  INTN                             Row,
  IN  UINTN                            LineLength,
  IN  UINTN                            TotalRow,
  IN  CHAR16                           *Str,
  IN  UINTN                            StrPos,
  IN  UINTN                            Len
  )
{
  CHAR16  Backup;

  ASSERT (ConOut != NULL);
  ASSERT (Str != NULL);

  Backup = 0;
  if (Row >= 0) {
    ConOut->SetCursorPosition (ConOut, Column, Row);
    return;
  }

  if (Len - StrPos > Column * Row) {
    Backup                         = *(Str + StrPos + Column * Row);
    *(Str + StrPos + Column * Row) = 0;
  }

  EDBPrint (L"%s", Str + StrPos);
  if (Len - StrPos > Column * Row) {
    *(Str + StrPos + Column * Row) = Backup;
  }

  ConOut->SetCursorPosition (ConOut, 0, 0);
}

/**

  SetPageBreak.

**/
BOOLEAN
EFIAPI
SetPageBreak (
  VOID
  )
{
  EFI_INPUT_KEY  Key;
  CHAR16         Str[3];
  BOOLEAN        OmitPrint;

  //
  // Check
  //
  if (!mDebuggerPrivate.EnablePageBreak) {
    return FALSE;
  }

  gST->ConOut->OutputString (gST->ConOut, L"Press ENTER to continue, 'q' to exit:");

  OmitPrint = FALSE;
  //
  // Wait for user input
  //
  Str[0] = ' ';
  Str[1] = 0;
  Str[2] = 0;
  for ( ; ;) {
    WaitForSingleEvent (gST->ConIn->WaitForKey, 0);
    gST->ConIn->ReadKeyStroke (gST->ConIn, &Key);

    //
    // handle control keys
    //
    if (Key.UnicodeChar == CHAR_NULL) {
      if (Key.ScanCode == SCAN_ESC) {
        gST->ConOut->OutputString (gST->ConOut, L"\r\n");
        OmitPrint = TRUE;
        break;
      }

      continue;
    }

    if (Key.UnicodeChar == CHAR_CARRIAGE_RETURN) {
      gST->ConOut->OutputString (gST->ConOut, L"\r\n");
      break;
    }

    //
    // Echo input
    //
    Str[1] = Key.UnicodeChar;
    if (Str[1] == CHAR_BACKSPACE) {
      continue;
    }

    gST->ConOut->OutputString (gST->ConOut, Str);

    if ((Str[1] == L'q') || (Str[1] == L'Q')) {
      OmitPrint = TRUE;
    } else {
      OmitPrint = FALSE;
    }

    Str[0] = CHAR_BACKSPACE;
  }

  return OmitPrint;
}

/**
  Print a Unicode string to the output device.

  @param  Format    A Null-terminated Unicode format string.
  @param  ...       The variable argument list that contains pointers to Null-
                    terminated Unicode strings to be printed

**/
UINTN
EFIAPI
EDBPrint (
  IN CONST CHAR16  *Format,
  ...
  )
{
  UINTN    Return;
  VA_LIST  Marker;
  CHAR16   Buffer[EFI_DEBUG_MAX_PRINT_BUFFER];

  VA_START (Marker, Format);
  Return = UnicodeVSPrint (Buffer, sizeof (Buffer), Format, Marker);
  VA_END (Marker);

  if (gST->ConOut != NULL) {
    //
    // To be extra safe make sure ConOut has been initialized
    //
    gST->ConOut->OutputString (gST->ConOut, Buffer);
  }

  return Return;
}

/**
  Print a Unicode string to the output buffer.

  @param  Buffer          A pointer to the output buffer for the produced Null-terminated
                          Unicode string.
  @param  BufferSize      The size, in bytes, of the output buffer specified by StartOfBuffer.
  @param  Format          A Null-terminated Unicode format string.
  @param  ...             The variable argument list that contains pointers to Null-
                          terminated Unicode strings to be printed

**/
UINTN
EFIAPI
EDBSPrint (
  OUT CHAR16        *Buffer,
  IN  INTN          BufferSize,
  IN  CONST CHAR16  *Format,
  ...
  )
{
  UINTN    Return;
  VA_LIST  Marker;

  ASSERT (BufferSize > 0);

  VA_START (Marker, Format);
  Return = UnicodeVSPrint (Buffer, (UINTN)BufferSize, Format, Marker);
  VA_END (Marker);

  return Return;
}

/**
  Print a Unicode string to the output buffer with specified offset..

  @param  Buffer          A pointer to the output buffer for the produced Null-terminated
                          Unicode string.
  @param  BufferSize      The size, in bytes, of the output buffer specified by StartOfBuffer.
  @param  Offset          The offset of the buffer.
  @param  Format          A Null-terminated Unicode format string.
  @param  ...             The variable argument list that contains pointers to Null-
                          terminated Unicode strings to be printed

**/
UINTN
EFIAPI
EDBSPrintWithOffset (
  OUT CHAR16        *Buffer,
  IN  INTN          BufferSize,
  IN  UINTN         Offset,
  IN  CONST CHAR16  *Format,
  ...
  )
{
  UINTN    Return;
  VA_LIST  Marker;

  ASSERT (BufferSize - (Offset * sizeof (CHAR16)) > 0);

  VA_START (Marker, Format);
  Return = UnicodeVSPrint (Buffer + Offset, (UINTN)(BufferSize - (Offset * sizeof (CHAR16))), Format, Marker);
  VA_END (Marker);

  return Return;
}
