/** @file

Copyright (c) 2007, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.


**/

#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;
}
