/** @file
  Simple Console that sits on a SerialLib.

  Copyright (c) 2008 - 2009, Apple Inc. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

/*
  Symbols used in table below
===========================
  ESC = 0x1B
  CSI = 0x9B
  DEL = 0x7f
  ^   = CTRL

+=========+======+===========+==========+==========+
|         | EFI  | UEFI 2.0  |          |          |
|         | Scan |           |  VT100+  |          |
|   KEY   | Code |  PC ANSI  |  VTUTF8  |   VT100  |
+=========+======+===========+==========+==========+
| NULL    | 0x00 |           |          |          |
| UP      | 0x01 | ESC [ A   | ESC [ A  | ESC [ A  |
| DOWN    | 0x02 | ESC [ B   | ESC [ B  | ESC [ B  |
| RIGHT   | 0x03 | ESC [ C   | ESC [ C  | ESC [ C  |
| LEFT    | 0x04 | ESC [ D   | ESC [ D  | ESC [ D  |
| HOME    | 0x05 | ESC [ H   | ESC h    | ESC [ H  |
| END     | 0x06 | ESC [ F   | ESC k    | ESC [ K  |
| INSERT  | 0x07 | ESC [ @   | ESC +    | ESC [ @  |
|         |      | ESC [ L   |          | ESC [ L  |
| DELETE  | 0x08 | ESC [ X   | ESC -    | ESC [ P  |
| PG UP   | 0x09 | ESC [ I   | ESC ?    | ESC [ V  |
|         |      |           |          | ESC [ ?  |
| PG DOWN | 0x0A | ESC [ G   | ESC /    | ESC [ U  |
|         |      |           |          | ESC [ /  |
| F1      | 0x0B | ESC [ M   | ESC 1    | ESC O P  |
| F2      | 0x0C | ESC [ N   | ESC 2    | ESC O Q  |
| F3      | 0x0D | ESC [ O   | ESC 3    | ESC O w  |
| F4      | 0x0E | ESC [ P   | ESC 4    | ESC O x  |
| F5      | 0x0F | ESC [ Q   | ESC 5    | ESC O t  |
| F6      | 0x10 | ESC [ R   | ESC 6    | ESC O u  |
| F7      | 0x11 | ESC [ S   | ESC 7    | ESC O q  |
| F8      | 0x12 | ESC [ T   | ESC 8    | ESC O r  |
| F9      | 0x13 | ESC [ U   | ESC 9    | ESC O p  |
| F10     | 0x14 | ESC [ V   | ESC 0    | ESC O M  |
| Escape  | 0x17 | ESC       | ESC      | ESC      |
| F11     | 0x15 |           | ESC !    |          |
| F12     | 0x16 |           | ESC @    |          |
+=========+======+===========+==========+==========+

*/

#include <PiDxe.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/BaseLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/DebugLib.h>
#include <Library/SerialPortLib.h>
#include <Library/PcdLib.h>

#include <Protocol/SerialIo.h>
#include <Protocol/SimpleTextIn.h>
#include <Protocol/SimpleTextOut.h>
#include <Protocol/DevicePath.h>


#define MODE0_COLUMN_COUNT        80
#define MODE0_ROW_COUNT           25


EFI_STATUS
EFIAPI
TextInReset(
  IN EFI_SIMPLE_TEXT_INPUT_PROTOCOL *This,
  IN BOOLEAN                        ExtendedVerification
  );


EFI_STATUS
EFIAPI
ReadKeyStroke(
  IN EFI_SIMPLE_TEXT_INPUT_PROTOCOL *This,
  OUT EFI_INPUT_KEY                 *Key
  );


EFI_STATUS
EFIAPI
TextOutReset(
  IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *This,
  IN BOOLEAN                          ExtendedVerification
  );

CHAR8 *
EFIAPI
SafeUnicodeStrToAsciiStr (
  IN      CONST CHAR16                *Source,
  OUT     CHAR8                       *Destination
  );

EFI_STATUS
EFIAPI
OutputString (
  IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *This,
  IN CHAR16                           *String
  );


EFI_STATUS
EFIAPI
TestString (
  IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *This,
  IN CHAR16                           *String
  );


EFI_STATUS
EFIAPI
QueryMode (
  IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *This,
  IN UINTN                            ModeNumber,
  OUT UINTN                           *Columns,
  OUT UINTN                           *Rows
  );


EFI_STATUS
EFIAPI
SetMode(
  IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *This,
  IN UINTN                            ModeNumber
  );


EFI_STATUS
EFIAPI
SetAttribute(
  IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *This,
  IN UINTN                            Attribute
  );


EFI_STATUS
EFIAPI
ClearScreen (
  IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *This
  );


EFI_STATUS
EFIAPI
SetCursorPosition (
  IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *This,
  IN UINTN                            Column,
  IN UINTN                            Row
  );


EFI_STATUS
EFIAPI
EnableCursor (
  IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *This,
  IN BOOLEAN                          Enable
  );


 EFI_SIMPLE_TEXT_INPUT_PROTOCOL mSimpleTextIn = {
  TextInReset,
  ReadKeyStroke,
  NULL
};

 EFI_SIMPLE_TEXT_OUTPUT_MODE mSimpleTextOutMode = {
  1,
  0,
  EFI_TEXT_ATTR( EFI_LIGHTGRAY, EFI_BLACK ),
  0,
  0,
  TRUE
};

EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL mSimpleTextOut = {
  TextOutReset,
  OutputString,
  TestString,
  QueryMode,
  SetMode,
  SetAttribute,
  ClearScreen,
  SetCursorPosition,
  EnableCursor,
  &mSimpleTextOutMode
};

EFI_HANDLE           mInstallHandle = NULL;

typedef struct {
  VENDOR_DEVICE_PATH        Guid;
  UART_DEVICE_PATH          Uart;
  EFI_DEVICE_PATH_PROTOCOL  End;
} SIMPLE_TEXT_OUT_DEVICE_PATH;

SIMPLE_TEXT_OUT_DEVICE_PATH mDevicePath = {
  {
    { HARDWARE_DEVICE_PATH, HW_VENDOR_DP, { sizeof (VENDOR_DEVICE_PATH), 0} },
    EFI_CALLER_ID_GUID
  },
  {
    { MESSAGING_DEVICE_PATH, MSG_UART_DP, { sizeof (UART_DEVICE_PATH), 0} },
    0,        // Reserved
    FixedPcdGet64 (PcdUartDefaultBaudRate),   // BaudRate
    FixedPcdGet8 (PcdUartDefaultDataBits),    // DataBits
    FixedPcdGet8 (PcdUartDefaultParity),      // Parity (N)
    FixedPcdGet8 (PcdUartDefaultStopBits)     // StopBits
  },
  { END_DEVICE_PATH_TYPE, END_ENTIRE_DEVICE_PATH_SUBTYPE, { sizeof (EFI_DEVICE_PATH_PROTOCOL), 0} }
};




BOOLEAN
TextOutIsValidAscii (
  IN CHAR16       Ascii
  )
{
  //
  // valid ASCII code lies in the extent of 0x20 - 0x7F
  //
  if ((Ascii >= 0x20) && (Ascii <= 0x7F)) {
    return TRUE;
  }

  return FALSE;
}


BOOLEAN
TextOutIsValidEfiCntlChar (
  IN CHAR16       Char
  )
{
  //
  // only support four control characters.
  //
  if (Char == CHAR_NULL ||
      Char == CHAR_BACKSPACE ||
      Char == CHAR_LINEFEED ||
      Char == CHAR_CARRIAGE_RETURN ||
      Char == CHAR_TAB ) {
    return TRUE;
  }

  return FALSE;
}


VOID
EFIAPI
WaitForKeyEvent (
  IN EFI_EVENT          Event,
  IN VOID               *Context
  )
{
  if (SerialPortPoll ())  {
    gBS->SignalEvent (Event);
  }
}


EFI_STATUS
EFIAPI
TextInReset (
  IN EFI_SIMPLE_TEXT_INPUT_PROTOCOL *This,
  IN BOOLEAN                        ExtendedVerification
  )
{
  return EFI_SUCCESS;
}


EFI_STATUS
EFIAPI
ReadKeyStroke (
  IN EFI_SIMPLE_TEXT_INPUT_PROTOCOL *This,
  OUT EFI_INPUT_KEY                 *Key
  )
{
  CHAR8             Char;

  if (!SerialPortPoll ()) {
    return EFI_NOT_READY;
  }

  SerialPortRead ((UINT8 *)&Char, 1);

  //
  // Check for ESC sequence. This code is not technically correct VT100 code.
  // An illegal ESC sequence represents an ESC and the characters that follow.
  // This code will eat one or two chars after an escape. This is done to
  // prevent some complex FIFOing of the data. It is good enough to get
  // the arrow and delete keys working
  //
  Key->UnicodeChar = 0;
  Key->ScanCode    = SCAN_NULL;
  if (Char == 0x1b) {
    SerialPortRead ((UINT8 *)&Char, 1);
    if (Char == '[') {
      SerialPortRead ((UINT8 *)&Char, 1);
      switch (Char) {
      case 'A':
        Key->ScanCode = SCAN_UP;
        break;
      case 'B':
        Key->ScanCode = SCAN_DOWN;
        break;
      case 'C':
        Key->ScanCode = SCAN_RIGHT;
        break;
      case 'D':
        Key->ScanCode = SCAN_LEFT;
        break;
      case 'H':
        Key->ScanCode = SCAN_HOME;
        break;
      case 'K':
      case 'F': // PC ANSI
        Key->ScanCode = SCAN_END;
        break;
      case '@':
      case 'L':
        Key->ScanCode = SCAN_INSERT;
        break;
      case 'P':
      case 'X': // PC ANSI
        Key->ScanCode = SCAN_DELETE;
        break;
      case 'U':
      case '/':
      case 'G': // PC ANSI
        Key->ScanCode = SCAN_PAGE_DOWN;
        break;
      case 'V':
      case '?':
      case 'I': // PC ANSI
        Key->ScanCode = SCAN_PAGE_UP;
        break;

      // PCANSI that does not conflict with VT100
      case 'M':
        Key->ScanCode = SCAN_F1;
        break;
      case 'N':
        Key->ScanCode = SCAN_F2;
        break;
      case 'O':
        Key->ScanCode = SCAN_F3;
        break;
      case 'Q':
        Key->ScanCode = SCAN_F5;
        break;
      case 'R':
        Key->ScanCode = SCAN_F6;
        break;
      case 'S':
        Key->ScanCode = SCAN_F7;
        break;
      case 'T':
        Key->ScanCode = SCAN_F8;
        break;

      default:
        Key->UnicodeChar = Char;
        break;
      }
    } else if (Char == '0') {
      SerialPortRead ((UINT8 *)&Char, 1);
      switch (Char) {
      case 'P':
        Key->ScanCode = SCAN_F1;
        break;
      case 'Q':
        Key->ScanCode = SCAN_F2;
        break;
      case 'w':
        Key->ScanCode = SCAN_F3;
        break;
      case 'x':
        Key->ScanCode = SCAN_F4;
        break;
      case 't':
        Key->ScanCode = SCAN_F5;
        break;
      case 'u':
        Key->ScanCode = SCAN_F6;
        break;
      case 'q':
        Key->ScanCode = SCAN_F7;
        break;
      case 'r':
        Key->ScanCode = SCAN_F8;
        break;
      case 'p':
        Key->ScanCode = SCAN_F9;
        break;
      case 'm':
        Key->ScanCode = SCAN_F10;
        break;
      default :
        break;
      }
    }
  } else if (Char < ' ') {
    if ((Char == CHAR_BACKSPACE) ||
        (Char == CHAR_TAB)       ||
        (Char == CHAR_LINEFEED)  ||
        (Char == CHAR_CARRIAGE_RETURN)) {
      // Only let through EFI required control characters
      Key->UnicodeChar = (CHAR16)Char;
    }
  } else if (Char == 0x7f) {
    Key->ScanCode = SCAN_DELETE;
  } else {
    Key->UnicodeChar = (CHAR16)Char;
  }

  return EFI_SUCCESS;
}


EFI_STATUS
EFIAPI
TextOutReset (
  IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *This,
  IN BOOLEAN                          ExtendedVerification
  )
{
  EFI_STATUS            Status;

  This->SetAttribute(
        This,
        EFI_TEXT_ATTR(This->Mode->Attribute & 0x0F, EFI_BACKGROUND_BLACK)
        );

  Status = This->SetMode (This, 0);

  return Status;
}

CHAR8 *
EFIAPI
SafeUnicodeStrToAsciiStr (
  IN      CONST CHAR16                *Source,
  OUT     CHAR8                       *Destination
  )
{
  CHAR8                               *ReturnValue;

  ASSERT (Destination != NULL);

  //
  // ASSERT if Source is long than PcdMaximumUnicodeStringLength.
  // Length tests are performed inside StrLen().
  //
  ASSERT (StrSize (Source) != 0);

  //
  // Source and Destination should not overlap
  //
  ASSERT ((UINTN) ((CHAR16 *) Destination -  Source) > StrLen (Source));
  ASSERT ((UINTN) ((CHAR8 *) Source - Destination) > StrLen (Source));


  ReturnValue = Destination;
  while (*Source != '\0') {
    //
    // If any non-ascii characters in Source then replace it with '?'.
    //
    if (*Source < 0x80) {
      *Destination = (CHAR8) *Source;
    } else {
      *Destination = '?';

      //Surrogate pair check.
      if ((*Source >= 0xD800) && (*Source <= 0xDFFF)) {
        Source++;
      }
    }

    Destination++;
    Source++;
  }

  *Destination = '\0';

  //
  // ASSERT Original Destination is less long than PcdMaximumAsciiStringLength.
  // Length tests are performed inside AsciiStrLen().
  //
  ASSERT (AsciiStrSize (ReturnValue) != 0);

  return ReturnValue;
}

EFI_STATUS
EFIAPI
OutputString (
  IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *This,
  IN CHAR16                           *String
  )
{
  UINTN                       Size;
  CHAR8*                      OutputString;
  EFI_STATUS                  Status;
  EFI_SIMPLE_TEXT_OUTPUT_MODE *Mode;
  UINTN                       MaxColumn;
  UINTN                       MaxRow;

  Size = StrLen(String) + 1;
  OutputString = AllocatePool(Size);

  //If there is any non-ascii characters in String buffer then replace it with '?'
  //Eventually, UnicodeStrToAsciiStr API should be fixed.
  SafeUnicodeStrToAsciiStr(String, OutputString);
  SerialPortWrite ((UINT8 *)OutputString, Size - 1);

  //
  // Parse each character of the string to output
  // to update the cursor position information
  //
  Mode = This->Mode;

  Status = This->QueryMode (
                   This,
                   Mode->Mode,
                   &MaxColumn,
                   &MaxRow
                   );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  for (; *String != CHAR_NULL; String++) {

    switch (*String) {
    case CHAR_BACKSPACE:
      if (Mode->CursorColumn > 0) {
        Mode->CursorColumn--;
      }
      break;

    case CHAR_LINEFEED:
      if (Mode->CursorRow < (INT32) (MaxRow - 1)) {
        Mode->CursorRow++;
      }
      break;

    case CHAR_CARRIAGE_RETURN:
      Mode->CursorColumn = 0;
      break;

    default:
      if (Mode->CursorColumn >= (INT32) (MaxColumn - 1)) {
        // Move the cursor as if we print CHAR_CARRIAGE_RETURN & CHAR_LINE_FEED
        // CHAR_LINEFEED
        if (Mode->CursorRow < (INT32) (MaxRow - 1)) {
          Mode->CursorRow++;
        }
        // CHAR_CARIAGE_RETURN
        Mode->CursorColumn = 0;
      } else {
        Mode->CursorColumn++;
      }
      break;
    }
  }

  FreePool(OutputString);

  return EFI_SUCCESS;
}


EFI_STATUS
EFIAPI
TestString (
  IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *This,
  IN CHAR16                           *String
  )
{
  CHAR8           Character;

  for ( ; *String != CHAR_NULL; String++) {
    Character = (CHAR8)*String;
    if (!(TextOutIsValidAscii (Character) || TextOutIsValidEfiCntlChar (Character))) {
      return EFI_UNSUPPORTED;
    }
  }

  return EFI_SUCCESS;
}


EFI_STATUS
EFIAPI
QueryMode (
  IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *This,
  IN UINTN                            ModeNumber,
  OUT UINTN                          *Columns,
  OUT UINTN                          *Rows
  )
{
  if (This->Mode->MaxMode > 1) {
    return EFI_DEVICE_ERROR;
  }

  if (ModeNumber == 0) {
    *Columns  = MODE0_COLUMN_COUNT;
    *Rows     = MODE0_ROW_COUNT;
    return EFI_SUCCESS;
  }

  return EFI_UNSUPPORTED;
}


EFI_STATUS
EFIAPI
SetMode (
  IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL    *This,
  IN UINTN                              ModeNumber
  )
{
  if (ModeNumber != 0) {
    return EFI_UNSUPPORTED;
  }

  This->Mode->Mode = 0;
  This->ClearScreen (This);
  return EFI_SUCCESS;
}


EFI_STATUS
EFIAPI
SetAttribute(
  IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL    *This,
  IN UINTN                              Attribute
  )
{
  This->Mode->Attribute = (INT32)Attribute;
  return EFI_SUCCESS;
}


EFI_STATUS
EFIAPI
ClearScreen (
  IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL    *This
  )
{
  EFI_STATUS    Status;

  Status = This->SetCursorPosition (This, 0, 0);
  return Status;
}


EFI_STATUS
EFIAPI
SetCursorPosition (
  IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL    *This,
  IN UINTN                              Column,
  IN UINTN                              Row
  )
{
  EFI_SIMPLE_TEXT_OUTPUT_MODE       *Mode;
  EFI_STATUS                        Status;
  UINTN                             MaxColumn;
  UINTN                             MaxRow;

  Mode = This->Mode;

  Status = This->QueryMode(
                  This,
                  Mode->Mode,
                  &MaxColumn,
                  &MaxRow
                  );
  if (EFI_ERROR(Status)) {
    return EFI_UNSUPPORTED;
  }

  if ((Column >= MaxColumn) || (Row >= MaxRow)) {
    return EFI_UNSUPPORTED;
  }

  Mode->CursorColumn = (INT32)Column;
  Mode->CursorRow = (INT32)Row;

  return EFI_SUCCESS;
}


EFI_STATUS
EFIAPI
EnableCursor (
  IN EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *This,
  IN BOOLEAN                          Enable
  )
{
  if (!Enable) {
    return EFI_UNSUPPORTED;
  }

  return EFI_SUCCESS;
}


EFI_STATUS
EFIAPI
SimpleTextInOutEntryPoint (
  IN EFI_HANDLE         ImageHandle,
  IN EFI_SYSTEM_TABLE   *SystemTable
  )
{
  EFI_STATUS            Status;

  Status = gBS->CreateEvent (
                  EVT_NOTIFY_WAIT,
                  TPL_NOTIFY,
                  WaitForKeyEvent,
                  NULL,
                  &mSimpleTextIn.WaitForKey
                  );
  ASSERT_EFI_ERROR (Status);

  Status = gBS->InstallMultipleProtocolInterfaces(
                  &mInstallHandle,
                  &gEfiSimpleTextInProtocolGuid,   &mSimpleTextIn,
                  &gEfiSimpleTextOutProtocolGuid,  &mSimpleTextOut,
                  &gEfiDevicePathProtocolGuid,     &mDevicePath,
                  NULL
                  );
  if (!EFI_ERROR (Status)) {
    gST->ConOut = &mSimpleTextOut;
    gST->ConIn = &mSimpleTextIn;
  }

  return Status;
}
