/** @file
  Defines HBufferImage - the view of the file that is visible at any point,
  as well as the event handlers for editing the file

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

**/

#include "HexEditor.h"

extern EFI_HANDLE  HImageHandleBackup;

extern HEFI_EDITOR_FILE_IMAGE  HFileImage;
extern HEFI_EDITOR_DISK_IMAGE  HDiskImage;
extern HEFI_EDITOR_MEM_IMAGE   HMemImage;

extern HEFI_EDITOR_FILE_IMAGE  HFileImageBackupVar;
extern HEFI_EDITOR_DISK_IMAGE  HDiskImageBackupVar;
extern HEFI_EDITOR_MEM_IMAGE   HMemImageBackupVar;

extern BOOLEAN  HEditorMouseAction;

extern HEFI_EDITOR_GLOBAL_EDITOR  HMainEditor;
extern HEFI_EDITOR_GLOBAL_EDITOR  HMainEditorBackupVar;

HEFI_EDITOR_BUFFER_IMAGE  HBufferImage;
HEFI_EDITOR_BUFFER_IMAGE  HBufferImageBackupVar;

//
// for basic initialization of HBufferImage
//
HEFI_EDITOR_BUFFER_IMAGE  HBufferImageConst = {
  NULL,
  NULL,
  0,
  NULL,
  {
    0,
    0
  },
  {
    0,
    0
  },
  {
    0,
    0
  },
  0,
  TRUE,
  FALSE,
  FileTypeNone,
  NULL,
  NULL,
  NULL
};

//
// the whole edit area needs to be refreshed
//
BOOLEAN  HBufferImageNeedRefresh;

//
// only the current line in edit area needs to be refresh
//
BOOLEAN  HBufferImageOnlyLineNeedRefresh;

BOOLEAN  HBufferImageMouseNeedRefresh;

/**
  Initialization function for HBufferImage

  @retval EFI_SUCCESS       The operation was successful.
  @retval EFI_LOAD_ERROR    A load error occurred.
**/
EFI_STATUS
HBufferImageInit (
  VOID
  )
{
  EFI_STATUS  Status;

  //
  // basically initialize the HBufferImage
  //
  CopyMem (&HBufferImage, &HBufferImageConst, sizeof (HBufferImage));

  //
  // INIT listhead
  //
  HBufferImage.ListHead = AllocateZeroPool (sizeof (LIST_ENTRY));
  if (HBufferImage.ListHead == NULL) {
    return EFI_LOAD_ERROR;
  }

  InitializeListHead (HBufferImage.ListHead);

  HBufferImage.DisplayPosition.Row    = 2;
  HBufferImage.DisplayPosition.Column = 10;
  HBufferImage.MousePosition.Row      = 2;
  HBufferImage.MousePosition.Column   = 10;

  HBufferImage.FileImage = &HFileImage;
  HBufferImage.DiskImage = &HDiskImage;
  HBufferImage.MemImage  = &HMemImage;

  HBufferImageNeedRefresh         = FALSE;
  HBufferImageOnlyLineNeedRefresh = FALSE;
  HBufferImageMouseNeedRefresh    = FALSE;

  HBufferImageBackupVar.FileImage = &HFileImageBackupVar;
  HBufferImageBackupVar.DiskImage = &HDiskImageBackupVar;
  HBufferImageBackupVar.MemImage  = &HMemImageBackupVar;

  Status = HFileImageInit ();
  if (EFI_ERROR (Status)) {
    return EFI_LOAD_ERROR;
  }

  Status = HDiskImageInit ();
  if (EFI_ERROR (Status)) {
    return EFI_LOAD_ERROR;
  }

  Status = HMemImageInit ();
  if (EFI_ERROR (Status)) {
    return EFI_LOAD_ERROR;
  }

  return EFI_SUCCESS;
}

/**
  Backup function for HBufferImage. Only a few fields need to be backup.
  This is for making the file buffer refresh as few as possible.

  @retval EFI_SUCCESS  The operation was successful.
**/
EFI_STATUS
HBufferImageBackup (
  VOID
  )
{
  CopyMem (&HBufferImageBackupVar.MousePosition, &HBufferImage.MousePosition, sizeof (HBufferImage.MousePosition));
  CopyMem (&HBufferImageBackupVar.BufferPosition, &HBufferImage.BufferPosition, sizeof (HBufferImage.BufferPosition));

  HBufferImageBackupVar.Modified = HBufferImage.Modified;

  HBufferImageBackupVar.BufferType    = HBufferImage.BufferType;
  HBufferImageBackupVar.LowVisibleRow = HBufferImage.LowVisibleRow;
  HBufferImageBackupVar.HighBits      = HBufferImage.HighBits;

  //
  // three kinds of buffer supported
  //   file buffer
  //   disk buffer
  //   memory buffer
  //
  switch (HBufferImage.BufferType) {
    case FileTypeFileBuffer:
      HFileImageBackup ();
      break;

    case FileTypeDiskBuffer:
      HDiskImageBackup ();
      break;

    case FileTypeMemBuffer:
      HMemImageBackup ();
      break;

    default:
      break;
  }

  return EFI_SUCCESS;
}

/**
  Free all the lines in HBufferImage.
    Fields affected:
    Lines
    CurrentLine
    NumLines
    ListHead

  @retval EFI_SUCCESS  The operation was successful.
**/
EFI_STATUS
HBufferImageFreeLines (
  VOID
  )
{
  HFreeLines (HBufferImage.ListHead, HBufferImage.Lines);

  HBufferImage.Lines       = NULL;
  HBufferImage.CurrentLine = NULL;
  HBufferImage.NumLines    = 0;

  return EFI_SUCCESS;
}

/**
  Cleanup function for HBufferImage

  @retval EFI_SUCCESS  The operation was successful.
**/
EFI_STATUS
HBufferImageCleanup (
  VOID
  )
{
  EFI_STATUS  Status;

  //
  // free all the lines
  //
  Status = HBufferImageFreeLines ();

  SHELL_FREE_NON_NULL (HBufferImage.ListHead);
  HBufferImage.ListHead = NULL;

  HFileImageCleanup ();
  HDiskImageCleanup ();

  return Status;
}

/**
  Print Line on Row

  @param[in] Line     The lline to print.
  @param[in] Row      The row on screen ( begin from 1 ).
  @param[in] FRow     The FRow.
  @param[in] Orig     The original color.
  @param[in] New      The color to print with.

  @retval EFI_SUCCESS The operation was successful.
**/
EFI_STATUS
HBufferImagePrintLine (
  IN HEFI_EDITOR_LINE         *Line,
  IN UINTN                    Row,
  IN UINTN                    FRow,
  IN HEFI_EDITOR_COLOR_UNION  Orig,
  IN HEFI_EDITOR_COLOR_UNION  New

  )
{
  UINTN    Index;
  UINTN    Pos;
  BOOLEAN  Selected;
  BOOLEAN  BeNewColor;
  UINTN    RowStart;
  UINTN    RowEnd;
  UINTN    ColStart;
  UINTN    ColEnd;

  //
  // variable initialization
  //
  ColStart = 0;
  ColEnd   = 0;
  Selected = FALSE;

  //
  // print the selected area in opposite color
  //
  if ((HMainEditor.SelectStart != 0) && (HMainEditor.SelectEnd != 0)) {
    RowStart = (HMainEditor.SelectStart - 1) / 0x10 + 1;
    RowEnd   = (HMainEditor.SelectEnd - 1) / 0x10 + 1;

    ColStart = (HMainEditor.SelectStart - 1) % 0x10 + 1;
    ColEnd   = (HMainEditor.SelectEnd - 1) % 0x10 + 1;

    if ((FRow >= RowStart) && (FRow <= RowEnd)) {
      Selected = TRUE;
    }

    if (FRow > RowStart) {
      ColStart = 1;
    }

    if (FRow < RowEnd) {
      ColEnd = 0x10;
    }
  }

  if (!HEditorMouseAction) {
    ShellPrintEx (
      0,
      (INT32)Row - 1,
      L"%8X ",
      ((INT32)Row - 2 + HBufferImage.LowVisibleRow - 1) * 0x10
      );
  }

  for (Index = 0; Index < 0x08 && Index < Line->Size; Index++) {
    BeNewColor = FALSE;

    if (Selected) {
      if ((Index + 1 >= ColStart) && (Index + 1 <= ColEnd)) {
        BeNewColor = TRUE;
      }
    }

    if (BeNewColor) {
      gST->ConOut->SetAttribute (gST->ConOut, New.Data & 0x7F);
    } else {
      gST->ConOut->SetAttribute (gST->ConOut, Orig.Data & 0x7F);
    }

    Pos = 10 + (Index * 3);
    if (Line->Buffer[Index] < 0x10) {
      ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L"0");
      Pos++;
    }

    if (Index < 0x07) {
      ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L"%x ", Line->Buffer[Index]);
    } else {
      ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L"%x  ", Line->Buffer[Index]);
    }
  }

  gST->ConOut->SetAttribute (gST->ConOut, Orig.Data & 0x7F);
  while (Index < 0x08) {
    Pos = 10 + (Index * 3);
    ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L"    ");
    Index++;
  }

  while (Index < 0x10 && Index < Line->Size) {
    BeNewColor = FALSE;

    if (Selected) {
      if ((Index + 1 >= ColStart) && (Index + 1 <= ColEnd)) {
        BeNewColor = TRUE;
      }
    }

    if (BeNewColor) {
      gST->ConOut->SetAttribute (gST->ConOut, New.Data & 0x7F);
    } else {
      gST->ConOut->SetAttribute (gST->ConOut, Orig.Data & 0x7F);
    }

    Pos = 10 + (Index * 3) + 1;
    if (Line->Buffer[Index] < 0x10) {
      ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L"0");
      Pos++;
    }

    ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L"%x ", Line->Buffer[Index]);
    Index++;
  }

  gST->ConOut->SetAttribute (gST->ConOut, Orig.Data & 0x7F);
  while (Index < 0x10) {
    Pos = 10 + (Index * 3) + 1;
    ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L"   ");
    Index++;
  }

  //
  // restore the original color
  //
  gST->ConOut->SetAttribute (gST->ConOut, Orig.Data & 0x7F);

  //
  // PRINT the buffer content
  //
  if (!HEditorMouseAction) {
    for (Index = 0; Index < 0x10 && Index < Line->Size; Index++) {
      Pos = ASCII_POSITION + Index;

      //
      // learned from shelle.h -- IsValidChar
      //
      if (Line->Buffer[Index] >= L' ') {
        ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L"%c", (CHAR16)Line->Buffer[Index]);
      } else {
        ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L"%c", '.');
      }
    }

    while (Index < 0x10) {
      Pos = ASCII_POSITION + Index;
      ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L" ");
      Index++;
    }
  }

  //
  // restore the abundant blank in hex edit area to original color
  //
  if (Selected) {
    if (ColEnd <= 7) {
      Pos = 10 + (ColEnd - 1) * 3 + 2;
      ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L" ");
    } else if (ColEnd == 8) {
      Pos = 10 + (ColEnd - 1) * 3 + 2;
      ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L"  ");
    } else {
      Pos = 10 + (ColEnd - 1) * 3 + 3;
      ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L" ");
    }
  }

  return EFI_SUCCESS;
}

/**
  Function to decide if a column number is stored in the high bits.

  @param[in] Column     The column to examine.
  @param[out] FCol      The actual column number.

  @retval TRUE      The actual column was in high bits and is now in FCol.
  @retval FALSE     There was not a column number in the high bits.
**/
BOOLEAN
HBufferImageIsAtHighBits (
  IN  UINTN  Column,
  OUT UINTN  *FCol
  )
{
  Column -= 10;

  //
  // NOW AFTER THE SUB, Column start from 0
  // 23 AND 24 ARE BOTH BLANK
  //
  if (Column == 24) {
    *FCol = 0;
    return FALSE;
  }

  if (Column > 24) {
    Column--;
  }

  *FCol = (Column / 3) + 1;

  if (Column % 3 == 0) {
    return TRUE;
  }

  if ((Column % 3 == 2)) {
    *FCol = 0;
  }

  return FALSE;
}

/**
  Decide if a point is in the already selected area.

  @param[in] MouseRow     The row of the point to test.
  @param[in] MouseCol     The col of the point to test.

  @retval TRUE      The point is in the selected area.
  @retval FALSE     The point is not in the selected area.
**/
BOOLEAN
HBufferImageIsInSelectedArea (
  IN UINTN  MouseRow,
  IN UINTN  MouseCol
  )
{
  UINTN  FRow;
  UINTN  RowStart;
  UINTN  RowEnd;
  UINTN  ColStart;
  UINTN  ColEnd;
  UINTN  MouseColStart;
  UINTN  MouseColEnd;

  //
  // judge mouse position whether is in selected area
  //
  //
  // not select
  //
  if ((HMainEditor.SelectStart == 0) || (HMainEditor.SelectEnd == 0)) {
    return FALSE;
  }

  //
  // calculate the select area
  //
  RowStart = (HMainEditor.SelectStart - 1) / 0x10 + 1;
  RowEnd   = (HMainEditor.SelectEnd - 1) / 0x10 + 1;

  ColStart = (HMainEditor.SelectStart - 1) % 0x10 + 1;
  ColEnd   = (HMainEditor.SelectEnd - 1) % 0x10 + 1;

  FRow = HBufferImage.LowVisibleRow + MouseRow - 2;
  if ((FRow < RowStart) || (FRow > RowEnd)) {
    return FALSE;
  }

  if (FRow > RowStart) {
    ColStart = 1;
  }

  if (FRow < RowEnd) {
    ColEnd = 0x10;
  }

  MouseColStart = 10 + (ColStart - 1) * 3;
  if (ColStart > 8) {
    MouseColStart++;
  }

  MouseColEnd = 10 + (ColEnd - 1) * 3 + 1;
  if (ColEnd > 8) {
    MouseColEnd++;
  }

  if ((MouseCol < MouseColStart) || (MouseCol > MouseColEnd)) {
    return FALSE;
  }

  return TRUE;
}

/**
  Set mouse position according to HBufferImage.MousePosition.

  @retval EFI_SUCCESS  The operation was successful.
**/
EFI_STATUS
HBufferImageRestoreMousePosition (
  VOID
  )
{
  HEFI_EDITOR_COLOR_UNION  Orig;
  HEFI_EDITOR_COLOR_UNION  New;
  UINTN                    FRow;
  UINTN                    FColumn;
  BOOLEAN                  HasCharacter;
  HEFI_EDITOR_LINE         *CurrentLine;
  HEFI_EDITOR_LINE         *Line;
  UINT8                    Value;
  BOOLEAN                  HighBits;

  Line = NULL;
  if (HMainEditor.MouseSupported) {
    if (HBufferImageMouseNeedRefresh) {
      HBufferImageMouseNeedRefresh = FALSE;

      //
      // if mouse position not moved and only mouse action
      // so do not need to refresh mouse position
      //
      if ((
           (HBufferImage.MousePosition.Row == HBufferImageBackupVar.MousePosition.Row) &&
           (HBufferImage.MousePosition.Column == HBufferImageBackupVar.MousePosition.Column)
           ) &&
          HEditorMouseAction
          )
      {
        return EFI_SUCCESS;
      }

      //
      // backup the old screen attributes
      //
      CopyMem (&Orig, &HMainEditor.ColorAttributes, sizeof (Orig));
      New.Data              = 0;
      New.Colors.Foreground = Orig.Colors.Background & 0xF;
      New.Colors.Background = Orig.Colors.Foreground & 0x7;

      //
      // if in selected area,
      // so do not need to refresh mouse
      //
      if (!HBufferImageIsInSelectedArea (
             HBufferImageBackupVar.MousePosition.Row,
             HBufferImageBackupVar.MousePosition.Column
             ))
      {
        gST->ConOut->SetAttribute (gST->ConOut, Orig.Data);
      } else {
        gST->ConOut->SetAttribute (gST->ConOut, New.Data & 0x7F);
      }

      //
      // clear the old mouse position
      //
      FRow = HBufferImage.LowVisibleRow + HBufferImageBackupVar.MousePosition.Row - 2;

      HighBits = HBufferImageIsAtHighBits (
                   HBufferImageBackupVar.MousePosition.Column,
                   &FColumn
                   );

      HasCharacter = TRUE;
      if ((FRow > HBufferImage.NumLines) || (FColumn == 0)) {
        HasCharacter = FALSE;
      } else {
        CurrentLine = HBufferImage.CurrentLine;
        Line        = HMoveLine (FRow - HBufferImage.BufferPosition.Row);

        if ((Line == NULL) || (FColumn > Line->Size)) {
          HasCharacter = FALSE;
        }

        HBufferImage.CurrentLine = CurrentLine;
      }

      ShellPrintEx (
        (INT32)HBufferImageBackupVar.MousePosition.Column - 1,
        (INT32)HBufferImageBackupVar.MousePosition.Row - 1,
        L" "
        );

      if (HasCharacter) {
        if (HighBits) {
          Value = (UINT8)(Line->Buffer[FColumn - 1] & 0xf0);
          Value = (UINT8)(Value >> 4);
        } else {
          Value = (UINT8)(Line->Buffer[FColumn - 1] & 0xf);
        }

        ShellPrintEx (
          (INT32)HBufferImageBackupVar.MousePosition.Column - 1,
          (INT32)HBufferImageBackupVar.MousePosition.Row - 1,
          L"%x",
          Value
          );
      }

      if (!HBufferImageIsInSelectedArea (
             HBufferImage.MousePosition.Row,
             HBufferImage.MousePosition.Column
             ))
      {
        gST->ConOut->SetAttribute (gST->ConOut, New.Data & 0x7F);
      } else {
        gST->ConOut->SetAttribute (gST->ConOut, Orig.Data);
      }

      //
      // clear the old mouse position
      //
      FRow = HBufferImage.LowVisibleRow + HBufferImage.MousePosition.Row - 2;

      HighBits = HBufferImageIsAtHighBits (
                   HBufferImage.MousePosition.Column,
                   &FColumn
                   );

      HasCharacter = TRUE;
      if ((FRow > HBufferImage.NumLines) || (FColumn == 0)) {
        HasCharacter = FALSE;
      } else {
        CurrentLine = HBufferImage.CurrentLine;
        Line        = HMoveLine (FRow - HBufferImage.BufferPosition.Row);

        if ((Line == NULL) || (FColumn > Line->Size)) {
          HasCharacter = FALSE;
        }

        HBufferImage.CurrentLine = CurrentLine;
      }

      ShellPrintEx (
        (INT32)HBufferImage.MousePosition.Column - 1,
        (INT32)HBufferImage.MousePosition.Row - 1,
        L" "
        );

      if (HasCharacter) {
        if (HighBits) {
          Value = (UINT8)(Line->Buffer[FColumn - 1] & 0xf0);
          Value = (UINT8)(Value >> 4);
        } else {
          Value = (UINT8)(Line->Buffer[FColumn - 1] & 0xf);
        }

        ShellPrintEx (
          (INT32)HBufferImage.MousePosition.Column - 1,
          (INT32)HBufferImage.MousePosition.Row - 1,
          L"%x",
          Value
          );
      }

      //
      // end of HasCharacter
      //
      gST->ConOut->SetAttribute (gST->ConOut, Orig.Data);
    }

    //
    // end of MouseNeedRefresh
    //
  }

  //
  // end of MouseSupported
  //
  return EFI_SUCCESS;
}

/**
  Set cursor position according to HBufferImage.DisplayPosition.

  @retval EFI_SUCCESS  The operation was successful.
**/
EFI_STATUS
HBufferImageRestorePosition (
  VOID
  )
{
  //
  // set cursor position
  //
  gST->ConOut->SetCursorPosition (
                 gST->ConOut,
                 HBufferImage.DisplayPosition.Column - 1,
                 HBufferImage.DisplayPosition.Row - 1
                 );

  return EFI_SUCCESS;
}

/**
  Refresh function for HBufferImage.

  @retval EFI_SUCCESS     The operation was successful.
  @retval EFI_LOAD_ERROR  A Load error occurred.

**/
EFI_STATUS
HBufferImageRefresh (
  VOID
  )
{
  LIST_ENTRY               *Link;
  HEFI_EDITOR_LINE         *Line;
  UINTN                    Row;
  HEFI_EDITOR_COLOR_UNION  Orig;
  HEFI_EDITOR_COLOR_UNION  New;

  UINTN  StartRow;
  UINTN  EndRow;
  UINTN  FStartRow;
  UINTN  Tmp;

  Orig                  = HMainEditor.ColorAttributes;
  New.Data              = 0;
  New.Colors.Foreground = Orig.Colors.Background;
  New.Colors.Background = Orig.Colors.Foreground;

  //
  // if it's the first time after editor launch, so should refresh
  //
  if (HEditorFirst == FALSE) {
    //
    // no definite required refresh
    // and file position displayed on screen has not been changed
    //
    if (!HBufferImageNeedRefresh &&
        !HBufferImageOnlyLineNeedRefresh &&
        (HBufferImageBackupVar.LowVisibleRow == HBufferImage.LowVisibleRow)
        )
    {
      HBufferImageRestoreMousePosition ();
      HBufferImageRestorePosition ();
      return EFI_SUCCESS;
    }
  }

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

  //
  // only need to refresh current line
  //
  if (HBufferImageOnlyLineNeedRefresh && (HBufferImageBackupVar.LowVisibleRow == HBufferImage.LowVisibleRow)) {
    HBufferImagePrintLine (
      HBufferImage.CurrentLine,
      HBufferImage.DisplayPosition.Row,
      HBufferImage.BufferPosition.Row,
      Orig,
      New
      );
  } else {
    //
    // the whole edit area need refresh
    //
    if (HEditorMouseAction && (HMainEditor.SelectStart != 0) && (HMainEditor.SelectEnd != 0)) {
      if (HMainEditor.SelectStart != HMainEditorBackupVar.SelectStart) {
        if ((HMainEditor.SelectStart >= HMainEditorBackupVar.SelectStart) && (HMainEditorBackupVar.SelectStart != 0)) {
          StartRow = (HMainEditorBackupVar.SelectStart - 1) / 0x10 + 1;
        } else {
          StartRow = (HMainEditor.SelectStart - 1) / 0x10 + 1;
        }
      } else {
        StartRow = (HMainEditor.SelectStart - 1) / 0x10 + 1;
      }

      if (HMainEditor.SelectEnd <= HMainEditorBackupVar.SelectEnd) {
        EndRow = (HMainEditorBackupVar.SelectEnd - 1) / 0x10 + 1;
      } else {
        EndRow = (HMainEditor.SelectEnd - 1) / 0x10 + 1;
      }

      //
      // swap
      //
      if (StartRow > EndRow) {
        Tmp      = StartRow;
        StartRow = EndRow;
        EndRow   = Tmp;
      }

      FStartRow = StartRow;

      StartRow = 2 + StartRow - HBufferImage.LowVisibleRow;
      EndRow   = 2 + EndRow - HBufferImage.LowVisibleRow;
    } else {
      //
      // not mouse selection actions
      //
      FStartRow = HBufferImage.LowVisibleRow;
      StartRow  = 2;
      EndRow    = (HMainEditor.ScreenSize.Row - 1);
    }

    //
    // no line
    //
    if (HBufferImage.Lines == NULL) {
      HBufferImageRestoreMousePosition ();
      HBufferImageRestorePosition ();
      gST->ConOut->EnableCursor (gST->ConOut, TRUE);
      return EFI_SUCCESS;
    }

    //
    // get the first line that will be displayed
    //
    Line = HMoveLine (FStartRow - HBufferImage.BufferPosition.Row);
    if (Line == NULL) {
      gST->ConOut->EnableCursor (gST->ConOut, TRUE);
      return EFI_LOAD_ERROR;
    }

    Link = &(Line->Link);
    Row  = StartRow;
    do {
      Line = CR (Link, HEFI_EDITOR_LINE, Link, EFI_EDITOR_LINE_LIST);

      //
      // print line at row
      //
      HBufferImagePrintLine (
        Line,
        Row,
        HBufferImage.LowVisibleRow + Row - 2,
        Orig,
        New
        );

      Link = Link->ForwardLink;
      Row++;
    } while (Link != HBufferImage.ListHead && Row <= EndRow);

    while (Row <= EndRow) {
      EditorClearLine (Row, HMainEditor.ScreenSize.Column, HMainEditor.ScreenSize.Row);
      Row++;
    }

    //
    // while not file end and not screen full
    //
  }

  HBufferImageRestoreMousePosition ();
  HBufferImageRestorePosition ();

  HBufferImageNeedRefresh         = FALSE;
  HBufferImageOnlyLineNeedRefresh = FALSE;
  gST->ConOut->EnableCursor (gST->ConOut, TRUE);

  return EFI_SUCCESS;
}

/**
  Read an image into a buffer friom a source.

  @param[in] FileName     Pointer to the file name.  OPTIONAL and ignored if not FileTypeFileBuffer.
  @param[in] DiskName     Pointer to the disk name.  OPTIONAL and ignored if not FileTypeDiskBuffer.
  @param[in] DiskOffset   Offset into the disk.  OPTIONAL and ignored if not FileTypeDiskBuffer.
  @param[in] DiskSize     Size of the disk buffer.  OPTIONAL and ignored if not FileTypeDiskBuffer.
  @param[in] MemOffset    Offset into the Memory.  OPTIONAL and ignored if not FileTypeMemBuffer.
  @param[in] MemSize      Size of the Memory buffer.  OPTIONAL and ignored if not FileTypeMemBuffer.
  @param[in] BufferType   The type of buffer to save.  IGNORED.
  @param[in] Recover      TRUE for recovermode, FALSE otherwise.

  @return EFI_SUCCESS     The operation was successful.
**/
EFI_STATUS
HBufferImageRead (
  IN CONST CHAR16    *FileName,
  IN CONST CHAR16    *DiskName,
  IN UINTN           DiskOffset,
  IN UINTN           DiskSize,
  IN UINTN           MemOffset,
  IN UINTN           MemSize,
  IN EDIT_FILE_TYPE  BufferType,
  IN BOOLEAN         Recover
  )
{
  EFI_STATUS      Status;
  EDIT_FILE_TYPE  BufferTypeBackup;

  //
  // variable initialization
  //
  Status                  = EFI_SUCCESS;
  HBufferImage.BufferType = BufferType;

  //
  // three types of buffer supported
  //   file buffer
  //   disk buffer
  //   memory buffer
  //
  BufferTypeBackup = HBufferImage.BufferType;

  switch (BufferType) {
    case FileTypeFileBuffer:
      Status = HFileImageRead (FileName, Recover);
      break;

    case FileTypeDiskBuffer:
      Status = HDiskImageRead (DiskName, DiskOffset, DiskSize, Recover);
      break;

    case FileTypeMemBuffer:
      Status = HMemImageRead (MemOffset, MemSize, Recover);
      break;

    default:
      Status = EFI_NOT_FOUND;
      break;
  }

  if (EFI_ERROR (Status)) {
    HBufferImage.BufferType = BufferTypeBackup;
  }

  return Status;
}

/**
  Save the current image.

  @param[in] FileName     Pointer to the file name.  OPTIONAL and ignored if not FileTypeFileBuffer.
  @param[in] DiskName     Pointer to the disk name.  OPTIONAL and ignored if not FileTypeDiskBuffer.
  @param[in] DiskOffset   Offset into the disk.  OPTIONAL and ignored if not FileTypeDiskBuffer.
  @param[in] DiskSize     Size of the disk buffer.  OPTIONAL and ignored if not FileTypeDiskBuffer.
  @param[in] MemOffset    Offset into the Memory.  OPTIONAL and ignored if not FileTypeMemBuffer.
  @param[in] MemSize      Size of the Memory buffer.  OPTIONAL and ignored if not FileTypeMemBuffer.
  @param[in] BufferType   The type of buffer to save.  IGNORED.

  @return EFI_SUCCESS     The operation was successful.
**/
EFI_STATUS
HBufferImageSave (
  IN CHAR16          *FileName,
  IN CHAR16          *DiskName,
  IN UINTN           DiskOffset,
  IN UINTN           DiskSize,
  IN UINTN           MemOffset,
  IN UINTN           MemSize,
  IN EDIT_FILE_TYPE  BufferType
  )
{
  EFI_STATUS      Status;
  EDIT_FILE_TYPE  BufferTypeBackup;

  //
  // variable initialization
  //
  Status           = EFI_SUCCESS;
  BufferTypeBackup = HBufferImage.BufferType;

  switch (HBufferImage.BufferType) {
    //
    // file buffer
    //
    case FileTypeFileBuffer:
      Status = HFileImageSave (FileName);
      break;

    //
    // disk buffer
    //
    case FileTypeDiskBuffer:
      Status = HDiskImageSave (DiskName, DiskOffset, DiskSize);
      break;

    //
    // memory buffer
    //
    case FileTypeMemBuffer:
      Status = HMemImageSave (MemOffset, MemSize);
      break;

    default:
      Status = EFI_NOT_FOUND;
      break;
  }

  if (EFI_ERROR (Status)) {
    HBufferImage.BufferType = BufferTypeBackup;
  }

  return Status;
}

/**
  Create a new line and append it to the line list.
    Fields affected:
    NumLines
    Lines

  @retval NULL    create line failed.
  @return         the line created.

**/
HEFI_EDITOR_LINE *
HBufferImageCreateLine (
  VOID
  )
{
  HEFI_EDITOR_LINE  *Line;

  //
  // allocate for line structure
  //
  Line = AllocateZeroPool (sizeof (HEFI_EDITOR_LINE));
  if (Line == NULL) {
    return NULL;
  }

  Line->Signature = EFI_EDITOR_LINE_LIST;
  Line->Size      = 0;

  HBufferImage.NumLines++;

  //
  // insert to line list
  //
  InsertTailList (HBufferImage.ListHead, &Line->Link);

  if (HBufferImage.Lines == NULL) {
    HBufferImage.Lines = CR (
                           HBufferImage.ListHead->ForwardLink,
                           HEFI_EDITOR_LINE,
                           Link,
                           EFI_EDITOR_LINE_LIST
                           );
  }

  return Line;
}

/**
  Free the current image.

  @retval EFI_SUCCESS   The operation was successful.
**/
EFI_STATUS
HBufferImageFree (
  VOID
  )
{
  //
  // free all lines
  //
  HBufferImageFreeLines ();

  return EFI_SUCCESS;
}

/**
  change char to int value based on Hex.

  @param[in] Char     The input char.

  @return The character's index value.
  @retval -1  The operation failed.
**/
INTN
HBufferImageCharToHex (
  IN CHAR16  Char
  )
{
  //
  // change the character to hex
  //
  if ((Char >= L'0') && (Char <= L'9')) {
    return (Char - L'0');
  }

  if ((Char >= L'a') && (Char <= L'f')) {
    return (Char - L'a' + 10);
  }

  if ((Char >= L'A') && (Char <= L'F')) {
    return (Char - L'A' + 10);
  }

  return -1;
}

/**
  Add character.

  @param[in] Char -- input char.

  @retval EFI_SUCCESS             The operation was successful.
  @retval EFI_OUT_OF_RESOURCES    A memory allocation failed.
**/
EFI_STATUS
HBufferImageAddChar (
  IN  CHAR16  Char
  )
{
  HEFI_EDITOR_LINE  *Line;
  HEFI_EDITOR_LINE  *NewLine;
  INTN              Value;
  UINT8             Old;
  UINTN             FRow;
  UINTN             FCol;
  BOOLEAN           High;

  Value = HBufferImageCharToHex (Char);

  //
  // invalid input
  //
  if (Value == -1) {
    return EFI_SUCCESS;
  }

  Line = HBufferImage.CurrentLine;
  FRow = HBufferImage.BufferPosition.Row;
  FCol = HBufferImage.BufferPosition.Column;
  High = HBufferImage.HighBits;

  //
  // only needs to refresh current line
  //
  HBufferImageOnlyLineNeedRefresh = TRUE;

  //
  // not a full line and beyond the last character
  //
  if (FCol > Line->Size) {
    //
    // cursor always at high 4 bits
    // and always put input to the low 4 bits
    //
    Line->Buffer[Line->Size] = (UINT8)Value;
    Line->Size++;
    High = FALSE;
  } else {
    Old = Line->Buffer[FCol - 1];

    //
    // always put the input to the low 4 bits
    //
    Old                    = (UINT8)(Old & 0x0f);
    Old                    = (UINT8)(Old << 4);
    Old                    = (UINT8)(Value + Old);
    Line->Buffer[FCol - 1] = Old;

    //
    // at the low 4 bits of the last character of a full line
    // so if no next line, need to create a new line
    //
    if (!High && (FCol == 0x10)) {
      HBufferImageOnlyLineNeedRefresh = FALSE;
      HBufferImageNeedRefresh         = TRUE;

      if (Line->Link.ForwardLink == HBufferImage.ListHead) {
        //
        // last line
        //
        // create a new line
        //
        NewLine = HBufferImageCreateLine ();
        if (NewLine == NULL) {
          return EFI_OUT_OF_RESOURCES;
        }

        //
        // end of NULL
        //
      }

      //
      // end of == ListHead
      //
    }

    //
    // end of == 0x10
    //
    // if already at end of this line, scroll it to the start of next line
    //
    if ((FCol == 0x10) && !High) {
      //
      // definitely has next line
      //
      FRow++;
      FCol = 1;
      High = TRUE;
    } else {
      //
      // if not at end of this line, just move to next column
      //
      if (!High) {
        FCol++;
      }

      if (High) {
        High = FALSE;
      } else {
        High = TRUE;
      }
    }

    //
    // end of ==FALSE
    //
  }

  //
  // move cursor to right
  //
  HBufferImageMovePosition (FRow, FCol, High);

  if (!HBufferImage.Modified) {
    HBufferImage.Modified = TRUE;
  }

  return EFI_SUCCESS;
}

/**
  Delete the previous character.

  @retval EFI_SUCCESS   The operationw as successful.
**/
EFI_STATUS
HBufferImageDoBackspace (
  VOID
  )
{
  HEFI_EDITOR_LINE  *Line;

  UINTN    FileColumn;
  UINTN    FPos;
  BOOLEAN  LastLine;

  //
  // variable initialization
  //
  LastLine = FALSE;

  //
  // already the first character
  //
  if ((HBufferImage.BufferPosition.Row == 1) && (HBufferImage.BufferPosition.Column == 1)) {
    return EFI_SUCCESS;
  }

  FPos = (HBufferImage.BufferPosition.Row - 1) * 0x10 + HBufferImage.BufferPosition.Column - 1;

  FileColumn = HBufferImage.BufferPosition.Column;

  Line     = HBufferImage.CurrentLine;
  LastLine = FALSE;
  if ((Line->Link.ForwardLink == HBufferImage.ListHead) && (FileColumn > 1)) {
    LastLine = TRUE;
  }

  HBufferImageDeleteCharacterFromBuffer (FPos - 1, 1, NULL);

  //
  // if is the last line
  // then only this line need to be refreshed
  //
  if (LastLine) {
    HBufferImageNeedRefresh         = FALSE;
    HBufferImageOnlyLineNeedRefresh = TRUE;
  } else {
    HBufferImageNeedRefresh         = TRUE;
    HBufferImageOnlyLineNeedRefresh = FALSE;
  }

  if (!HBufferImage.Modified) {
    HBufferImage.Modified = TRUE;
  }

  return EFI_SUCCESS;
}

/**
  ASCII key + Backspace + return.

  @param[in] Char               The input char.

  @retval EFI_SUCCESS           The operation was successful.
  @retval EFI_LOAD_ERROR        A load error occurred.
  @retval EFI_OUT_OF_RESOURCES  A memory allocation failed.
**/
EFI_STATUS
HBufferImageDoCharInput (
  IN  CHAR16  Char
  )
{
  EFI_STATUS  Status;

  Status = EFI_SUCCESS;

  switch (Char) {
    case 0:
      break;

    case 0x08:
      Status = HBufferImageDoBackspace ();
      break;

    case 0x09:
    case 0x0a:
    case 0x0d:
      //
      // Tabs, Returns are thought as nothing
      //
      break;

    default:
      //
      // DEAL WITH ASCII CHAR, filter out thing like ctrl+f
      //
      if ((Char > 127) || (Char < 32)) {
        Status = StatusBarSetStatusString (L"Unknown Command");
      } else {
        Status = HBufferImageAddChar (Char);
      }

      break;
  }

  return Status;
}

/**
  Check user specified FileRow is above current screen.

  @param[in] FileRow  Row of file position ( start from 1 ).

  @retval TRUE   It is above the current screen.
  @retval FALSE  It is not above the current screen.

**/
BOOLEAN
HAboveCurrentScreen (
  IN  UINTN  FileRow
  )
{
  if (FileRow < HBufferImage.LowVisibleRow) {
    return TRUE;
  }

  return FALSE;
}

/**
  Check user specified FileRow is under current screen.

  @param[in] FileRow    Row of file position ( start from 1 ).

  @retval TRUE      It is under the current screen.
  @retval FALSE     It is not under the current screen.

**/
BOOLEAN
HUnderCurrentScreen (
  IN  UINTN  FileRow
  )
{
  if (FileRow > HBufferImage.LowVisibleRow + (HMainEditor.ScreenSize.Row - 2) - 1) {
    return TRUE;
  }

  return FALSE;
}

/**
  According to cursor's file position, adjust screen display.

  @param[in] NewFilePosRow    Row of file position ( start from 1 ).
  @param[in] NewFilePosCol    Column of file position ( start from 1 ).
  @param[in] HighBits         Cursor will on high4 bits or low4 bits.
**/
VOID
HBufferImageMovePosition (
  IN UINTN    NewFilePosRow,
  IN UINTN    NewFilePosCol,
  IN BOOLEAN  HighBits
  )
{
  INTN     RowGap;
  UINTN    Abs;
  BOOLEAN  Above;
  BOOLEAN  Under;
  UINTN    NewDisplayCol;

  //
  // CALCULATE gap between current file position and new file position
  //
  RowGap = NewFilePosRow - HBufferImage.BufferPosition.Row;

  Under = HUnderCurrentScreen (NewFilePosRow);
  Above = HAboveCurrentScreen (NewFilePosRow);

  HBufferImage.HighBits = HighBits;

  //
  // if is below current screen
  //
  if (Under) {
    //
    // display row will be unchanged
    //
    HBufferImage.BufferPosition.Row = NewFilePosRow;
  } else {
    if (Above) {
      //
      // has enough above line, so display row unchanged
      // not has enough above lines, so the first line is
      // at the first display line
      //
      if (NewFilePosRow < (HBufferImage.DisplayPosition.Row - 2 + 1)) {
        HBufferImage.DisplayPosition.Row = NewFilePosRow + 2 - 1;
      }

      HBufferImage.BufferPosition.Row = NewFilePosRow;
    } else {
      //
      // in current screen
      //
      HBufferImage.BufferPosition.Row = NewFilePosRow;
      if (RowGap <= 0) {
        Abs                               = (UINTN)ABS (RowGap);
        HBufferImage.DisplayPosition.Row -= Abs;
      } else {
        HBufferImage.DisplayPosition.Row += RowGap;
      }
    }
  }

  HBufferImage.LowVisibleRow = HBufferImage.BufferPosition.Row - (HBufferImage.DisplayPosition.Row - 2);

  //
  // always in current screen
  //
  HBufferImage.BufferPosition.Column = NewFilePosCol;

  NewDisplayCol = 10 + (NewFilePosCol - 1) * 3;
  if (NewFilePosCol > 0x8) {
    NewDisplayCol++;
  }

  if (!HighBits) {
    NewDisplayCol++;
  }

  HBufferImage.DisplayPosition.Column = NewDisplayCol;

  //
  // let CurrentLine point to correct line;
  //
  HBufferImage.CurrentLine = HMoveCurrentLine (RowGap);
}

/**
  Scroll cursor to right.

  @retval EFI_SUCCESS   The operation was successful.
**/
EFI_STATUS
HBufferImageScrollRight (
  VOID
  )
{
  HEFI_EDITOR_LINE  *Line;
  UINTN             FRow;
  UINTN             FCol;

  //
  // scroll right will always move to the high4 bits of the next character
  //
  HBufferImageNeedRefresh         = FALSE;
  HBufferImageOnlyLineNeedRefresh = FALSE;

  Line = HBufferImage.CurrentLine;

  FRow = HBufferImage.BufferPosition.Row;
  FCol = HBufferImage.BufferPosition.Column;

  //
  // this line is not full and no next line
  //
  if (FCol > Line->Size) {
    return EFI_SUCCESS;
  }

  //
  // if already at end of this line, scroll it to the start of next line
  //
  if (FCol == 0x10) {
    //
    // has next line
    //
    if (Line->Link.ForwardLink != HBufferImage.ListHead) {
      FRow++;
      FCol = 1;
    } else {
      return EFI_SUCCESS;
    }
  } else {
    //
    // if not at end of this line, just move to next column
    //
    FCol++;
  }

  HBufferImageMovePosition (FRow, FCol, TRUE);

  return EFI_SUCCESS;
}

/**
  Scroll cursor to left.

  @retval EFI_SUCCESS   The operation was successful.
**/
EFI_STATUS
HBufferImageScrollLeft (
  VOID
  )
{
  HEFI_EDITOR_LINE  *Line;
  UINTN             FRow;
  UINTN             FCol;

  HBufferImageNeedRefresh         = FALSE;
  HBufferImageOnlyLineNeedRefresh = FALSE;

  Line = HBufferImage.CurrentLine;

  FRow = HBufferImage.BufferPosition.Row;
  FCol = HBufferImage.BufferPosition.Column;

  //
  // if already at start of this line, so move to the end of previous line
  //
  if (FCol <= 1) {
    //
    // has previous line
    //
    if (Line->Link.BackLink != HBufferImage.ListHead) {
      FRow--;
      Line = CR (Line->Link.BackLink, HEFI_EDITOR_LINE, Link, EFI_EDITOR_LINE_LIST);
      FCol = Line->Size;
    } else {
      return EFI_SUCCESS;
    }
  } else {
    //
    // if not at start of this line, just move to previous column
    //
    FCol--;
  }

  HBufferImageMovePosition (FRow, FCol, TRUE);

  return EFI_SUCCESS;
}

/**
  Scroll cursor to the next line

  @retval EFI_SUCCESS   The operation was successful.
**/
EFI_STATUS
HBufferImageScrollDown (
  VOID
  )
{
  HEFI_EDITOR_LINE  *Line;
  UINTN             FRow;
  UINTN             FCol;
  BOOLEAN           HighBits;

  Line = HBufferImage.CurrentLine;

  FRow     = HBufferImage.BufferPosition.Row;
  FCol     = HBufferImage.BufferPosition.Column;
  HighBits = HBufferImage.HighBits;

  //
  // has next line
  //
  if (Line->Link.ForwardLink != HBufferImage.ListHead) {
    FRow++;
    Line = CR (Line->Link.ForwardLink, HEFI_EDITOR_LINE, Link, EFI_EDITOR_LINE_LIST);

    //
    // if the next line is not that long, so move to end of next line
    //
    if (FCol > Line->Size) {
      FCol     = Line->Size + 1;
      HighBits = TRUE;
    }
  } else {
    return EFI_SUCCESS;
  }

  HBufferImageMovePosition (FRow, FCol, HighBits);

  return EFI_SUCCESS;
}

/**
  Scroll cursor to previous line

  @retval EFI_SUCCESS   The operation was successful.
**/
EFI_STATUS
HBufferImageScrollUp (
  VOID
  )
{
  HEFI_EDITOR_LINE  *Line;
  UINTN             FRow;
  UINTN             FCol;

  Line = HBufferImage.CurrentLine;

  FRow = HBufferImage.BufferPosition.Row;
  FCol = HBufferImage.BufferPosition.Column;

  //
  // has previous line
  //
  if (Line->Link.BackLink != HBufferImage.ListHead) {
    FRow--;
  } else {
    return EFI_SUCCESS;
  }

  HBufferImageMovePosition (FRow, FCol, HBufferImage.HighBits);

  return EFI_SUCCESS;
}

/**
  Scroll cursor to next page

  @retval EFI_SUCCESS   The operation was successful.
**/
EFI_STATUS
HBufferImagePageDown (
  VOID
  )
{
  HEFI_EDITOR_LINE  *Line;
  UINTN             FRow;
  UINTN             FCol;
  UINTN             Gap;
  BOOLEAN           HighBits;

  Line = HBufferImage.CurrentLine;

  FRow     = HBufferImage.BufferPosition.Row;
  FCol     = HBufferImage.BufferPosition.Column;
  HighBits = HBufferImage.HighBits;

  //
  // has next page
  //
  if (HBufferImage.NumLines >= FRow + (HMainEditor.ScreenSize.Row - 2)) {
    Gap = (HMainEditor.ScreenSize.Row - 2);
  } else {
    //
    // MOVE CURSOR TO LAST LINE
    //
    Gap = HBufferImage.NumLines - FRow;
  }

  //
  // get correct line
  //
  Line = HMoveLine (Gap);

  //
  // if that line, is not that long, so move to the end of that line
  //
  if ((Line != NULL) && (FCol > Line->Size)) {
    FCol     = Line->Size + 1;
    HighBits = TRUE;
  }

  FRow += Gap;

  HBufferImageMovePosition (FRow, FCol, HighBits);

  return EFI_SUCCESS;
}

/**
  Scroll cursor to previous page

  @retval EFI_SUCCESS   The operation was successful.
**/
EFI_STATUS
HBufferImagePageUp (
  VOID
  )
{
  UINTN  FRow;
  UINTN  FCol;
  UINTN  Gap;
  INTN   Retreat;

  FRow = HBufferImage.BufferPosition.Row;
  FCol = HBufferImage.BufferPosition.Column;

  //
  // has previous page
  //
  if (FRow > (HMainEditor.ScreenSize.Row - 2)) {
    Gap = (HMainEditor.ScreenSize.Row - 2);
  } else {
    //
    // the first line of file will displayed on the first line of screen
    //
    Gap = FRow - 1;
  }

  Retreat = Gap;
  Retreat = -Retreat;

  FRow -= Gap;

  HBufferImageMovePosition (FRow, FCol, HBufferImage.HighBits);

  return EFI_SUCCESS;
}

/**
  Scroll cursor to start of line

  @retval EFI_SUCCESS  The operation was successful.
**/
EFI_STATUS
HBufferImageHome (
  VOID
  )
{
  UINTN    FRow;
  UINTN    FCol;
  BOOLEAN  HighBits;

  //
  // curosr will at the high bit
  //
  FRow     = HBufferImage.BufferPosition.Row;
  FCol     = 1;
  HighBits = TRUE;

  //
  // move cursor position
  //
  HBufferImageMovePosition (FRow, FCol, HighBits);

  return EFI_SUCCESS;
}

/**
  Scroll cursor to end of line.

  @retval EFI_SUCCESS  Teh operation was successful.
**/
EFI_STATUS
HBufferImageEnd (
  VOID
  )
{
  HEFI_EDITOR_LINE  *Line;
  UINTN             FRow;
  UINTN             FCol;
  BOOLEAN           HighBits;

  //
  // need refresh mouse
  //
  HBufferImageMouseNeedRefresh = TRUE;

  Line = HBufferImage.CurrentLine;

  FRow = HBufferImage.BufferPosition.Row;

  if (Line->Size == 0x10) {
    FCol     = Line->Size;
    HighBits = FALSE;
  } else {
    FCol     = Line->Size + 1;
    HighBits = TRUE;
  }

  //
  // move cursor position
  //
  HBufferImageMovePosition (FRow, FCol, HighBits);

  return EFI_SUCCESS;
}

/**
  Get the size of the open buffer.

  @retval The size in bytes.
**/
UINTN
HBufferImageGetTotalSize (
  VOID
  )
{
  UINTN  Size;

  HEFI_EDITOR_LINE  *Line;

  //
  // calculate the total size of whole line list's buffer
  //
  if (HBufferImage.Lines == NULL) {
    return 0;
  }

  Line = CR (
           HBufferImage.ListHead->BackLink,
           HEFI_EDITOR_LINE,
           Link,
           EFI_EDITOR_LINE_LIST
           );
  //
  // one line at most 0x10
  //
  Size = 0x10 * (HBufferImage.NumLines - 1) + Line->Size;

  return Size;
}

/**
  Delete character from buffer.

  @param[in] Pos      Position, Pos starting from 0.
  @param[in] Count    The Count of characters to delete.
  @param[out] DeleteBuffer    The DeleteBuffer.

  @retval EFI_SUCCESS Success
**/
EFI_STATUS
HBufferImageDeleteCharacterFromBuffer (
  IN  UINTN  Pos,
  IN  UINTN  Count,
  OUT UINT8  *DeleteBuffer
  )
{
  UINTN  Index;

  VOID   *Buffer;
  UINT8  *BufferPtr;
  UINTN  Size;

  HEFI_EDITOR_LINE  *Line;
  LIST_ENTRY        *Link;

  UINTN  OldFCol;
  UINTN  OldFRow;
  UINTN  OldPos;

  UINTN  NewPos;

  EFI_STATUS  Status;

  Size = HBufferImageGetTotalSize ();

  if (Size < Count) {
    return EFI_LOAD_ERROR;
  }

  if (Size == 0) {
    return EFI_SUCCESS;
  }

  //
  // relocate all the HBufferImage fields
  //
  OldFRow = HBufferImage.BufferPosition.Row;
  OldFCol = HBufferImage.BufferPosition.Column;
  OldPos  = (OldFRow - 1) * 0x10 + OldFCol - 1;

  if (Pos > 0) {
    //
    // has character before it,
    // so locate according to block's previous character
    //
    NewPos = Pos - 1;
  } else {
    //
    // has no character before it,
    // so locate according to block's next character
    //
    NewPos = 0;
  }

  HBufferImageMovePosition (NewPos / 0x10 + 1, NewPos % 0x10 + 1, TRUE);

  Buffer = AllocateZeroPool (Size);
  if (Buffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  HBufferImageListToBuffer (Buffer, Size);

  BufferPtr = (UINT8 *)Buffer;

  //
  // pass deleted buffer out
  //
  if (DeleteBuffer != NULL) {
    CopyMem (&DeleteBuffer[0], &BufferPtr[Pos], Count);
  }

  //
  // delete the part from Pos
  //
  CopyMem (&BufferPtr[Pos], &BufferPtr[Pos + Count], Size - Count - Pos);

  Size -= Count;

  HBufferImageFreeLines ();

  Status = HBufferImageBufferToList (Buffer, Size);
  FreePool (Buffer);

  if (EFI_ERROR (Status)) {
    return Status;
  }

  Link = HMainEditor.BufferImage->ListHead->ForwardLink;
  for (Index = 0; Index < NewPos / 0x10; Index++) {
    Link = Link->ForwardLink;
  }

  Line                     = CR (Link, HEFI_EDITOR_LINE, Link, EFI_EDITOR_LINE_LIST);
  HBufferImage.CurrentLine = Line;

  //
  // if current cursor position if inside select area
  // then move it to the block's NEXT character
  //
  if ((OldPos >= Pos) && (OldPos < (Pos + Count))) {
    NewPos = Pos;
  } else {
    if (OldPos < Pos) {
      NewPos = OldPos;
    } else {
      NewPos = OldPos - Count;
    }
  }

  HBufferImageMovePosition (NewPos / 0x10 + 1, NewPos % 0x10 + 1, TRUE);

  return EFI_SUCCESS;
}

/**
  Add character to buffer, add before pos.

  @param[in] Pos        Position, Pos starting from 0.
  @param[in] Count      Count of characters to add.
  @param[in] AddBuffer  Add buffer.

  @retval EFI_SUCCESS   Success.
**/
EFI_STATUS
HBufferImageAddCharacterToBuffer (
  IN  UINTN  Pos,
  IN  UINTN  Count,
  IN  UINT8  *AddBuffer
  )
{
  INTN  Index;

  VOID   *Buffer;
  UINT8  *BufferPtr;
  UINTN  Size;

  HEFI_EDITOR_LINE  *Line;

  LIST_ENTRY  *Link;

  UINTN  OldFCol;
  UINTN  OldFRow;
  UINTN  OldPos;

  UINTN  NewPos;

  Size = HBufferImageGetTotalSize ();

  //
  // relocate all the HBufferImage fields
  //
  OldFRow = HBufferImage.BufferPosition.Row;
  OldFCol = HBufferImage.BufferPosition.Column;
  OldPos  = (OldFRow - 1) * 0x10 + OldFCol - 1;

  //
  // move cursor before Pos
  //
  if (Pos > 0) {
    NewPos = Pos - 1;
  } else {
    NewPos = 0;
  }

  HBufferImageMovePosition (NewPos / 0x10 + 1, NewPos % 0x10 + 1, TRUE);

  Buffer = AllocateZeroPool (Size + Count);
  if (Buffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  HBufferImageListToBuffer (Buffer, Size);

  BufferPtr = (UINT8 *)Buffer;

  //
  // get a place to add
  //
  CopyMem (&BufferPtr[Pos], &BufferPtr[Pos - Count], Size - Pos + Count);

  //
  // add the buffer
  //
  CopyMem (&BufferPtr[Pos], AddBuffer, Count);

  Size += Count;

  HBufferImageFreeLines ();

  HBufferImageBufferToList (Buffer, Size);

  FreePool (Buffer);

  Link = HMainEditor.BufferImage->ListHead->ForwardLink;
  for (Index = 0; Index < (INTN)NewPos / 0x10; Index++) {
    Link = Link->ForwardLink;
  }

  Line                     = CR (Link, HEFI_EDITOR_LINE, Link, EFI_EDITOR_LINE_LIST);
  HBufferImage.CurrentLine = Line;

  if (OldPos >= Pos) {
    NewPos = OldPos + Count;
  } else {
    NewPos = OldPos;
  }

  HBufferImageMovePosition (NewPos / 0x10 + 1, NewPos % 0x10 + 1, TRUE);

  return EFI_SUCCESS;
}

/**
  Delete current character from line.

  @retval EFI_SUCCESS   The operationw as successful.
**/
EFI_STATUS
HBufferImageDoDelete (
  VOID
  )
{
  HEFI_EDITOR_LINE  *Line;

  BOOLEAN  LastLine;
  UINTN    FileColumn;
  UINTN    FPos;

  FPos = (HBufferImage.BufferPosition.Row - 1) * 0x10 + HBufferImage.BufferPosition.Column - 1;

  FileColumn = HBufferImage.BufferPosition.Column;

  Line = HBufferImage.CurrentLine;

  //
  // if beyond the last character
  //
  if (FileColumn > Line->Size) {
    return EFI_SUCCESS;
  }

  LastLine = FALSE;
  if (Line->Link.ForwardLink == HBufferImage.ListHead) {
    LastLine = TRUE;
  }

  HBufferImageDeleteCharacterFromBuffer (FPos, 1, NULL);

  //
  // if is the last line
  // then only this line need to be refreshed
  //
  if (LastLine) {
    HBufferImageNeedRefresh         = FALSE;
    HBufferImageOnlyLineNeedRefresh = TRUE;
  } else {
    HBufferImageNeedRefresh         = TRUE;
    HBufferImageOnlyLineNeedRefresh = FALSE;
  }

  if (!HBufferImage.Modified) {
    HBufferImage.Modified = TRUE;
  }

  return EFI_SUCCESS;
}

/**
  Change the raw buffer to a list of lines for the UI.

  @param[in] Buffer   The pointer to the buffer to fill.
  @param[in] Bytes    The size of the buffer in bytes.

  @retval EFI_SUCCESS           The operation was successful.
  @retval EFI_OUT_OF_RESOURCES  A memory allocation failed.
**/
EFI_STATUS
HBufferImageBufferToList (
  IN VOID   *Buffer,
  IN UINTN  Bytes
  )
{
  UINTN             TempI;
  UINTN             TempJ;
  UINTN             Left;
  HEFI_EDITOR_LINE  *Line;
  UINT8             *BufferPtr;

  TempI     = 0;
  Left      = 0;
  BufferPtr = (UINT8 *)Buffer;

  //
  // parse file content line by line
  //
  while (TempI < Bytes) {
    if (Bytes - TempI >= 0x10) {
      Left = 0x10;
    } else {
      Left = Bytes - TempI;
    }

    //
    // allocate a new line
    //
    Line = HBufferImageCreateLine ();
    if (Line == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    Line->Size = Left;

    for (TempJ = 0; TempJ < Left; TempJ++) {
      Line->Buffer[TempJ] = BufferPtr[TempI];
      TempI++;
    }
  }

  //
  // last line is a full line, SO create a new line
  //
  if ((Left == 0x10) || (Bytes == 0)) {
    Line = HBufferImageCreateLine ();
    if (Line == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }
  }

  return EFI_SUCCESS;
}

/**
  Change the list of lines from the UI to a raw buffer.

  @param[in] Buffer   The pointer to the buffer to fill.
  @param[in] Bytes    The size of the buffer in bytes.

  @retval EFI_SUCCESS   The operation was successful.
**/
EFI_STATUS
HBufferImageListToBuffer (
  IN VOID   *Buffer,
  IN UINTN  Bytes
  )
{
  UINTN             Count;
  UINTN             Index;
  HEFI_EDITOR_LINE  *Line;
  LIST_ENTRY        *Link;
  UINT8             *BufferPtr;

  //
  // change the line list to a large buffer
  //
  if (HBufferImage.Lines == NULL) {
    return EFI_SUCCESS;
  }

  Link      = &HBufferImage.Lines->Link;
  Count     = 0;
  BufferPtr = (UINT8 *)Buffer;

  //
  // deal line by line
  //
  while (Link != HBufferImage.ListHead) {
    Line = CR (Link, HEFI_EDITOR_LINE, Link, EFI_EDITOR_LINE_LIST);

    // @todo shouldn't this be an error???
    if (Count + Line->Size > Bytes) {
      return EFI_SUCCESS;
    }

    for (Index = 0; Index < Line->Size; Index++) {
      BufferPtr[Index] = Line->Buffer[Index];
    }

    Count     += Line->Size;
    BufferPtr += Line->Size;

    Link = Link->ForwardLink;
  }

  return EFI_SUCCESS;
}

/**
  Move the mouse in the image buffer.

  @param[in] TextX    The x-coordinate.
  @param[in] TextY    The y-coordinate.
**/
VOID
HBufferImageAdjustMousePosition (
  IN INT32  TextX,
  IN INT32  TextY
  )
{
  UINTN  TempX;
  UINTN  TempY;
  UINTN  AbsX;
  UINTN  AbsY;

  //
  // TextX and TextY is mouse movement data returned by mouse driver
  // This function will change it to MousePosition
  //
  //
  // get absolute TempX value
  //
  if (TextX >= 0) {
    AbsX = TextX;
  } else {
    AbsX = -TextX;
  }

  //
  // get absolute TempY value
  //
  if (TextY >= 0) {
    AbsY = TextY;
  } else {
    AbsY = -TextY;
  }

  TempX = HBufferImage.MousePosition.Column;
  TempY = HBufferImage.MousePosition.Row;

  if (TextX >= 0) {
    TempX += TextX;
  } else {
    if (TempX >= AbsX) {
      TempX -= AbsX;
    } else {
      TempX = 0;
    }
  }

  if (TextY >= 0) {
    TempY += TextY;
  } else {
    if (TempY >= AbsY) {
      TempY -= AbsY;
    } else {
      TempY = 0;
    }
  }

  //
  // check whether new mouse column position is beyond screen
  // if not, adjust it
  //
  if ((TempX >= 10) && (TempX <= (10 + 0x10 * 3 - 1))) {
    HBufferImage.MousePosition.Column = TempX;
  } else if (TempX < 10) {
    HBufferImage.MousePosition.Column = 10;
  } else if (TempX > (10 + 0x10 * 3 - 1)) {
    HBufferImage.MousePosition.Column = 10 + 0x10 * 3 - 1;
  }

  //
  // check whether new mouse row position is beyond screen
  // if not, adjust it
  //
  if ((TempY >= 2) && (TempY <= (HMainEditor.ScreenSize.Row - 1))) {
    HBufferImage.MousePosition.Row = TempY;
  } else if (TempY < 2) {
    HBufferImage.MousePosition.Row = 2;
  } else if (TempY > (HMainEditor.ScreenSize.Row - 1)) {
    HBufferImage.MousePosition.Row = (HMainEditor.ScreenSize.Row - 1);
  }
}

/**
  Dispatch input to different handler

  @param[in] Key    The input key:
                     the keys can be:
                       ASCII KEY
                        Backspace/Delete
                        Direction key: up/down/left/right/pgup/pgdn
                        Home/End
                        INS

  @retval EFI_SUCCESS           The operation was successful.
  @retval EFI_LOAD_ERROR        A load error occurred.
  @retval EFI_OUT_OF_RESOURCES  A Memory allocation failed.
**/
EFI_STATUS
HBufferImageHandleInput (
  IN  EFI_INPUT_KEY  *Key
  )
{
  EFI_STATUS  Status;

  Status = EFI_SUCCESS;

  switch (Key->ScanCode) {
    //
    // ordinary key
    //
    case SCAN_NULL:
      Status = HBufferImageDoCharInput (Key->UnicodeChar);
      break;

    //
    // up arrow
    //
    case SCAN_UP:
      Status = HBufferImageScrollUp ();
      break;

    //
    // down arrow
    //
    case SCAN_DOWN:
      Status = HBufferImageScrollDown ();
      break;

    //
    // right arrow
    //
    case SCAN_RIGHT:
      Status = HBufferImageScrollRight ();
      break;

    //
    // left arrow
    //
    case SCAN_LEFT:
      Status = HBufferImageScrollLeft ();
      break;

    //
    // page up
    //
    case SCAN_PAGE_UP:
      Status = HBufferImagePageUp ();
      break;

    //
    // page down
    //
    case SCAN_PAGE_DOWN:
      Status = HBufferImagePageDown ();
      break;

    //
    // delete
    //
    case SCAN_DELETE:
      Status = HBufferImageDoDelete ();
      break;

    //
    // home
    //
    case SCAN_HOME:
      Status = HBufferImageHome ();
      break;

    //
    // end
    //
    case SCAN_END:
      Status = HBufferImageEnd ();
      break;

    default:
      Status = StatusBarSetStatusString (L"Unknown Command");
      break;
  }

  return Status;
}
