/** @file
  Implements filebuffer interface functions.

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

**/

#include "TextEditor.h"
#include <Guid/FileSystemInfo.h>
#include <Library/FileHandleLib.h>

EFI_EDITOR_FILE_BUFFER  FileBuffer;
EFI_EDITOR_FILE_BUFFER  FileBufferBackupVar;

//
// for basic initialization of FileBuffer
//
EFI_EDITOR_FILE_BUFFER  FileBufferConst = {
  NULL,
  FileTypeUnicode,
  NULL,
  NULL,
  0,
  {
    0,
    0
  },
  {
    0,
    0
  },
  {
    0,
    0
  },
  {
    0,
    0
  },
  FALSE,
  TRUE,
  FALSE,
  NULL
};

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

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

BOOLEAN                 FileBufferMouseNeedRefresh;

extern BOOLEAN          EditorMouseAction;

/**
  Initialization function for FileBuffer.

  @param EFI_SUCCESS            The initialization was successful.
  @param EFI_LOAD_ERROR         A default name could not be created.
  @param EFI_OUT_OF_RESOURCES   A memory allocation failed.
**/
EFI_STATUS
FileBufferInit (
  VOID
  )
{
  //
  // basically initialize the FileBuffer
  //
  CopyMem (&FileBuffer         , &FileBufferConst, sizeof (EFI_EDITOR_FILE_BUFFER));
  CopyMem (&FileBufferBackupVar, &FileBufferConst, sizeof (EFI_EDITOR_FILE_BUFFER));

  //
  // set default FileName
  //
  FileBuffer.FileName = EditGetDefaultFileName (L"txt");
  if (FileBuffer.FileName == NULL) {
    return EFI_LOAD_ERROR;
  }

  FileBuffer.ListHead = AllocateZeroPool (sizeof (LIST_ENTRY));
  if (FileBuffer.ListHead == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  InitializeListHead (FileBuffer.ListHead);

  FileBuffer.DisplayPosition.Row    = 2;
  FileBuffer.DisplayPosition.Column = 1;
  FileBuffer.LowVisibleRange.Row    = 2;
  FileBuffer.LowVisibleRange.Column = 1;

  FileBufferNeedRefresh             = FALSE;
  FileBufferMouseNeedRefresh        = FALSE;
  FileBufferOnlyLineNeedRefresh     = FALSE;

  return EFI_SUCCESS;
}

/**
  Backup function for FileBuffer.  Only backup the following items:
      Mouse/Cursor position
      File Name, Type, ReadOnly, Modified
      Insert Mode

  This is for making the file buffer refresh as few as possible.

  @retval EFI_SUCCESS           The backup operation was successful.
**/
EFI_STATUS
FileBufferBackup (
  VOID
  )
{
  FileBufferBackupVar.MousePosition = FileBuffer.MousePosition;

  SHELL_FREE_NON_NULL (FileBufferBackupVar.FileName);
  FileBufferBackupVar.FileName        = NULL;
  FileBufferBackupVar.FileName        = StrnCatGrow (&FileBufferBackupVar.FileName, NULL, FileBuffer.FileName, 0);

  FileBufferBackupVar.ModeInsert      = FileBuffer.ModeInsert;
  FileBufferBackupVar.FileType        = FileBuffer.FileType;

  FileBufferBackupVar.FilePosition    = FileBuffer.FilePosition;
  FileBufferBackupVar.LowVisibleRange = FileBuffer.LowVisibleRange;

  FileBufferBackupVar.FileModified    = FileBuffer.FileModified;
  FileBufferBackupVar.ReadOnly        = FileBuffer.ReadOnly;

  return EFI_SUCCESS;
}

/**
  Advance to the next Count lines

  @param[in] Count              The line number to advance by.
  @param[in] CurrentLine        The pointer to the current line structure.
  @param[in] LineList           The pointer to the linked list of lines.

  @retval NULL                  There was an error.
  @return  The line structure after the advance.
**/
EFI_EDITOR_LINE *
InternalEditorMiscLineAdvance (
  IN CONST UINTN            Count,
  IN CONST EFI_EDITOR_LINE  *CurrentLine,
  IN CONST LIST_ENTRY       *LineList
  )

{
  UINTN                 Index;
  CONST EFI_EDITOR_LINE *Line;

  if (CurrentLine == NULL || LineList == NULL) {
    return NULL;
  }

  for (Line = CurrentLine, Index = 0; Index < Count; Index++) {
    //
    // if already last line
    //
    if (Line->Link.ForwardLink == LineList) {
      return NULL;
    }

    Line = CR (Line->Link.ForwardLink, EFI_EDITOR_LINE, Link, LINE_LIST_SIGNATURE);
  }

  return ((EFI_EDITOR_LINE *)Line);
}

/**
  Retreat to the previous Count lines.

  @param[in] Count              The line number to retreat by.
  @param[in] CurrentLine        The pointer to the current line structure.
  @param[in] LineList           The pointer to the linked list of lines.

  @retval NULL                  There was an error.
  @return  The line structure after the retreat.
**/
EFI_EDITOR_LINE *
InternalEditorMiscLineRetreat (
  IN CONST UINTN            Count,
  IN CONST EFI_EDITOR_LINE  *CurrentLine,
  IN CONST LIST_ENTRY       *LineList
  )

{
  UINTN                 Index;
  CONST EFI_EDITOR_LINE *Line;

  if (CurrentLine == NULL || LineList == NULL) {
    return NULL;
  }

  for (Line = CurrentLine, Index = 0; Index < Count; Index++) {
    //
    // already the first line
    //
    if (Line->Link.BackLink == LineList) {
      return NULL;
    }

    Line = CR (Line->Link.BackLink, EFI_EDITOR_LINE, Link, LINE_LIST_SIGNATURE);
  }

  return ((EFI_EDITOR_LINE *)Line);
}

/**
  Advance/Retreat lines

  @param[in] Count  line number to advance/retreat
                       >0 : advance
                       <0 : retreat

  @retval NULL An error occured.
  @return The line after advance/retreat.
**/
EFI_EDITOR_LINE *
MoveLine (
  IN CONST INTN Count
  )
{
  EFI_EDITOR_LINE *Line;
  UINTN           AbsCount;

  //
  // if < 0, then retreat
  // if > 0, the advance
  //
  if (Count <= 0) {
    AbsCount  = (UINTN)ABS(Count);
    Line      = InternalEditorMiscLineRetreat (AbsCount,MainEditor.FileBuffer->CurrentLine,MainEditor.FileBuffer->ListHead);
  } else {
    Line = InternalEditorMiscLineAdvance ((UINTN)Count,MainEditor.FileBuffer->CurrentLine,MainEditor.FileBuffer->ListHead);
  }

  return Line;
}

/**
  Function to update the 'screen' to display the mouse position.

  @retval EFI_SUCCESS           The backup operation was successful.
**/
EFI_STATUS
FileBufferRestoreMousePosition (
  VOID
  )
{
  EFI_EDITOR_COLOR_UNION  Orig;
  EFI_EDITOR_COLOR_UNION  New;
  UINTN                   FRow;
  UINTN                   FColumn;
  BOOLEAN                 HasCharacter;
  EFI_EDITOR_LINE         *CurrentLine;
  EFI_EDITOR_LINE         *Line;
  CHAR16                  Value;

  //
  // variable initialization
  //
  Line = NULL;

  if (MainEditor.MouseSupported) {

    if (FileBufferMouseNeedRefresh) {

      FileBufferMouseNeedRefresh = FALSE;

      //
      // if mouse position not moved and only mouse action
      // so do not need to refresh mouse position
      //
      if ((FileBuffer.MousePosition.Row == FileBufferBackupVar.MousePosition.Row &&
          FileBuffer.MousePosition.Column == FileBufferBackupVar.MousePosition.Column)
          && EditorMouseAction) {
        return EFI_SUCCESS;
      }
      //
      // backup the old screen attributes
      //
      Orig                  = MainEditor.ColorAttributes;
      New.Data              = 0;
      New.Colors.Foreground = Orig.Colors.Background & 0xF;
      New.Colors.Background = Orig.Colors.Foreground & 0x7;

      //
      // clear the old mouse position
      //
      FRow          = FileBuffer.LowVisibleRange.Row + FileBufferBackupVar.MousePosition.Row - 2;

      FColumn       = FileBuffer.LowVisibleRange.Column + FileBufferBackupVar.MousePosition.Column - 1;

      HasCharacter  = TRUE;
      if (FRow > FileBuffer.NumLines) {
        HasCharacter = FALSE;
      } else {
        CurrentLine = FileBuffer.CurrentLine;
        Line        = MoveLine (FRow - FileBuffer.FilePosition.Row);

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

        FileBuffer.CurrentLine = CurrentLine;
      }

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

      if (HasCharacter) {
        Value = (Line->Buffer[FColumn - 1]);
        ShellPrintEx (
          (INT32)FileBufferBackupVar.MousePosition.Column - 1,
          (INT32)FileBufferBackupVar.MousePosition.Row - 1,
          L"%c",
          Value
          );
      }
      //
      // set the new mouse position
      //
      gST->ConOut->SetAttribute (gST->ConOut, New.Data & 0x7F);

      //
      // clear the old mouse position
      //
      FRow          = FileBuffer.LowVisibleRange.Row + FileBuffer.MousePosition.Row - 2;
      FColumn       = FileBuffer.LowVisibleRange.Column + FileBuffer.MousePosition.Column - 1;

      HasCharacter  = TRUE;
      if (FRow > FileBuffer.NumLines) {
        HasCharacter = FALSE;
      } else {
        CurrentLine = FileBuffer.CurrentLine;
        Line        = MoveLine (FRow - FileBuffer.FilePosition.Row);

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

        FileBuffer.CurrentLine = CurrentLine;
      }

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

      if (HasCharacter) {
        Value = Line->Buffer[FColumn - 1];
        ShellPrintEx (
          (INT32)FileBuffer.MousePosition.Column - 1,
          (INT32)FileBuffer.MousePosition.Row - 1,
          L"%c",
          Value
          );
      }
      //
      // end of HasCharacter
      //
      gST->ConOut->SetAttribute (gST->ConOut, Orig.Data);
    }
    //
    // end of MouseNeedRefresh
    //
  }
  //
  // end of MouseSupported
  //
  return EFI_SUCCESS;
}

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

  @retval EFI_SUCCESS     The operation was successful.
**/
EFI_STATUS
FileBufferFreeLines (
  VOID
  )
{
  LIST_ENTRY  *Link;
  EFI_EDITOR_LINE *Line;

  //
  // free all the lines
  //
  if (FileBuffer.Lines != NULL) {

    Line  = FileBuffer.Lines;
    Link  = &(Line->Link);
    do {
      Line  = CR (Link, EFI_EDITOR_LINE, Link, LINE_LIST_SIGNATURE);
      Link  = Link->ForwardLink;

      //
      // free line's buffer and line itself
      //
      LineFree (Line);
    } while (Link != FileBuffer.ListHead);
  }
  //
  // clean the line list related structure
  //
  FileBuffer.Lines            = NULL;
  FileBuffer.CurrentLine      = NULL;
  FileBuffer.NumLines         = 0;

  FileBuffer.ListHead->ForwardLink  = FileBuffer.ListHead;
  FileBuffer.ListHead->BackLink  = FileBuffer.ListHead;

  return EFI_SUCCESS;
}

/**
  Cleanup function for FileBuffer.

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

  SHELL_FREE_NON_NULL (FileBuffer.FileName);

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

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

  SHELL_FREE_NON_NULL (FileBufferBackupVar.FileName);
  return Status;

}

/**
  Print a line specified by Line on a row specified by Row of the screen.

  @param[in] Line               The line to print.
  @param[in] Row                The row on the screen to print onto (begin from 1).

  @retval EFI_SUCCESS           The printing was successful.
**/
EFI_STATUS
FileBufferPrintLine (
  IN CONST EFI_EDITOR_LINE  *Line,
  IN CONST UINTN            Row
  )
{

  CHAR16  *Buffer;
  UINTN   Limit;
  CHAR16  *PrintLine;
  CHAR16  *PrintLine2;
  UINTN   BufLen;

  //
  // print start from correct character
  //
  Buffer  = Line->Buffer + FileBuffer.LowVisibleRange.Column - 1;

  Limit   = Line->Size - FileBuffer.LowVisibleRange.Column + 1;
  if (Limit > Line->Size) {
    Limit = 0;
  }

  BufLen = (MainEditor.ScreenSize.Column + 1) * sizeof (CHAR16);
  PrintLine = AllocatePool (BufLen);
  if (PrintLine != NULL) {
    StrnCpyS (PrintLine, BufLen/sizeof(CHAR16), Buffer, MIN(Limit, MainEditor.ScreenSize.Column));
    for (Limit = StrLen (PrintLine); Limit < MainEditor.ScreenSize.Column; Limit++) {
      PrintLine[Limit] = L' ';
    }

    PrintLine[MainEditor.ScreenSize.Column] = CHAR_NULL;

    PrintLine2 = AllocatePool (BufLen * 2);
    if (PrintLine2 != NULL) {
      ShellCopySearchAndReplace(PrintLine, PrintLine2, BufLen * 2, L"%", L"^%", FALSE, FALSE);

      ShellPrintEx (
        0,
        (INT32)Row - 1,
        L"%s",
        PrintLine2
        );
      FreePool (PrintLine2);
    }
    FreePool (PrintLine);
  }

  return EFI_SUCCESS;
}

/**
  Set the cursor position according to FileBuffer.DisplayPosition.

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

/**
  Refresh the screen with whats in the buffer.

  @retval EFI_SUCCESS     The refresh was successful.
  @retval EFI_LOAD_ERROR  There was an error finding what to write.
**/
EFI_STATUS
FileBufferRefresh (
  VOID
  )
{
  LIST_ENTRY  *Link;
  EFI_EDITOR_LINE *Line;
  UINTN           Row;

  //
  // if it's the first time after editor launch, so should refresh
  //
  if (!EditorFirst) {
    //
    // no definite required refresh
    // and file position displayed on screen has not been changed
    //
    if (!FileBufferNeedRefresh &&
        !FileBufferOnlyLineNeedRefresh &&
        FileBufferBackupVar.LowVisibleRange.Row == FileBuffer.LowVisibleRange.Row &&
        FileBufferBackupVar.LowVisibleRange.Column == FileBuffer.LowVisibleRange.Column
        ) {

      FileBufferRestoreMousePosition ();
      FileBufferRestorePosition ();

      return EFI_SUCCESS;
    }
  }

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

  //
  // only need to refresh current line
  //
  if (FileBufferOnlyLineNeedRefresh &&
      FileBufferBackupVar.LowVisibleRange.Row == FileBuffer.LowVisibleRange.Row &&
      FileBufferBackupVar.LowVisibleRange.Column == FileBuffer.LowVisibleRange.Column
      ) {

    EditorClearLine (FileBuffer.DisplayPosition.Row, MainEditor.ScreenSize.Column, MainEditor.ScreenSize.Row);
    FileBufferPrintLine (
      FileBuffer.CurrentLine,
      FileBuffer.DisplayPosition.Row
      );
  } else {
    //
    // the whole edit area need refresh
    //

    //
    // no line
    //
    if (FileBuffer.Lines == NULL) {
      FileBufferRestoreMousePosition ();
      FileBufferRestorePosition ();
      gST->ConOut->EnableCursor (gST->ConOut, TRUE);

      return EFI_SUCCESS;
    }
    //
    // get the first line that will be displayed
    //
    Line = MoveLine (FileBuffer.LowVisibleRange.Row - FileBuffer.FilePosition.Row);
    if (Line == NULL) {
      gST->ConOut->EnableCursor (gST->ConOut, TRUE);

      return EFI_LOAD_ERROR;
    }

    Link  = &(Line->Link);
    Row   = 2;
    do {
      Line = CR (Link, EFI_EDITOR_LINE, Link, LINE_LIST_SIGNATURE);

      //
      // print line at row
      //
      FileBufferPrintLine (Line, Row);

      Link = Link->ForwardLink;
      Row++;
    } while (Link != FileBuffer.ListHead && Row <= (MainEditor.ScreenSize.Row - 1));
    //
    // while not file end and not screen full
    //
    while (Row <= (MainEditor.ScreenSize.Row - 1)) {
      EditorClearLine (Row, MainEditor.ScreenSize.Column, MainEditor.ScreenSize.Row);
      Row++;
    }
  }

  FileBufferRestoreMousePosition ();
  FileBufferRestorePosition ();

  FileBufferNeedRefresh         = FALSE;
  FileBufferOnlyLineNeedRefresh = FALSE;

  gST->ConOut->EnableCursor (gST->ConOut, TRUE);
  return EFI_SUCCESS;
}

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

  @retval NULL    The create line failed.
  @return         The line created.
**/
EFI_EDITOR_LINE *
FileBufferCreateLine (
  VOID
  )
{
  EFI_EDITOR_LINE *Line;

  //
  // allocate a line structure
  //
  Line = AllocateZeroPool (sizeof (EFI_EDITOR_LINE));
  if (Line == NULL) {
    return NULL;
  }
  //
  // initialize the structure
  //
  Line->Signature = LINE_LIST_SIGNATURE;
  Line->Size      = 0;
  Line->TotalSize = 0;
  Line->Type      = NewLineTypeDefault;

  //
  // initial buffer of the line is "\0"
  //
  ASSERT(CHAR_NULL == CHAR_NULL);
  Line->Buffer = CatSPrint (NULL, L"\0");
  if (Line->Buffer == NULL) {
    return NULL;
  }

  FileBuffer.NumLines++;

  //
  // insert the line into line list
  //
  InsertTailList (FileBuffer.ListHead, &Line->Link);

  if (FileBuffer.Lines == NULL) {
    FileBuffer.Lines = CR (FileBuffer.ListHead->ForwardLink, EFI_EDITOR_LINE, Link, LINE_LIST_SIGNATURE);
  }

  return Line;
}

/**
  Set FileName field in FileBuffer.

  @param Str                    The file name to set.

  @retval EFI_SUCCESS           The filename was successfully set.
  @retval EFI_OUT_OF_RESOURCES  A memory allocation failed.
  @retval EFI_INVALID_PARAMETER Str is not a valid filename.
**/
EFI_STATUS
FileBufferSetFileName (
  IN CONST CHAR16 *Str
  )
{
  //
  // Verify the parameters
  //
  if (!IsValidFileName(Str)) {
    return (EFI_INVALID_PARAMETER);
  }
  //
  // free the old file name
  //
  SHELL_FREE_NON_NULL (FileBuffer.FileName);

  //
  // Allocate and set the new name
  //
  FileBuffer.FileName = CatSPrint (NULL, L"%s", Str);
  if (FileBuffer.FileName == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  return EFI_SUCCESS;
}
/**
  Free the existing file lines and reset the modified flag.

  @retval EFI_SUCCESS           The operation was successful.
**/
EFI_STATUS
FileBufferFree (
  VOID
  )
{
  //
  // free all the lines
  //
  FileBufferFreeLines ();
  FileBuffer.FileModified = FALSE;

  return EFI_SUCCESS;
}


/**
  Read a file from disk into the FileBuffer.

  @param[in] FileName           The filename to read.
  @param[in] Recover            TRUE if is for recover mode, no information printouts.

  @retval EFI_SUCCESS            The load was successful.
  @retval EFI_LOAD_ERROR         The load failed.
  @retval EFI_OUT_OF_RESOURCES   A memory allocation failed.
  @retval EFI_INVALID_PARAMETER  FileName is a directory.
**/
EFI_STATUS
FileBufferRead (
  IN CONST CHAR16  *FileName,
  IN CONST BOOLEAN Recover
  )
{
  EFI_EDITOR_LINE                 *Line;
  EE_NEWLINE_TYPE                 Type;
  UINTN                           LoopVar1;
  UINTN                           LoopVar2;
  UINTN                           LineSize;
  VOID                            *Buffer;
  CHAR16                          *UnicodeBuffer;
  UINT8                           *AsciiBuffer;
  UINTN                           FileSize;
  SHELL_FILE_HANDLE               FileHandle;
  BOOLEAN                         CreateFile;
  EFI_STATUS                      Status;
  UINTN                           LineSizeBackup;
  EFI_FILE_INFO                   *Info;

  Line          = NULL;
  LoopVar1      = 0;
  FileSize      = 0;
  UnicodeBuffer = NULL;
  Type          = NewLineTypeDefault;
  FileHandle    = NULL;
  CreateFile    = FALSE;

  //
  // in this function, when you return error ( except EFI_OUT_OF_RESOURCES )
  // you should set status string via StatusBarSetStatusString(L"blah")
  // since this function maybe called before the editorhandleinput loop
  // so any error will cause editor return
  // so if you want to print the error status
  // you should set the status string
  //

  //
  // try to open the file
  //
  Status = ShellOpenFileByName (FileName, &FileHandle, EFI_FILE_MODE_READ, 0);

  if (!EFI_ERROR(Status)) {
    CreateFile = FALSE;
    if (FileHandle == NULL) {
      StatusBarSetStatusString (L"Disk Error");
      return EFI_LOAD_ERROR;
    }

    Info = ShellGetFileInfo(FileHandle);

    if (Info->Attribute & EFI_FILE_DIRECTORY) {
      StatusBarSetStatusString (L"Directory Can Not Be Edited");
      FreePool (Info);
      return EFI_INVALID_PARAMETER;
    }

    if (Info->Attribute & EFI_FILE_READ_ONLY) {
      FileBuffer.ReadOnly = TRUE;
    } else {
      FileBuffer.ReadOnly = FALSE;
    }
    //
    // get file size
    //
    FileSize = (UINTN) Info->FileSize;

    FreePool (Info);
  } else if (Status == EFI_NOT_FOUND) {
    //
    // file not exists.  add create and try again
    //
    Status = ShellOpenFileByName (FileName, &FileHandle, EFI_FILE_MODE_READ|EFI_FILE_MODE_WRITE|EFI_FILE_MODE_CREATE, 0);
    if (EFI_ERROR (Status)) {
      if (Status == EFI_WRITE_PROTECTED ||
          Status == EFI_ACCESS_DENIED ||
          Status == EFI_NO_MEDIA ||
          Status == EFI_MEDIA_CHANGED
          ) {
        StatusBarSetStatusString (L"Access Denied");
      } else if (Status == EFI_DEVICE_ERROR || Status == EFI_VOLUME_CORRUPTED || Status == EFI_VOLUME_FULL) {
        StatusBarSetStatusString (L"Disk Error");
      } else {
        StatusBarSetStatusString (L"Invalid File Name or Current-working-directory");
      }

      return Status;
    } else {
      //
      // it worked.  now delete it and move on with the name (now validated)
      //
      Status = ShellDeleteFile (&FileHandle);
      if (Status == EFI_WARN_DELETE_FAILURE) {
        Status = EFI_ACCESS_DENIED;
      }
      FileHandle = NULL;
      if (EFI_ERROR (Status)) {
        StatusBarSetStatusString (L"Access Denied");
        return Status;
      }
    }
    //
    // file doesn't exist, so set CreateFile to TRUE
    //
    CreateFile          = TRUE;
    FileBuffer.ReadOnly = FALSE;

    //
    // all the check ends
    // so now begin to set file name, free lines
    //
    if (StrCmp (FileName, FileBuffer.FileName) != 0) {
      FileBufferSetFileName (FileName);
    }
    //
    // free the old lines
    //
    FileBufferFree ();

  }
  //
  // the file exists
  //
  if (!CreateFile) {
    //
    // allocate buffer to read file
    //
    Buffer = AllocateZeroPool (FileSize);
    if (Buffer == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }
    //
    // read file into Buffer
    //
    Status = ShellReadFile (FileHandle, &FileSize, Buffer);
    ShellCloseFile(&FileHandle);
    FileHandle = NULL;
    if (EFI_ERROR (Status)) {
      StatusBarSetStatusString (L"Read File Failed");
      SHELL_FREE_NON_NULL (Buffer);
      return EFI_LOAD_ERROR;
    }
    //
    // nothing in this file
    //
    if (FileSize == 0) {
      SHELL_FREE_NON_NULL (Buffer);
      //
      // since has no head, so only can be an ASCII file
      //
      FileBuffer.FileType = FileTypeAscii;

      goto Done;
    }

    AsciiBuffer = Buffer;

    if (FileSize < 2) {
      //
      // size < Unicode file header, so only can be ASCII file
      //
      FileBuffer.FileType = FileTypeAscii;
    } else {
      //
      // Unicode file
      //
      if (*(UINT16 *) Buffer == EFI_UNICODE_BYTE_ORDER_MARK) {
        //
        // Unicode file's size should be even
        //
        if ((FileSize % 2) != 0) {
          StatusBarSetStatusString (L"File Format Wrong");
          SHELL_FREE_NON_NULL (Buffer);
          return EFI_LOAD_ERROR;
        }

        FileSize /= 2;

        FileBuffer.FileType = FileTypeUnicode;
        UnicodeBuffer       = Buffer;

        //
        // pass this 0xff and 0xfe
        //
        UnicodeBuffer++;
        FileSize--;
      } else {
        FileBuffer.FileType = FileTypeAscii;
      }
      //
      // end of AsciiBuffer ==
      //
    }
    //
    // end of FileSize < 2
    // all the check ends
    // so now begin to set file name, free lines
    //
    if (StrCmp (FileName, FileBuffer.FileName) != 0) {
      FileBufferSetFileName (FileName);
    }

    //
    // free the old lines
    //
    FileBufferFree ();

    //
    // parse file content line by line
    //
    for (LoopVar1 = 0; LoopVar1 < FileSize; LoopVar1++) {
      Type = NewLineTypeUnknown;

      for (LineSize = LoopVar1; LineSize < FileSize; LineSize++) {
        if (FileBuffer.FileType == FileTypeAscii) {
          if (AsciiBuffer[LineSize] == CHAR_CARRIAGE_RETURN) {
            Type = NewLineTypeCarriageReturn;

            //
            // has LF following
            //
            if (LineSize < FileSize - 1) {
              if (AsciiBuffer[LineSize + 1] == CHAR_LINEFEED) {
                Type = NewLineTypeCarriageReturnLineFeed;
              }
            }

            break;
          } else if (AsciiBuffer[LineSize] == CHAR_LINEFEED) {
            Type = NewLineTypeLineFeed;

            //
            // has CR following
            //
            if (LineSize < FileSize - 1) {
              if (AsciiBuffer[LineSize + 1] == CHAR_CARRIAGE_RETURN) {
                Type = NewLineTypeLineFeedCarriageReturn;
              }
            }

            break;
          }
        } else {
          if (UnicodeBuffer[LineSize] == CHAR_CARRIAGE_RETURN) {
            Type = NewLineTypeCarriageReturn;

            //
            // has LF following
            //
            if (LineSize < FileSize - 1) {
              if (UnicodeBuffer[LineSize + 1] == CHAR_LINEFEED) {
                Type = NewLineTypeCarriageReturnLineFeed;
              }
            }

            break;
          } else if (UnicodeBuffer[LineSize] == CHAR_LINEFEED) {
            Type = NewLineTypeLineFeed;

            //
            // has CR following
            //
            if (LineSize < FileSize - 1) {
              if (UnicodeBuffer[LineSize + 1] == CHAR_CARRIAGE_RETURN) {
                Type = NewLineTypeLineFeedCarriageReturn;
              }
            }

            break;
          }
        }
        //
        // endif == ASCII
        //
      }
      //
      // end of for LineSize
      //
      // if the type is wrong, then exit
      //
      if (Type == NewLineTypeUnknown) {
        //
        // Now if Type is NewLineTypeUnknown, it should be file end
        //
        Type = NewLineTypeDefault;
      }

      LineSizeBackup = LineSize;

      //
      // create a new line
      //
      Line = FileBufferCreateLine ();
      if (Line == NULL) {
        SHELL_FREE_NON_NULL (Buffer);
        return EFI_OUT_OF_RESOURCES;
      }
      //
      // calculate file length
      //
      LineSize -= LoopVar1;

      //
      // Unicode and one CHAR_NULL
      //
      SHELL_FREE_NON_NULL (Line->Buffer);
      Line->Buffer = AllocateZeroPool (LineSize * 2 + 2);

      if (Line->Buffer == NULL) {
        RemoveEntryList (&Line->Link);
        return EFI_OUT_OF_RESOURCES;
      }
      //
      // copy this line to Line->Buffer
      //
      for (LoopVar2 = 0; LoopVar2 < LineSize; LoopVar2++) {
        if (FileBuffer.FileType == FileTypeAscii) {
          Line->Buffer[LoopVar2] = (CHAR16) AsciiBuffer[LoopVar1];
        } else {
          Line->Buffer[LoopVar2] = UnicodeBuffer[LoopVar1];
        }

        LoopVar1++;
      }
      //
      // LoopVar1 now points to where CHAR_CARRIAGE_RETURN or CHAR_LINEFEED;
      //
      Line->Buffer[LineSize]  = 0;

      Line->Size              = LineSize;
      Line->TotalSize         = LineSize;
      Line->Type              = Type;

      if (Type == NewLineTypeCarriageReturnLineFeed || Type == NewLineTypeLineFeedCarriageReturn) {
        LoopVar1++;
      }

      //
      // last character is a return, SO create a new line
      //
      if (((Type == NewLineTypeCarriageReturnLineFeed || Type == NewLineTypeLineFeedCarriageReturn) && LineSizeBackup == FileSize - 2) ||
          ((Type == NewLineTypeLineFeed || Type == NewLineTypeCarriageReturn) && LineSizeBackup == FileSize - 1)
          ) {
        Line = FileBufferCreateLine ();
        if (Line == NULL) {
          SHELL_FREE_NON_NULL (Buffer);
          return EFI_OUT_OF_RESOURCES;
        }
      }
      //
      // end of if
      //
    }
    //
    // end of LoopVar1
    //
    SHELL_FREE_NON_NULL (Buffer);

  }
  //
  // end of if CreateFile
  //
Done:

  FileBuffer.DisplayPosition.Row    = 2;
  FileBuffer.DisplayPosition.Column = 1;
  FileBuffer.LowVisibleRange.Row    = 1;
  FileBuffer.LowVisibleRange.Column = 1;
  FileBuffer.FilePosition.Row       = 1;
  FileBuffer.FilePosition.Column    = 1;
  FileBuffer.MousePosition.Row      = 2;
  FileBuffer.MousePosition.Column   = 1;

  if (!Recover) {
    UnicodeBuffer = CatSPrint (NULL, L"%d Lines Read", FileBuffer.NumLines);
    if (UnicodeBuffer == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    StatusBarSetStatusString (UnicodeBuffer);
    FreePool (UnicodeBuffer);
  }
/*
    //
    // check whether we have fs?: in filename
    //
    LoopVar1             = 0;
    FSMappingPtr  = NULL;
    while (FileName[LoopVar1] != 0) {
      if (FileName[LoopVar1] == L':') {
        FSMappingPtr = &FileName[LoopVar1];
        break;
      }

      LoopVar1++;
    }

    if (FSMappingPtr == NULL) {
      CurDir = ShellGetCurrentDir (NULL);
    } else {
      LoopVar1 = 0;
      LoopVar2 = 0;
      while (FileName[LoopVar1] != 0) {
        if (FileName[LoopVar1] == L':') {
          break;
        }

        FSMapping[LoopVar2++] = FileName[LoopVar1];

        LoopVar1++;
      }

      FSMapping[LoopVar2]  = 0;
      CurDir        = ShellGetCurrentDir (FSMapping);
    }

    if (CurDir != NULL) {
      for (LoopVar1 = 0; LoopVar1 < StrLen (CurDir) && CurDir[LoopVar1] != ':'; LoopVar1++);

      CurDir[LoopVar1]   = 0;
      DevicePath  = (EFI_DEVICE_PATH_PROTOCOL *) ShellGetMap (CurDir);
      FreePool (CurDir);
    } else {
      return EFI_LOAD_ERROR;
    }

    Status = LibDevicePathToInterface (
              &gEfiSimpleFileSystemProtocolGuid,
              DevicePath,
              (VOID **) &Vol
              );
    if (EFI_ERROR (Status)) {
      return EFI_LOAD_ERROR;
    }

    Status = Vol->OpenVolume (Vol, &RootFs);
    if (EFI_ERROR (Status)) {
      return EFI_LOAD_ERROR;
    }
    //
    // Get volume information of file system
    //
    Size        = SIZE_OF_EFI_FILE_SYSTEM_INFO + 100;
    VolumeInfo  = (EFI_FILE_SYSTEM_INFO *) AllocateZeroPool (Size);
    Status      = RootFs->GetInfo (RootFs, &gEfiFileSystemInfoGuid, &Size, VolumeInfo);
    if (EFI_ERROR (Status)) {
      RootFs->Close (RootFs);
      return EFI_LOAD_ERROR;
    }

    if (VolumeInfo->ReadOnly) {
      StatusBarSetStatusString (L"WARNING: Volume Read Only");
    }

    FreePool (VolumeInfo);
    RootFs->Close (RootFs);
  }
//
*/
  //
  // has line
  //
  if (FileBuffer.Lines != 0) {
    FileBuffer.CurrentLine = CR (FileBuffer.ListHead->ForwardLink, EFI_EDITOR_LINE, Link, LINE_LIST_SIGNATURE);
  } else {
    //
    // create a dummy line
    //
    Line = FileBufferCreateLine ();
    if (Line == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    FileBuffer.CurrentLine = Line;
  }

  FileBuffer.FileModified       = FALSE;
  FileBufferNeedRefresh         = TRUE;
  FileBufferOnlyLineNeedRefresh = FALSE;
  FileBufferMouseNeedRefresh    = TRUE;


  return EFI_SUCCESS;
}

/**
  According to FileBuffer.NewLineType & FileBuffer.FileType,
  get the return buffer and size.

  @param[in] Type               The type of line.
  @param[out] Buffer            The buffer to fill.
  @param[out] Size              The amount of the buffer used on return.
**/
VOID
GetNewLine (
  IN CONST EE_NEWLINE_TYPE Type,
  OUT CHAR8           *Buffer,
  OUT UINT8           *Size
  )
{
  UINT8 NewLineSize;

  //
  // give new line buffer,
  // and will judge unicode or ascii
  //
  NewLineSize = 0;

  //
  // not legal new line type
  //
  if (Type != NewLineTypeLineFeed && Type != NewLineTypeCarriageReturn && Type != NewLineTypeCarriageReturnLineFeed && Type != NewLineTypeLineFeedCarriageReturn) {
    *Size = 0;
    return ;
  }
  //
  // use_cr: give 0x0d
  //
  if (Type == NewLineTypeCarriageReturn) {
    if (MainEditor.FileBuffer->FileType == FileTypeUnicode) {
      Buffer[0]   = 0x0d;
      Buffer[1]   = 0;
      NewLineSize = 2;
    } else {
      Buffer[0]   = 0x0d;
      NewLineSize = 1;
    }

    *Size = NewLineSize;
    return ;
  }
  //
  // use_lf: give 0x0a
  //
  if (Type == NewLineTypeLineFeed) {
    if (MainEditor.FileBuffer->FileType == FileTypeUnicode) {
      Buffer[0]   = 0x0a;
      Buffer[1]   = 0;
      NewLineSize = 2;
    } else {
      Buffer[0]   = 0x0a;
      NewLineSize = 1;
    }

    *Size = NewLineSize;
    return ;
  }
  //
  // use_crlf: give 0x0d 0x0a
  //
  if (Type == NewLineTypeCarriageReturnLineFeed) {
    if (MainEditor.FileBuffer->FileType == FileTypeUnicode) {
      Buffer[0]   = 0x0d;
      Buffer[1]   = 0;
      Buffer[2]   = 0x0a;
      Buffer[3]   = 0;

      NewLineSize = 4;
    } else {
      Buffer[0]   = 0x0d;
      Buffer[1]   = 0x0a;
      NewLineSize = 2;
    }

    *Size = NewLineSize;
    return ;
  }
  //
  // use_lfcr: give 0x0a 0x0d
  //
  if (Type == NewLineTypeLineFeedCarriageReturn) {
    if (MainEditor.FileBuffer->FileType == FileTypeUnicode) {
      Buffer[0]   = 0x0a;
      Buffer[1]   = 0;
      Buffer[2]   = 0x0d;
      Buffer[3]   = 0;

      NewLineSize = 4;
    } else {
      Buffer[0]   = 0x0a;
      Buffer[1]   = 0x0d;
      NewLineSize = 2;
    }

    *Size = NewLineSize;
    return ;
  }

}

/**
  Change a Unicode string to an ASCII string.

  @param[in] UStr     The Unicode string.
  @param[in] Length   The maximum size of AStr.
  @param[out] AStr    ASCII string to pass out.

  @return The actuall length.
**/
UINTN
UnicodeToAscii (
  IN CONST CHAR16   *UStr,
  IN CONST UINTN    Length,
  OUT CHAR8         *AStr
  )
{
  UINTN Index;

  //
  // just buffer copy, not character copy
  //
  for (Index = 0; Index < Length; Index++) {
    *AStr++ = (CHAR8) *UStr++;
  }

  return Index;
}

/**
  Save lines in FileBuffer to disk

  @param[in] FileName           The file name for writing.

  @retval EFI_SUCCESS           Data was written.
  @retval EFI_LOAD_ERROR
  @retval EFI_OUT_OF_RESOURCES  There were not enough resources to write the file.
**/
EFI_STATUS
FileBufferSave (
  IN CONST CHAR16 *FileName
  )
{
  SHELL_FILE_HANDLE FileHandle;
  LIST_ENTRY        *Link;
  EFI_EDITOR_LINE   *Line;
  CHAR16            *Str;

  EFI_STATUS        Status;
  UINTN             Length;
  UINTN             NumLines;
  CHAR8             NewLineBuffer[4];
  UINT8             NewLineSize;

  EFI_FILE_INFO     *Info;

  UINT64            Attribute;

  EE_NEWLINE_TYPE   Type;

  UINTN             TotalSize;
  //
  // 2M
  //
  CHAR8             *Cache;
  UINTN             LeftSize;
  UINTN             Size;
  CHAR8             *Ptr;

  Length    = 0;
  //
  // 2M
  //
  TotalSize = 0x200000;

  Attribute = 0;



  //
  // if is the old file
  //
  if (FileBuffer.FileName != NULL && StrCmp (FileName, FileBuffer.FileName) == 0) {
    //
    // file has not been modified
    //
    if (!FileBuffer.FileModified) {
      return EFI_SUCCESS;
    }

    //
    // if file is read-only, set error
    //
    if (FileBuffer.ReadOnly) {
      StatusBarSetStatusString (L"Read Only File Can Not Be Saved");
      return EFI_SUCCESS;
    }
  }

  Status = ShellOpenFileByName (FileName, &FileHandle, EFI_FILE_MODE_READ|EFI_FILE_MODE_WRITE, 0);

  if (!EFI_ERROR (Status)) {
    Info = ShellGetFileInfo(FileHandle);

    if (Info != NULL && Info->Attribute & EFI_FILE_DIRECTORY) {
      StatusBarSetStatusString (L"Directory Can Not Be Saved");
      ShellCloseFile (&FileHandle);
      FreePool(Info);
      return EFI_LOAD_ERROR;
    }

    if (Info != NULL) {
      Attribute = Info->Attribute & ~EFI_FILE_READ_ONLY;
      FreePool(Info);
    }

    //
    // if file exits, so delete it
    //
    Status = ShellDeleteFile (&FileHandle);
    if (EFI_ERROR (Status) || Status == EFI_WARN_DELETE_FAILURE) {
      StatusBarSetStatusString (L"Write File Failed");
      return EFI_LOAD_ERROR;
    }
 }

  Status = ShellOpenFileByName (FileName, &FileHandle, EFI_FILE_MODE_READ|EFI_FILE_MODE_WRITE|EFI_FILE_MODE_CREATE, Attribute);

  if (EFI_ERROR (Status)) {
    StatusBarSetStatusString (L"Create File Failed");
    return EFI_LOAD_ERROR;
  }

  //
  // if file is Unicode file, write Unicode header to it.
  //
  if (FileBuffer.FileType == FileTypeUnicode) {
    Length  = 2;
    Status  = ShellWriteFile (FileHandle, &Length, (VOID*)&gUnicodeFileTag);
    if (EFI_ERROR (Status)) {
      ShellDeleteFile (&FileHandle);
      return EFI_LOAD_ERROR;
    }
  }

  Cache = AllocateZeroPool (TotalSize);
  if (Cache == NULL) {
    ShellDeleteFile (&FileHandle);
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // write all the lines back to disk
  //
  NumLines  = 0;
  Type      = NewLineTypeCarriageReturnLineFeed;

  Ptr       = Cache;
  LeftSize  = TotalSize;

  for (Link = FileBuffer.ListHead->ForwardLink; Link != FileBuffer.ListHead; Link = Link->ForwardLink) {
    Line = CR (Link, EFI_EDITOR_LINE, Link, LINE_LIST_SIGNATURE);

    if (Line->Type != NewLineTypeDefault) {
      Type = Line->Type;
    }
    //
    // newline character is at most 4 bytes ( two Unicode characters )
    //
    Length = 4;
    if (Line->Buffer != NULL && Line->Size != 0) {
      if (FileBuffer.FileType == FileTypeAscii) {
        Length += Line->Size;
      } else {
        Length += (Line->Size * 2);
      }
      //
      // end if FileTypeAscii
      //
    }

    //
    // no cache room left, so write cache to disk
    //
    if (LeftSize < Length) {
      Size    = TotalSize - LeftSize;
      Status  = ShellWriteFile (FileHandle, &Size, Cache);
      if (EFI_ERROR (Status)) {
        ShellDeleteFile (&FileHandle);
        FreePool (Cache);
        return EFI_LOAD_ERROR;
      }
      Ptr       = Cache;
      LeftSize  = TotalSize;
    }

    if (Line->Buffer != NULL && Line->Size != 0) {
      if (FileBuffer.FileType == FileTypeAscii) {
        UnicodeToAscii (Line->Buffer, Line->Size, Ptr);
        Length = Line->Size;
      } else {
        Length = (Line->Size * 2);
        CopyMem (Ptr, (CHAR8 *) Line->Buffer, Length);
      }
      //
      // end if FileTypeAscii
      //
      Ptr += Length;
      LeftSize -= Length;

    }
    //
    // end of if Line -> Buffer != NULL && Line -> Size != 0
    //
    // if not the last line , write return buffer to disk
    //
    if (Link->ForwardLink != FileBuffer.ListHead) {
      GetNewLine (Type, NewLineBuffer, &NewLineSize);
      CopyMem (Ptr, (CHAR8 *) NewLineBuffer, NewLineSize);

      Ptr += NewLineSize;
      LeftSize -= NewLineSize;
    }

    NumLines++;
  }

  if (TotalSize != LeftSize) {
    Size    = TotalSize - LeftSize;
    Status  = ShellWriteFile (FileHandle, &Size, Cache);
    if (EFI_ERROR (Status)) {
      ShellDeleteFile (&FileHandle);
      FreePool (Cache);
      return EFI_LOAD_ERROR;
    }
  }

  FreePool (Cache);

  ShellCloseFile(&FileHandle);

  FileBuffer.FileModified = FALSE;

  //
  // set status string
  //
  Str = CatSPrint (NULL, L"%d Lines Wrote", NumLines);
  if (Str == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  StatusBarSetStatusString (Str);
  SHELL_FREE_NON_NULL (Str);

  //
  // now everything is ready , you can set the new file name to filebuffer
  //
  if (FileName != NULL && FileBuffer.FileName != NULL && StrCmp (FileName, FileBuffer.FileName) != 0) {
    //
    // not the same
    //
    FileBufferSetFileName (FileName);
    if (FileBuffer.FileName == NULL) {
      ShellDeleteFile (&FileHandle);
      return EFI_OUT_OF_RESOURCES;
    }
  }

  FileBuffer.ReadOnly = FALSE;
  return EFI_SUCCESS;
}

/**
  Scroll cursor to left 1 character position.

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

  Line  = FileBuffer.CurrentLine;

  FRow  = FileBuffer.FilePosition.Row;
  FCol  = FileBuffer.FilePosition.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 != FileBuffer.ListHead) {
      FRow--;
      Line  = CR (Line->Link.BackLink, EFI_EDITOR_LINE, Link, LINE_LIST_SIGNATURE);
      FCol  = Line->Size + 1;
    } else {
      return EFI_SUCCESS;
    }
  } else {
    //
    // if not at start of this line, just move to previous column
    //
    FCol--;
  }

  FileBufferMovePosition (FRow, FCol);

  return EFI_SUCCESS;
}

/**
  Delete a char in line

  @param[in, out] Line   The line to delete in.
  @param[in] Pos         Position to delete the char at ( start from 0 ).
**/
VOID
LineDeleteAt (
  IN  OUT EFI_EDITOR_LINE       *Line,
  IN      UINTN                 Pos
  )
{
  UINTN Index;

  //
  // move the latter characters front
  //
  for (Index = Pos - 1; Index < Line->Size; Index++) {
    Line->Buffer[Index] = Line->Buffer[Index + 1];
  }

  Line->Size--;
}

/**
  Concatenate Src into Dest.

  @param[in, out] Dest   Destination string
  @param[in] Src         Src String.
**/
VOID
LineCat (
  IN  OUT EFI_EDITOR_LINE *Dest,
  IN      EFI_EDITOR_LINE *Src
  )
{
  CHAR16  *Str;
  UINTN   Size;

  Size                = Dest->Size;

  Dest->Buffer[Size]  = 0;

  //
  // concatenate the two strings
  //
  Str = CatSPrint (NULL, L"%s%s", Dest->Buffer, Src->Buffer);
  if (Str == NULL) {
    Dest->Buffer = NULL;
    return ;
  }

  Dest->Size      = Size + Src->Size;
  Dest->TotalSize = Dest->Size;

  FreePool (Dest->Buffer);
  FreePool (Src->Buffer);

  //
  // put str to dest->buffer
  //
  Dest->Buffer = Str;
}

/**
  Delete the previous character.

  @retval EFI_SUCCESS           The delete was successful.
  @retval EFI_OUT_OF_RESOURCES  A memory allocation failed.
**/
EFI_STATUS
FileBufferDoBackspace (
  VOID
  )
{
  EFI_EDITOR_LINE *Line;
  EFI_EDITOR_LINE *End;
  LIST_ENTRY  *Link;
  UINTN           FileColumn;

  FileColumn  = FileBuffer.FilePosition.Column;

  Line        = FileBuffer.CurrentLine;

  //
  // the first column
  //
  if (FileColumn == 1) {
    //
    // the first row
    //
    if (FileBuffer.FilePosition.Row == 1) {
      return EFI_SUCCESS;
    }

    FileBufferScrollLeft ();

    Line  = FileBuffer.CurrentLine;
    Link  = Line->Link.ForwardLink;
    End   = CR (Link, EFI_EDITOR_LINE, Link, LINE_LIST_SIGNATURE);

    //
    // concatenate this line with previous line
    //
    LineCat (Line, End);
    if (Line->Buffer == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }
    //
    // remove End from line list
    //
    RemoveEntryList (&End->Link);
    FreePool (End);

    FileBuffer.NumLines--;

    FileBufferNeedRefresh         = TRUE;
    FileBufferOnlyLineNeedRefresh = FALSE;

  } else {
    //
    // just delete the previous character
    //
    LineDeleteAt (Line, FileColumn - 1);
    FileBufferScrollLeft ();
    FileBufferOnlyLineNeedRefresh = TRUE;
  }

  if (!FileBuffer.FileModified) {
    FileBuffer.FileModified = TRUE;
  }

  return EFI_SUCCESS;
}

/**
  Add a return into line at current position.

  @retval EFI_SUCCESS           The insetrion of the character was successful.
  @retval EFI_OUT_OF_RESOURCES  A memory allocation failed.
**/
EFI_STATUS
FileBufferDoReturn (
  VOID
  )
{
  EFI_EDITOR_LINE *Line;
  EFI_EDITOR_LINE *NewLine;
  UINTN           FileColumn;
  UINTN           Index;
  CHAR16          *Buffer;
  UINTN           Row;
  UINTN           Col;

  FileBufferNeedRefresh         = TRUE;
  FileBufferOnlyLineNeedRefresh = FALSE;

  Line                          = FileBuffer.CurrentLine;

  FileColumn                    = FileBuffer.FilePosition.Column;

  NewLine                       = AllocateZeroPool (sizeof (EFI_EDITOR_LINE));
  if (NewLine == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  NewLine->Signature  = LINE_LIST_SIGNATURE;
  NewLine->Size       = Line->Size - FileColumn + 1;
  NewLine->TotalSize  = NewLine->Size;
  NewLine->Buffer     = CatSPrint (NULL, L"\0");
  if (NewLine->Buffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  NewLine->Type = NewLineTypeDefault;

  if (NewLine->Size > 0) {
    //
    // UNICODE + CHAR_NULL
    //
    Buffer = AllocateZeroPool (2 * (NewLine->Size + 1));
    if (Buffer == NULL) {
      FreePool (NewLine->Buffer);
      FreePool (NewLine);
      return EFI_OUT_OF_RESOURCES;
    }

    FreePool (NewLine->Buffer);

    NewLine->Buffer = Buffer;

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

    NewLine->Buffer[NewLine->Size]  = CHAR_NULL;

    Line->Buffer[FileColumn - 1]    = CHAR_NULL;
    Line->Size                      = FileColumn - 1;
  }
  //
  // increase NumLines
  //
  FileBuffer.NumLines++;

  //
  // insert it into the correct position of line list
  //
  NewLine->Link.BackLink     = &(Line->Link);
  NewLine->Link.ForwardLink     = Line->Link.ForwardLink;
  Line->Link.ForwardLink->BackLink = &(NewLine->Link);
  Line->Link.ForwardLink        = &(NewLine->Link);

  //
  // move cursor to the start of next line
  //
  Row = FileBuffer.FilePosition.Row + 1;
  Col = 1;

  FileBufferMovePosition (Row, Col);

  //
  // set file is modified
  //
  if (!FileBuffer.FileModified) {
    FileBuffer.FileModified = TRUE;
  }

  return EFI_SUCCESS;
}

/**
  Delete current character from current line.  This is the effect caused
  by the 'del' key.

  @retval EFI_SUCCESS
**/
EFI_STATUS
FileBufferDoDelete (
  VOID
  )
{
  EFI_EDITOR_LINE *Line;
  EFI_EDITOR_LINE *Next;
  LIST_ENTRY  *Link;
  UINTN           FileColumn;

  Line        = FileBuffer.CurrentLine;
  FileColumn  = FileBuffer.FilePosition.Column;

  //
  // the last column
  //
  if (FileColumn >= Line->Size + 1) {
    //
    // the last line
    //
    if (Line->Link.ForwardLink == FileBuffer.ListHead) {
      return EFI_SUCCESS;
    }
    //
    // since last character,
    // so will add the next line to this line
    //
    Link  = Line->Link.ForwardLink;
    Next  = CR (Link, EFI_EDITOR_LINE, Link, LINE_LIST_SIGNATURE);
    LineCat (Line, Next);
    if (Line->Buffer == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    RemoveEntryList (&Next->Link);
    FreePool (Next);

    FileBuffer.NumLines--;

    FileBufferNeedRefresh         = TRUE;
    FileBufferOnlyLineNeedRefresh = FALSE;

  } else {
    //
    // just delete current character
    //
    LineDeleteAt (Line, FileColumn);
    FileBufferOnlyLineNeedRefresh = TRUE;
  }

  if (!FileBuffer.FileModified) {
    FileBuffer.FileModified = TRUE;
  }

  return EFI_SUCCESS;
}

/**
  Scroll cursor to right 1 character.

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

  Line = FileBuffer.CurrentLine;
  if (Line->Buffer == NULL) {
    return EFI_SUCCESS;
  }

  FRow  = FileBuffer.FilePosition.Row;
  FCol  = FileBuffer.FilePosition.Column;

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

  FileBufferMovePosition (FRow, FCol);

  return EFI_SUCCESS;
}

/**
  Insert a char into line


  @param[in] Line     The line to insert into.
  @param[in] Char     The char to insert.
  @param[in] Pos      The position to insert the char at ( start from 0 ).
  @param[in] StrSize  The current string size ( include CHAR_NULL ),unit is Unicode character.

  @return The new string size ( include CHAR_NULL ) ( unit is Unicode character ).
**/
UINTN
LineStrInsert (
  IN      EFI_EDITOR_LINE  *Line,
  IN      CHAR16           Char,
  IN      UINTN            Pos,
  IN      UINTN            StrSize
  )
{
  UINTN   Index;
  CHAR16  *TempStringPtr;
  CHAR16  *Str;

  Index = (StrSize) * 2;

  Str   = Line->Buffer;

  //
  // do not have free space
  //
  if (Line->TotalSize <= Line->Size) {
    Str = ReallocatePool (Index, Index + 16, Str);
    if (Str == NULL) {
      return 0;
    }

    Line->TotalSize += 8;
  }
  //
  // move the later part of the string one character right
  //
  TempStringPtr = Str;
  for (Index = StrSize; Index > Pos; Index--) {
    TempStringPtr[Index] = TempStringPtr[Index - 1];
  }
  //
  // insert char into it.
  //
  TempStringPtr[Index]      = Char;

  Line->Buffer  = Str;
  Line->Size++;

  return StrSize + 1;
}

/**
  Add a character to the current line.

  @param[in] Char               The Character to input.

  @retval EFI_SUCCESS           The input was succesful.
**/
EFI_STATUS
FileBufferAddChar (
  IN  CHAR16  Char
  )
{
  EFI_EDITOR_LINE *Line;
  UINTN           FilePos;

  Line = FileBuffer.CurrentLine;

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

  //
  // when is insert mode, or cursor is at end of this line,
  // so insert this character
  // or replace the character.
  //
  FilePos = FileBuffer.FilePosition.Column - 1;
  if (FileBuffer.ModeInsert || FilePos + 1 > Line->Size) {
    LineStrInsert (Line, Char, FilePos, Line->Size + 1);
  } else {
    Line->Buffer[FilePos] = Char;
  }
  //
  // move cursor to right
  //
  FileBufferScrollRight ();

  if (!FileBuffer.FileModified) {
    FileBuffer.FileModified = TRUE;
  }

  return EFI_SUCCESS;
}

/**
  Handles inputs from characters (ASCII key + Backspace + return)

  @param[in] Char               The input character.

  @retval EFI_SUCCESS           The operation was successful.
  @retval EFI_LOAD_ERROR        There was an error.
  @retval EFI_OUT_OF_RESOURCES  A memory allocation failed.
**/
EFI_STATUS
FileBufferDoCharInput (
  IN CONST CHAR16 Char
  )
{
  EFI_STATUS  Status;

  Status = EFI_SUCCESS;

  switch (Char) {
  case CHAR_NULL:
    break;

  case CHAR_BACKSPACE:
    Status = FileBufferDoBackspace ();
    break;

  case CHAR_TAB:
    //
    // Tabs are ignored
    //
    break;

  case CHAR_LINEFEED:
  case CHAR_CARRIAGE_RETURN:
    Status = FileBufferDoReturn ();
    break;

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

    break;

  }

  return Status;
}

/**
  Scroll cursor to the next line.

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

  Line = FileBuffer.CurrentLine;
  if (Line->Buffer == NULL) {
    return EFI_SUCCESS;
  }

  FRow  = FileBuffer.FilePosition.Row;
  FCol  = FileBuffer.FilePosition.Column;

  //
  // has next line
  //
  if (Line->Link.ForwardLink != FileBuffer.ListHead) {
    FRow++;
    Line = CR (Line->Link.ForwardLink, EFI_EDITOR_LINE, Link, LINE_LIST_SIGNATURE);

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

  } else {
    return EFI_SUCCESS;
  }

  FileBufferMovePosition (FRow, FCol);

  return EFI_SUCCESS;
}

/**
  Scroll the cursor to previous line.

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

  Line  = FileBuffer.CurrentLine;

  FRow  = FileBuffer.FilePosition.Row;
  FCol  = FileBuffer.FilePosition.Column;

  //
  // has previous line
  //
  if (Line->Link.BackLink != FileBuffer.ListHead) {
    FRow--;
    Line = CR (Line->Link.BackLink, EFI_EDITOR_LINE, Link, LINE_LIST_SIGNATURE);

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

  } else {
    return EFI_SUCCESS;
  }

  FileBufferMovePosition (FRow, FCol);

  return EFI_SUCCESS;
}

/**
  Scroll cursor to next page.

  @retval EFI_SUCCESS     The operation wa successful.
**/
EFI_STATUS
FileBufferPageDown (
  VOID
  )
{
  EFI_EDITOR_LINE *Line;
  UINTN           FRow;
  UINTN           FCol;
  UINTN           Gap;

  Line  = FileBuffer.CurrentLine;

  FRow  = FileBuffer.FilePosition.Row;
  FCol  = FileBuffer.FilePosition.Column;

  //
  // has next page
  //
  if (FileBuffer.NumLines >= FRow + (MainEditor.ScreenSize.Row - 2)) {
    Gap = (MainEditor.ScreenSize.Row - 2);
  } else {
    //
    // MOVE CURSOR TO LAST LINE
    //
    Gap = FileBuffer.NumLines - FRow;
  }
  //
  // get correct line
  //
  Line = MoveLine (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;
  }

  FRow += Gap;

  FileBufferMovePosition (FRow, FCol);

  return EFI_SUCCESS;
}

/**
  Scroll cursor to previous screen.

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

  Line  = FileBuffer.CurrentLine;

  FRow  = FileBuffer.FilePosition.Row;
  FCol  = FileBuffer.FilePosition.Column;

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

  Retreat = Gap;
  Retreat = -Retreat;

  //
  // get correct line
  //
  Line = MoveLine (Retreat);

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

  FRow -= Gap;

  FileBufferMovePosition (FRow, FCol);

  return EFI_SUCCESS;
}

/**
  Scroll cursor to end of the current line.

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

  Line  = FileBuffer.CurrentLine;

  FRow  = FileBuffer.FilePosition.Row;

  //
  // goto the last column of the line
  //
  FCol = Line->Size + 1;

  FileBufferMovePosition (FRow, FCol);

  return EFI_SUCCESS;
}

/**
  Dispatch input to different handler
  @param[in] Key                The input key.  One of:
                                    ASCII KEY
                                    Backspace/Delete
                                    Return
                                    Direction key: up/down/left/right/pgup/pgdn
                                    Home/End
                                    INS

  @retval EFI_SUCCESS           The dispatch was done successfully.
  @retval EFI_LOAD_ERROR        The dispatch was not successful.
  @retval EFI_OUT_OF_RESOURCES  A memory allocation failed.
**/
EFI_STATUS
FileBufferHandleInput (
  IN CONST EFI_INPUT_KEY *Key
  )
{
  EFI_STATUS  Status;

  Status = EFI_SUCCESS;

  switch (Key->ScanCode) {
  //
  // ordinary key input
  //
  case SCAN_NULL:
    if (!FileBuffer.ReadOnly) {
      Status = FileBufferDoCharInput (Key->UnicodeChar);
    } else {
      Status = StatusBarSetStatusString (L"Read Only File Can Not Be Modified");
    }

    break;

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

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

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

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

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

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

  //
  // delete
  //
  case SCAN_DELETE:
    if (!FileBuffer.ReadOnly) {
      Status = FileBufferDoDelete ();
    } else {
      Status = StatusBarSetStatusString (L"Read Only File Can Not Be Modified");
    }

    break;

  //
  // home
  //
  case SCAN_HOME:
    FileBufferMovePosition (FileBuffer.FilePosition.Row, 1);
    Status = EFI_SUCCESS;
    break;

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

  //
  // insert
  //
  case SCAN_INSERT:
    FileBuffer.ModeInsert = (BOOLEAN)!FileBuffer.ModeInsert;
    Status = EFI_SUCCESS;
    break;

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

  return Status;
}

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

  @param[in] FileRow    The 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
AboveCurrentScreen (
  IN UINTN FileRow
  )
{
  //
  // if is to the above of the screen
  //
  if (FileRow < FileBuffer.LowVisibleRange.Row) {
    return TRUE;
  }

  return FALSE;
}

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

  @param[in] FileRow    The 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
UnderCurrentScreen (
  IN UINTN FileRow
  )
{
  //
  // if is to the under of the screen
  //
  if (FileRow > FileBuffer.LowVisibleRange.Row + (MainEditor.ScreenSize.Row - 2) - 1) {
    return TRUE;
  }

  return FALSE;
}

/**
  Check user specified FileCol is left to current screen.

  @param[in] FileCol    The column of file position ( start from 1 ).

  @retval TRUE    It is to the left.
  @retval FALSE   It is not to the left.
**/
BOOLEAN
LeftCurrentScreen (
  IN UINTN FileCol
  )
{
  //
  // if is to the left of the screen
  //
  if (FileCol < FileBuffer.LowVisibleRange.Column) {
    return TRUE;
  }

  return FALSE;
}

/**
  Check user specified FileCol is right to current screen.

  @param[in] FileCol    The column of file position ( start from 1 ).

  @retval TRUE    It is to the right.
  @retval FALSE   It is not to the right.
**/
BOOLEAN
RightCurrentScreen (
  IN UINTN FileCol
  )
{
  //
  // if is to the right of the screen
  //
  if (FileCol > FileBuffer.LowVisibleRange.Column + MainEditor.ScreenSize.Column - 1) {
    return TRUE;
  }

  return FALSE;
}

/**
  Advance/Retreat lines and set CurrentLine in FileBuffer to it

  @param[in] Count The line number to advance/retreat
                     >0 : advance
                     <0: retreat

  @retval NULL An error occured.
  @return The line after advance/retreat.
**/
EFI_EDITOR_LINE *
MoveCurrentLine (
  IN  INTN Count
  )
{
  EFI_EDITOR_LINE *Line;
  UINTN           AbsCount;

  if (Count <= 0) {
    AbsCount  = (UINTN)ABS(Count);
    Line      = InternalEditorMiscLineRetreat (AbsCount,MainEditor.FileBuffer->CurrentLine,MainEditor.FileBuffer->ListHead);
  } else {
    Line = InternalEditorMiscLineAdvance ((UINTN)Count,MainEditor.FileBuffer->CurrentLine,MainEditor.FileBuffer->ListHead);
  }

  if (Line == NULL) {
    return NULL;
  }

  MainEditor.FileBuffer->CurrentLine = Line;

  return Line;
}

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

  @param[in] NewFilePosRow    The row of file position ( start from 1 ).
  @param[in] NewFilePosCol    The column of file position ( start from 1 ).
**/
VOID
FileBufferMovePosition (
  IN CONST UINTN NewFilePosRow,
  IN CONST UINTN NewFilePosCol
  )
{
  INTN    RowGap;
  INTN    ColGap;
  UINTN   Abs;
  BOOLEAN Above;
  BOOLEAN Under;
  BOOLEAN Right;
  BOOLEAN Left;

  //
  // CALCULATE gap between current file position and new file position
  //
  RowGap  = NewFilePosRow - FileBuffer.FilePosition.Row;
  ColGap  = NewFilePosCol - FileBuffer.FilePosition.Column;

  Under   = UnderCurrentScreen (NewFilePosRow);
  Above   = AboveCurrentScreen (NewFilePosRow);
  //
  // if is below current screen
  //
  if (Under) {
    //
    // display row will be unchanged
    //
    FileBuffer.FilePosition.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 < (FileBuffer.DisplayPosition.Row - 1)) {
        FileBuffer.DisplayPosition.Row = NewFilePosRow + 1;
      }

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

  FileBuffer.LowVisibleRange.Row  = FileBuffer.FilePosition.Row - (FileBuffer.DisplayPosition.Row - 2);

  Right = RightCurrentScreen (NewFilePosCol);
  Left = LeftCurrentScreen (NewFilePosCol);

  //
  // if right to current screen
  //
  if (Right) {
    //
    // display column will be changed to end
    //
    FileBuffer.DisplayPosition.Column = MainEditor.ScreenSize.Column;
    FileBuffer.FilePosition.Column    = NewFilePosCol;
  } else {
    if (Left) {
      //
      // has enough left characters , so display row unchanged
      // not has enough left characters,
      // so the first character is at the first display column
      //
      if (NewFilePosCol < (FileBuffer.DisplayPosition.Column)) {
        FileBuffer.DisplayPosition.Column = NewFilePosCol;
      }

      FileBuffer.FilePosition.Column = NewFilePosCol;
    } else {
      //
      // in current screen
      //
      FileBuffer.FilePosition.Column = NewFilePosCol;
      if (ColGap < 0) {
        Abs = (UINTN)(-ColGap);
        FileBuffer.DisplayPosition.Column -= Abs;
      } else {
        FileBuffer.DisplayPosition.Column += ColGap;
      }
    }
  }

  FileBuffer.LowVisibleRange.Column = FileBuffer.FilePosition.Column - (FileBuffer.DisplayPosition.Column - 1);

  //
  // let CurrentLine point to correct line;
  //
  FileBuffer.CurrentLine = MoveCurrentLine (RowGap);

}

/**
  Cut current line out and return a pointer to it.

  @param[out] CutLine    Upon a successful return pointer to the pointer to
                        the allocated cut line.

  @retval EFI_SUCCESS             The cut was successful.
  @retval EFI_NOT_FOUND           There was no selection to cut.
  @retval EFI_OUT_OF_RESOURCES    A memory allocation failed.
**/
EFI_STATUS
FileBufferCutLine (
  OUT EFI_EDITOR_LINE **CutLine
  )
{
  EFI_EDITOR_LINE *Line;
  EFI_EDITOR_LINE *NewLine;
  UINTN           Row;
  UINTN           Col;

  if (FileBuffer.ReadOnly) {
    StatusBarSetStatusString (L"Read Only File Can Not Be Modified");
    return EFI_SUCCESS;
  }

  Line = FileBuffer.CurrentLine;

  //
  // if is the last dummy line, SO CAN not cut
  //
  if (StrCmp (Line->Buffer, L"\0") == 0 && Line->Link.ForwardLink == FileBuffer.ListHead
  //
  // last line
  //
  ) {
    //
    // LAST LINE AND NOTHING ON THIS LINE, SO CUT NOTHING
    //
    StatusBarSetStatusString (L"Nothing to Cut");
    return EFI_NOT_FOUND;
  }
  //
  // if is the last line, so create a dummy line
  //
  if (Line->Link.ForwardLink == FileBuffer.ListHead) {
    //
    // last line
    // create a new line
    //
    NewLine = FileBufferCreateLine ();
    if (NewLine == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }
  }

  FileBuffer.NumLines--;
  Row = FileBuffer.FilePosition.Row;
  Col = 1;
  //
  // move home
  //
  FileBuffer.CurrentLine = CR (
                            FileBuffer.CurrentLine->Link.ForwardLink,
                            EFI_EDITOR_LINE,
                            Link,
                            LINE_LIST_SIGNATURE
                            );

  RemoveEntryList (&Line->Link);

  FileBuffer.Lines = CR (FileBuffer.ListHead->ForwardLink, EFI_EDITOR_LINE, Link, LINE_LIST_SIGNATURE);

  FileBufferMovePosition (Row, Col);

  FileBuffer.FileModified       = TRUE;
  FileBufferNeedRefresh         = TRUE;
  FileBufferOnlyLineNeedRefresh = FALSE;

  *CutLine                      = Line;

  return EFI_SUCCESS;
}

/**
  Paste a line into line list.

  @retval EFI_SUCCESS             The paste was successful.
  @retval EFI_OUT_OF_RESOURCES    A memory allocation failed.
**/
EFI_STATUS
FileBufferPasteLine (
  VOID
  )
{
  EFI_EDITOR_LINE *Line;
  EFI_EDITOR_LINE *NewLine;
  UINTN           Row;
  UINTN           Col;

  //
  // if nothing is on clip board
  // then do nothing
  //
  if (MainEditor.CutLine == NULL) {
    return EFI_SUCCESS;
  }
  //
  // read only file can not be pasted on
  //
  if (FileBuffer.ReadOnly) {
    StatusBarSetStatusString (L"Read Only File Can Not Be Modified");
    return EFI_SUCCESS;
  }

  NewLine = LineDup (MainEditor.CutLine);
  if (NewLine == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }
  //
  // insert it above current line
  //
  Line                    = FileBuffer.CurrentLine;
  NewLine->Link.BackLink     = Line->Link.BackLink;
  NewLine->Link.ForwardLink     = &Line->Link;

  Line->Link.BackLink->ForwardLink = &NewLine->Link;
  Line->Link.BackLink        = &NewLine->Link;

  FileBuffer.NumLines++;
  FileBuffer.CurrentLine  = NewLine;

  FileBuffer.Lines        = CR (FileBuffer.ListHead->ForwardLink, EFI_EDITOR_LINE, Link, LINE_LIST_SIGNATURE);

  Col                     = 1;
  //
  // move home
  //
  Row = FileBuffer.FilePosition.Row;

  FileBufferMovePosition (Row, Col);

  //
  // after paste, set some value so that refresh knows to do something
  //
  FileBuffer.FileModified       = TRUE;
  FileBufferNeedRefresh         = TRUE;
  FileBufferOnlyLineNeedRefresh = FALSE;

  return EFI_SUCCESS;
}

/**
  Search string from current position on in file

  @param[in] Str    The search string.
  @param[in] Offset The offset from current position.

  @retval EFI_SUCCESS       The operation was successful.
  @retval EFI_NOT_FOUND     The string Str was not found.
**/
EFI_STATUS
FileBufferSearch (
  IN CONST CHAR16  *Str,
  IN CONST UINTN Offset
  )
{
  CHAR16          *Current;
  UINTN           Position;
  UINTN           Row;
  UINTN           Column;
  EFI_EDITOR_LINE *Line;
  CHAR16          *CharPos;
  LIST_ENTRY      *Link;
  BOOLEAN         Found;

  Column = 0;
  Position = 0;

  //
  // search if in current line
  //
  Current = FileBuffer.CurrentLine->Buffer + FileBuffer.FilePosition.Column - 1 + Offset;

  if (Current >= (FileBuffer.CurrentLine->Buffer + FileBuffer.CurrentLine->Size)) {
    //
    // the end
    //
    Current = FileBuffer.CurrentLine->Buffer + FileBuffer.CurrentLine->Size;
  }

  Found = FALSE;

  CharPos  =  StrStr (Current, Str);
  if (CharPos != NULL) {
    Position = CharPos - Current + 1;
    Found   = TRUE;
  }

  //
  // found
  //
  if (Found) {
    Column  = (Position - 1) + FileBuffer.FilePosition.Column + Offset;
    Row     = FileBuffer.FilePosition.Row;
  } else {
    //
    // not found so find through next lines
    //
    Link  = FileBuffer.CurrentLine->Link.ForwardLink;

    Row   = FileBuffer.FilePosition.Row + 1;
    while (Link != FileBuffer.ListHead) {
      Line      = CR (Link, EFI_EDITOR_LINE, Link, LINE_LIST_SIGNATURE);
//      Position  = StrStr (Line->Buffer, Str);
      CharPos  =  StrStr (Line->Buffer, Str);
      if (CharPos != NULL) {
        Position = CharPos - Line->Buffer + 1;
        Found   = TRUE;
      }

      if (Found) {
        //
        // found
        //
        Column = Position;
        break;
      }

      Row++;
      Link = Link->ForwardLink;
    }

    if (Link == FileBuffer.ListHead) {
      Found = FALSE;
    } else {
      Found = TRUE;
    }
  }

  if (!Found) {
    return EFI_NOT_FOUND;
  }

  FileBufferMovePosition (Row, Column);

  //
  // call refresh to fresh edit area,
  // because the outer may loop to find multiply occurrence of this string
  //
  FileBufferRefresh ();

  return EFI_SUCCESS;
}

/**
  Replace SearchLen characters from current position on with Replace.

  This will modify the current buffer at the current position.

  @param[in] Replace    The string to replace.
  @param[in] SearchLen  Search string's length.

  @retval EFI_SUCCESS             The operation was successful.
  @retval EFI_OUT_OF_RESOURCES    A memory allocation failed.
**/
EFI_STATUS
FileBufferReplace (
  IN CONST CHAR16   *Replace,
  IN CONST UINTN    SearchLen
  )
{
  UINTN   ReplaceLen;
  UINTN   Index;
  CHAR16  *Buffer;
  UINTN   NewSize;
  UINTN   OldSize;
  UINTN   Gap;

  ReplaceLen  = StrLen (Replace);

  OldSize     = FileBuffer.CurrentLine->Size + 1;
  //
  // include CHAR_NULL
  //
  NewSize = OldSize + (ReplaceLen - SearchLen);

  if (ReplaceLen > SearchLen) {
    //
    // do not have the enough space
    //
    if (FileBuffer.CurrentLine->TotalSize + 1 <= NewSize) {
      FileBuffer.CurrentLine->Buffer = ReallocatePool (
                                        2 * OldSize,
                                        2 * NewSize,
                                        FileBuffer.CurrentLine->Buffer
                                        );
      FileBuffer.CurrentLine->TotalSize = NewSize - 1;
    }

    if (FileBuffer.CurrentLine->Buffer == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }
    //
    // the end CHAR_NULL character;
    //
    Buffer  = FileBuffer.CurrentLine->Buffer + (NewSize - 1);
    Gap     = ReplaceLen - SearchLen;

    //
    // keep the latter part
    //
    for (Index = 0; Index < (FileBuffer.CurrentLine->Size - FileBuffer.FilePosition.Column - SearchLen + 2); Index++) {
      *Buffer = *(Buffer - Gap);
      Buffer--;
    }
    //
    // set replace into it
    //
    Buffer = FileBuffer.CurrentLine->Buffer + FileBuffer.FilePosition.Column - 1;
    for (Index = 0; Index < ReplaceLen; Index++) {
      Buffer[Index] = Replace[Index];
    }
  }

  if (ReplaceLen < SearchLen) {
    Buffer = FileBuffer.CurrentLine->Buffer + FileBuffer.FilePosition.Column - 1;

    for (Index = 0; Index < ReplaceLen; Index++) {
      Buffer[Index] = Replace[Index];
    }

    Buffer += ReplaceLen;
    Gap = SearchLen - ReplaceLen;

    //
    // set replace into it
    //
    for (Index = 0; Index < (FileBuffer.CurrentLine->Size - FileBuffer.FilePosition.Column - ReplaceLen + 2); Index++) {
      *Buffer = *(Buffer + Gap);
      Buffer++;
    }
  }

  if (ReplaceLen == SearchLen) {
    Buffer = FileBuffer.CurrentLine->Buffer + FileBuffer.FilePosition.Column - 1;
    for (Index = 0; Index < ReplaceLen; Index++) {
      Buffer[Index] = Replace[Index];
    }
  }

  FileBuffer.CurrentLine->Size += (ReplaceLen - SearchLen);

  FileBufferOnlyLineNeedRefresh = TRUE;

  FileBuffer.FileModified       = TRUE;

  MainTitleBarRefresh (MainEditor.FileBuffer->FileName, MainEditor.FileBuffer->FileType, MainEditor.FileBuffer->ReadOnly, MainEditor.FileBuffer->FileModified, MainEditor.ScreenSize.Column, MainEditor.ScreenSize.Row, 0, 0);
  FileBufferRestorePosition ();
  FileBufferRefresh ();

  return EFI_SUCCESS;
}

/**
  Move the mouse cursor position.

  @param[in] TextX      The new x-coordinate.
  @param[in] TextY      The new y-coordinate.
**/
VOID
FileBufferAdjustMousePosition (
  IN CONST INT32 TextX,
  IN CONST INT32 TextY
  )
{
  UINTN CoordinateX;
  UINTN CoordinateY;
  UINTN AbsX;
  UINTN AbsY;

  //
  // TextX and TextY is mouse movement data returned by mouse driver
  // This function will change it to MousePosition
  //
  //
  // get absolute value
  //

  AbsX = ABS(TextX);
  AbsY = ABS(TextY);

  CoordinateX = FileBuffer.MousePosition.Column;
  CoordinateY = FileBuffer.MousePosition.Row;

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

  if (TextY >= 0) {
    CoordinateY += TextY;
  } else {
    if (CoordinateY >= AbsY) {
      CoordinateY -= AbsY;
    } else {
      CoordinateY = 0;
    }
  }
  //
  // check whether new mouse column position is beyond screen
  // if not, adjust it
  //
  if (CoordinateX >= 1 && CoordinateX <= MainEditor.ScreenSize.Column) {
    FileBuffer.MousePosition.Column = CoordinateX;
  } else if (CoordinateX < 1) {
    FileBuffer.MousePosition.Column = 1;
  } else if (CoordinateX > MainEditor.ScreenSize.Column) {
    FileBuffer.MousePosition.Column = MainEditor.ScreenSize.Column;
  }
  //
  // check whether new mouse row position is beyond screen
  // if not, adjust it
  //
  if (CoordinateY >= 2 && CoordinateY <= (MainEditor.ScreenSize.Row - 1)) {
    FileBuffer.MousePosition.Row = CoordinateY;
  } else if (CoordinateY < 2) {
    FileBuffer.MousePosition.Row = 2;
  } else if (CoordinateY > (MainEditor.ScreenSize.Row - 1)) {
    FileBuffer.MousePosition.Row = (MainEditor.ScreenSize.Row - 1);
  }

}

/**
  Search and replace operation.

  @param[in] SearchStr    The string to search for.
  @param[in] ReplaceStr   The string to replace with.
  @param[in] Offset       The column to start at.
**/
EFI_STATUS
FileBufferReplaceAll (
  IN CHAR16 *SearchStr,
  IN CHAR16 *ReplaceStr,
  IN UINTN  Offset
  )
{
  CHAR16          *Buffer;
  UINTN           Position;
  UINTN           Column;
  UINTN           ReplaceLen;
  UINTN           SearchLen;
  UINTN           Index;
  UINTN           NewSize;
  UINTN           OldSize;
  UINTN           Gap;
  EFI_EDITOR_LINE *Line;
  LIST_ENTRY      *Link;
  CHAR16          *CharPos;

  SearchLen   = StrLen (SearchStr);
  ReplaceLen  = StrLen (ReplaceStr);

  Column      = FileBuffer.FilePosition.Column + Offset - 1;

  if (Column > FileBuffer.CurrentLine->Size) {
    Column = FileBuffer.CurrentLine->Size;
  }

  Link = &(FileBuffer.CurrentLine->Link);

  while (Link != FileBuffer.ListHead) {
    Line      = CR (Link, EFI_EDITOR_LINE, Link, LINE_LIST_SIGNATURE);
    CharPos  =  StrStr (Line->Buffer + Column, SearchStr);
    if (CharPos != NULL) {
      Position = CharPos - Line->Buffer;// + Column;
      //
      // found
      //
      if (ReplaceLen > SearchLen) {
        OldSize = Line->Size + 1;
        //
        // include CHAR_NULL
        //
        NewSize = OldSize + (ReplaceLen - SearchLen);

        //
        // do not have the enough space
        //
        if (Line->TotalSize + 1 <= NewSize) {
          Line->Buffer = ReallocatePool (
                          2 * OldSize,
                          2 * NewSize,
                          Line->Buffer
                          );
          Line->TotalSize = NewSize - 1;
        }

        if (Line->Buffer == NULL) {
          return EFI_OUT_OF_RESOURCES;
        }
        //
        // the end CHAR_NULL character;
        //
        Buffer  = Line->Buffer + (NewSize - 1);
        Gap     = ReplaceLen - SearchLen;

        //
        // keep the latter part
        //
        for (Index = 0; Index < (Line->Size - Position - SearchLen + 1); Index++) {
          *Buffer = *(Buffer - Gap);
          Buffer--;
        }

      } else if (ReplaceLen < SearchLen){
        Buffer  = Line->Buffer + Position + ReplaceLen;
        Gap     = SearchLen - ReplaceLen;

        for (Index = 0; Index < (Line->Size - Position - ReplaceLen + 1); Index++) {
          *Buffer = *(Buffer + Gap);
          Buffer++;
        }
      } else {
        ASSERT(ReplaceLen == SearchLen);
      }
      //
      // set replace into it
      //
      Buffer = Line->Buffer + Position;
      for (Index = 0; Index < ReplaceLen; Index++) {
        Buffer[Index] = ReplaceStr[Index];
      }

      Line->Size += (ReplaceLen - SearchLen);
      Column += ReplaceLen;
    } else {
      //
      // not found
      //
      Column  = 0;
      Link    = Link->ForwardLink;
    }
  }
  //
  // call refresh to fresh edit area
  //
  FileBuffer.FileModified = TRUE;
  FileBufferNeedRefresh   = TRUE;
  FileBufferRefresh ();

  return EFI_SUCCESS;
}

/**
  Set the modified state to TRUE.
**/
VOID
FileBufferSetModified (
  VOID
  )
{
  FileBuffer.FileModified = TRUE;
}

