/** @file
  Functions to deal with file buffer.

  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_BUFFER_IMAGE  HBufferImage;

extern BOOLEAN  HBufferImageNeedRefresh;
extern BOOLEAN  HBufferImageOnlyLineNeedRefresh;
extern BOOLEAN  HBufferImageMouseNeedRefresh;

extern HEFI_EDITOR_GLOBAL_EDITOR  HMainEditor;

HEFI_EDITOR_FILE_IMAGE  HFileImage;
HEFI_EDITOR_FILE_IMAGE  HFileImageBackupVar;

//
// for basic initialization of HFileImage
//
HEFI_EDITOR_BUFFER_IMAGE  HFileImageConst = {
  NULL,
  0,
  FALSE
};

/**
  Initialization function for HFileImage

  @retval EFI_SUCCESS     The operation was successful.
**/
EFI_STATUS
HFileImageInit (
  VOID
  )
{
  //
  // basically initialize the HFileImage
  //
  CopyMem (&HFileImage, &HFileImageConst, sizeof (HFileImage));

  CopyMem (
    &HFileImageBackupVar,
    &HFileImageConst,
    sizeof (HFileImageBackupVar)
    );

  return EFI_SUCCESS;
}

/**
  Backup function for HFileImage. 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.
  @retval EFI_OUT_OF_RESOURCES  A memory allocation failed.
**/
EFI_STATUS
HFileImageBackup (
  VOID
  )
{
  SHELL_FREE_NON_NULL (HFileImageBackupVar.FileName);
  HFileImageBackupVar.FileName = CatSPrint (NULL, L"%s", HFileImage.FileName);
  if (HFileImageBackupVar.FileName == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  return EFI_SUCCESS;
}

/**
  Cleanup function for HFileImage.

  @retval EFI_SUCCESS           The operation was successful.
**/
EFI_STATUS
HFileImageCleanup (
  VOID
  )
{
  SHELL_FREE_NON_NULL (HFileImage.FileName);
  SHELL_FREE_NON_NULL (HFileImageBackupVar.FileName);

  return EFI_SUCCESS;
}

/**
  Set FileName field in HFileImage

  @param[in] Str  File name to set.

  @retval EFI_SUCCESS           The operation was successful.
  @retval EFI_OUT_OF_RESOURCES  A memory allocation failed.
**/
EFI_STATUS
HFileImageSetFileName (
  IN CONST CHAR16  *Str
  )
{
  if (Str == HFileImage.FileName) {
    //
    // This function might be called using HFileImage.FileName as Str.
    // Directly return without updating HFileImage.FileName.
    //
    return EFI_SUCCESS;
  }

  //
  // free the old file name
  //
  SHELL_FREE_NON_NULL (HFileImage.FileName);
  HFileImage.FileName = AllocateCopyPool (StrSize (Str), Str);
  if (HFileImage.FileName == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  return EFI_SUCCESS;
}

/**
  Read a file from disk into HBufferImage.

  @param[in] FileName     filename to read.
  @param[in] Recover      if is for recover, no information print.

  @retval EFI_SUCCESS           The operation was successful.
  @retval EFI_OUT_OF_RESOURCES  A memory allocation failed.
  @retval EFI_LOAD_ERROR        A load error occurred.
**/
EFI_STATUS
HFileImageRead (
  IN CONST CHAR16  *FileName,
  IN BOOLEAN       Recover
  )
{
  HEFI_EDITOR_LINE  *Line;
  UINT8             *Buffer;
  CHAR16            *UnicodeBuffer;
  EFI_STATUS        Status;

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

  //
  // in this function, when you return error ( except EFI_OUT_OF_RESOURCES )
  // you should set status string
  // 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
  //
  Status = ReadFileIntoBuffer (FileName, (VOID **)&Buffer, &HFileImage.Size, &HFileImage.ReadOnly);
  //
  // NULL pointer is only also a failure for a non-zero file size.
  //
  if ((EFI_ERROR (Status)) || ((Buffer == NULL) && (HFileImage.Size != 0))) {
    UnicodeBuffer = CatSPrint (NULL, L"Read error on file %s: %r", FileName, Status);
    if (UnicodeBuffer == NULL) {
      SHELL_FREE_NON_NULL (Buffer);
      return EFI_OUT_OF_RESOURCES;
    }

    StatusBarSetStatusString (UnicodeBuffer);
    FreePool (UnicodeBuffer);
    return EFI_OUT_OF_RESOURCES;
  }

  HFileImageSetFileName (FileName);

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

  Status = HBufferImageBufferToList (Buffer, HFileImage.Size);
  SHELL_FREE_NON_NULL (Buffer);
  if (EFI_ERROR (Status)) {
    StatusBarSetStatusString (L"Error parsing file.");
    return Status;
  }

  HBufferImage.DisplayPosition.Row    = 2;
  HBufferImage.DisplayPosition.Column = 10;
  HBufferImage.MousePosition.Row      = 2;
  HBufferImage.MousePosition.Column   = 10;
  HBufferImage.LowVisibleRow          = 1;
  HBufferImage.HighBits               = TRUE;
  HBufferImage.BufferPosition.Row     = 1;
  HBufferImage.BufferPosition.Column  = 1;
  HBufferImage.BufferType             = FileTypeFileBuffer;

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

    StatusBarSetStatusString (UnicodeBuffer);
    FreePool (UnicodeBuffer);

    HMainEditor.SelectStart = 0;
    HMainEditor.SelectEnd   = 0;
  }

  //
  // has line
  //
  if (HBufferImage.Lines != 0) {
    HBufferImage.CurrentLine = CR (HBufferImage.ListHead->ForwardLink, HEFI_EDITOR_LINE, Link, EFI_EDITOR_LINE_LIST);
  } else {
    //
    // create a dummy line
    //
    Line = HBufferImageCreateLine ();
    if (Line == NULL) {
      SHELL_FREE_NON_NULL (Buffer);
      return EFI_OUT_OF_RESOURCES;
    }

    HBufferImage.CurrentLine = Line;
  }

  HBufferImage.Modified           = FALSE;
  HBufferImageNeedRefresh         = TRUE;
  HBufferImageOnlyLineNeedRefresh = FALSE;
  HBufferImageMouseNeedRefresh    = TRUE;

  return EFI_SUCCESS;
}

/**
  Save lines in HBufferImage to disk.

  @param[in] FileName     The file name.

  @retval EFI_SUCCESS           The operation was successful.
  @retval EFI_OUT_OF_RESOURCES  A memory allocation failed.
  @retval EFI_LOAD_ERROR        A load error occurred.
**/
EFI_STATUS
HFileImageSave (
  IN CHAR16  *FileName
  )
{
  LIST_ENTRY         *Link;
  HEFI_EDITOR_LINE   *Line;
  CHAR16             *Str;
  EFI_STATUS         Status;
  UINTN              NumLines;
  SHELL_FILE_HANDLE  FileHandle;
  UINTN              TotalSize;
  UINT8              *Buffer;
  UINT8              *Ptr;
  EDIT_FILE_TYPE     BufferTypeBackup;

  BufferTypeBackup        = HBufferImage.BufferType;
  HBufferImage.BufferType = FileTypeFileBuffer;

  //
  // if is the old file
  //
  if ((HFileImage.FileName != NULL) && (FileName != NULL) && (StrCmp (FileName, HFileImage.FileName) == 0)) {
    //
    // check whether file exists on disk
    //
    if (ShellIsFile (FileName) == EFI_SUCCESS) {
      //
      // current file exists on disk
      // so if not modified, then not save
      //
      if (HBufferImage.Modified == FALSE) {
        return EFI_SUCCESS;
      }

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

  if (ShellIsDirectory (FileName) == EFI_SUCCESS) {
    StatusBarSetStatusString (L"Directory Can Not Be Saved");
    return EFI_LOAD_ERROR;
  }

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

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

  //
  // write all the lines back to disk
  //
  NumLines  = 0;
  TotalSize = 0;
  for (Link = HBufferImage.ListHead->ForwardLink; Link != HBufferImage.ListHead; Link = Link->ForwardLink) {
    Line = CR (Link, HEFI_EDITOR_LINE, Link, EFI_EDITOR_LINE_LIST);

    if (Line->Size != 0) {
      TotalSize += Line->Size;
    }

    //
    // end of if Line -> Size != 0
    //
    NumLines++;
  }

  //
  // end of for Link
  //
  Buffer = AllocateZeroPool (TotalSize);
  if (Buffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Ptr = Buffer;
  for (Link = HBufferImage.ListHead->ForwardLink; Link != HBufferImage.ListHead; Link = Link->ForwardLink) {
    Line = CR (Link, HEFI_EDITOR_LINE, Link, EFI_EDITOR_LINE_LIST);

    if (Line->Size != 0) {
      CopyMem (Ptr, Line->Buffer, Line->Size);
      Ptr += Line->Size;
    }

    //
    // end of if Line -> Size != 0
    //
  }

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

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

  Status = ShellWriteFile (FileHandle, &TotalSize, Buffer);
  FreePool (Buffer);
  if (EFI_ERROR (Status)) {
    ShellDeleteFile (&FileHandle);
    return EFI_LOAD_ERROR;
  }

  ShellCloseFile (&FileHandle);

  HBufferImage.Modified = FALSE;

  //
  // set status string
  //
  Str = CatSPrint (NULL, L"%d Lines Written", NumLines);
  StatusBarSetStatusString (Str);
  FreePool (Str);

  //
  // now everything is ready , you can set the new file name to filebuffer
  //
  if (((BufferTypeBackup != FileTypeFileBuffer) && (FileName != NULL)) ||
      ((FileName != NULL) && (HFileImage.FileName != NULL) && (StringNoCaseCompare (&FileName, &HFileImage.FileName) != 0)))
  {
    //
    // not the same
    //
    HFileImageSetFileName (FileName);
    if (HFileImage.FileName == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }
  }

  HFileImage.ReadOnly = FALSE;

  return EFI_SUCCESS;
}
