{
	This file is part of the Mufasa Macro Library (MML)
	Copyright (c) 2009 by Raymond van Venetiƫ and Merlijn Wajer

    MML is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    MML is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with MML.  If not, see <http://www.gnu.org/licenses/>.

	See the file COPYING, included in this distribution,
	for details about the copyright.

    Files Class for the Mufasa Macro Library
}

unit files;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, MufasaTypes;
const
  File_AccesError = -1;
  File_EventError = -2;

type

  { TMFiles }

  TMFiles = class(TObject)
  public
    OpenFileEvent : TOpenFileEvent;
    WriteFileEvent: TWriteFileEvent;
    function CreateFile(Path: string): Integer;
    function OpenFile(Path: string; Shared: Boolean): Integer;
    function RewriteFile(Path: string; Shared: Boolean): Integer;
    function AppendFile(Path: string): Integer;
    procedure CloseFile(FileNum: Integer);
    function EndOfFile(FileNum: Integer): Boolean;
    function FileSizeMuf(FileNum: Integer): LongInt;
    function ReadFileString(FileNum: Integer; out s: string; x: Integer): Boolean;
    function WriteFileString(FileNum: Integer;const s: string): Boolean;
    function SetFileCharPointer(FileNum, cChars, Origin: Integer): Integer;
    function FilePointerPos(FileNum: Integer): Integer;
    constructor Create(Owner : TObject);
    destructor Destroy; override;
  private
    MFiles: TMufasaFilesArray;
    FreeSpots: Array Of Integer;
    Client : TObject;
    procedure CheckFileNum(FileNum : integer);
    procedure FreeFileList;
    function AddFileToManagedList(Path: string; FS: TFileStream; Mode: Integer): Integer;
  end;

    // We don't need one per object. :-)
  function GetFiles(Path, Ext: string): TStringArray;
  function GetDirectories(Path: string): TstringArray;
  function FindFile(filename : string; Dirs : array of string) : string; //Results '' if not found

implementation
uses
  {$IFDEF MSWINDOWS}Windows,{$ENDIF} IniFiles,Client,FileUtil;

{ GetFiles in independant of the TMFiles class }

function GetFiles(Path, Ext: string): TstringArray;
var
    SearchRec : TSearchRec;
    c : integer;
begin
  c := 0;
  if FindFirst(Path + '*.' + ext, faAnyFile, SearchRec) = 0 then
  begin
    repeat
      if (SearchRec.Attr and faDirectory) = faDirectory then
        Continue;
      inc(c);
      SetLength(Result,c);
      Result[c-1] := SearchRec.Name;
    until FindNext(SearchRec) <> 0;
    SysUtils.FindClose(SearchRec);
  end;
end;

function GetDirectories(Path: string): TstringArray;
var
    SearchRec : TSearchRec;
    c : integer;
begin
  c := 0;
  if FindFirst(Path + '*', faDirectory, SearchRec) = 0 then
  begin
    repeat
      if (SearchRec.Name[1] = '.') or ((SearchRec.Attr and faDirectory) <> faDirectory) then
        continue;
      inc(c);
      SetLength(Result,c);
       Result[c-1] := SearchRec.Name;
    until FindNext(SearchRec) <> 0;
    SysUtils.FindClose(SearchRec);
  end;
end;

function FindFile(filename : string; Dirs : array of string) : string; //Results '' if not found
var
  i : integer;
begin;
  if FileExistsUTF8(filename) then
    result := filename
  else
  begin
    for i := 0 to high(Dirs) do
      if (Dirs[i] <> '') and DirectoryExists(dirs[i]) then
        if fileexistsUTF8(dirs[i] + filename) then
        begin
          result := dirs[i] + filename;
          exit;
        end;
  end;
  result := '';
end;

constructor TMFiles.Create(Owner : TObject);
begin
  inherited Create;
  self.Client := Owner;
  SetLength(Self.MFiles, 0);
  SetLength(Self.FreeSpots, 0);
end;

procedure TMFiles.FreeFileList;
var
  I : integer;
begin;
  For I := 0 To High(MFiles) Do
    if MFiles[i].FS <> nil then
    begin
      TClient(Client).Writeln(Format('File[%s] has not been freed in the script, freeing it now.',[MFiles[i].Path]));
      try
        MFiles[I].FS.Free;
      except
        TClient(Client).Writeln('FreeFileList - Exception when freeing FileStream');
      end;
    end;
  SetLength(MFiles, 0);
  SetLength(FreeSpots, 0);
end;

destructor TMFiles.Destroy;
begin
  FreeFileList;
  inherited;
end;

procedure TMFiles.CheckFileNum(FileNum: integer);
begin
  if(FileNum < 0) or (FileNum >= Length(MFiles)) then
    raise Exception.CreateFmt('Invalid FileNum passed: %d',[FileNum]);
end;

function TMFiles.AddFileToManagedList(Path: String; FS: TFileStream; Mode: Integer): Integer;
var
  tFile: TMufasaFile;
begin
  tFile.Path := Path;
  tFile.FS := FS;
  tFile.Mode := Mode;
  tFile.BytesRead := 0;
  if Length(FreeSpots) > 0 then
  begin
    MFiles[FreeSpots[High(FreeSpots)]] := tFile;
    Result := FreeSpots[High(FreeSpots)];
    SetLength(FreeSpots, High(FreeSpots));
  end else
  begin
    SetLength(MFiles, Length(MFiles) + 1);
    MFiles[High(MFiles)] := tFile;
    Result := High(MFiles);
  end;
end;

function TMFiles.SetFileCharPointer(FileNum, cChars, Origin: Integer): Integer;
begin
  CheckFileNum(FileNum);
  case Origin of
    fsFromBeginning:
                      if(cChars < 0) then
                        raise Exception.CreateFmt('fsFromBeginning takes no negative cChars. (%d)',[cChars]);
    fsFromCurrent:
                  ;
    fsFromEnd:
                  if(cChars > 0) then
                    raise Exception.CreateFmt('fsFromEnd takes no positive cChars. (%d)',[cChars]);
    else
      raise Exception.CreateFmt('Invalid Origin: %d',[Origin]);
  end;

  try
    Result := MFiles[FileNum].FS.Seek(cChars, Origin);
  except
    TClient(Client).Writeln('SetFileCharPointer - Exception Occured.');
    Result := File_AccesError;
  end;
  //Result := FileSeek(Files[FileNum].Handle, cChars, Origin);
end;

{/\
  Creates a file for reading/writing.
  Returns the handle (index) to the File Array.
  Returns File_AccesError if unsuccesfull.
/\}

function TMFiles.CreateFile(Path: string): Integer;
var
  FS: TFileStream;
  Continue : Boolean;
begin
  if Assigned(WriteFileEvent) then
  begin;
    Continue := true;
    WriteFileEvent(Self,path,continue);
    if not Continue then
      exit(File_EventError);
  end;
  try
    FS := TFileStream.Create(UTF8ToSys(Path), fmCreate);
    Result := AddFileToManagedList(Path, FS, fmCreate);
  except
    Result := File_AccesError;
    TClient(Client).Writeln(Format('CreateFile - Exception. Could not create file: %s',[path]));
  end;
end;

{/\
  Opens a file for reading.
  Returns the handle (index) to the File Array.
  Returns File_AccesError if unsuccesfull.
/\}

function TMFiles.OpenFile(Path: string; Shared: Boolean): Integer;
var
  FS: TFileStream;
  fMode: Integer;
  Continue : Boolean;
begin
  if Assigned(OpenFileEvent) then
  begin;
    Continue := true;
    OpenFileEvent(Self,path,continue);
    if not Continue then
      exit(File_EventError);
  end;
  if Shared then
    fMode := fmOpenRead or fmShareDenyNone
  else
    fMode := fmOpenRead or fmShareExclusive;
  try
      FS := TFileStream.Create(UTF8ToSys(Path), fMode)
  except
    Result := File_AccesError;
    TClient(Client).Writeln(Format('OpenFile - Exception. Could not open file: %s',[path]));
    Exit;
  end;
  Result := AddFileToManagedList(Path, FS, fMode);
end;

function TMFiles.AppendFile(Path: string): Integer;
var
  FS: TFileStream;
  fMode: Integer;
  Continue : Boolean;
begin
  if Assigned(WriteFileEvent) then
  begin
    Continue := true;
    WriteFileEvent(Self,path,continue);
    if not Continue then
      exit(File_EventError);
  end;
  fMode := fmOpenReadWrite;
  if not FileExists(Path) then
    fMode := fMode or fmCreate;
  try
    FS := TFileStream.Create(UTF8ToSys(Path), fMode);
    FS.Seek(0, fsFromEnd);
    Result := AddFileToManagedList(Path, FS, fMode);
  except
    Result := File_AccesError;
    TClient(Client).Writeln(Format('AppendFile - Exception. Could not create file: %s',[path]));
  end;
end;

{/\
  Opens a file for writing. And deletes the contents.
  Returns the handle (index) to the File Array.
  Returns File_AccesError if unsuccesfull.
/\}

function TMFiles.RewriteFile(Path: string; Shared: Boolean): Integer;
var
  FS: TFileStream;
  fMode: Integer;
  Continue : Boolean;
begin
  if Assigned(WriteFileEvent) then
  begin;
    Continue := true;
    WriteFileEvent(Self,path,continue);
    if not Continue then
      exit(File_EventError);
  end;
  if Shared then
    fMode := fmOpenReadWrite or fmShareDenyNone  or fmCreate
  else
    fMode := fmOpenReadWrite or fmShareDenyWrite or fmShareDenyRead or fmCreate;
  try
    FS := TFileStream.Create(UTF8ToSys(Path), fMode);
    FS.Size:=0;
    Result := AddFileToManagedList(Path, FS, fMode);
  except
    Result := File_AccesError;
    TClient(Client).Writeln(Format('ReWriteFile - Exception. Could not create file: %s',[path]));
  end;
end;

{/\
  Free's the given File at the given index.
/\}
procedure TMFiles.CloseFile(FileNum: Integer);
begin
  CheckFileNum(filenum);
  try
    MFiles[FileNum].FS.Free;
    MFiles[FileNum].FS := nil;
    SetLength(FreeSpots, Length(FreeSpots) + 1);
    FreeSpots[High(FreeSpots)] := FileNum;
  except
    TClient(Client).Writeln(Format('CloseFile, exception when freeing the file: %d',[filenum]));
  end;
end;

{/\
  Returns true if the BytesRead of the given FileNum (Index) has been reached.
  Also returns true if the FileNum is not valid.
/\}

function TMFiles.EndOfFile(FileNum: Integer): Boolean;
begin
  CheckFileNum(filenum);
  if MFiles[FileNum].FS = nil then
  begin
    TClient(Client).Writeln(format('EndOfFile: Invalid Internal Handle of File: %d',[filenum]));
    Result := True;
    Exit;
  end;
  Result := FilePointerPos(FileNum) >= FileSizeMuf(FileNum);
end;

{/\
  Returns the FileSize of the given index (FileNum)
/\}

function TMFiles.FileSizeMuf(FileNum: Integer): LongInt;
begin
  CheckFileNum(filenum);
  if MFiles[FileNum].FS = nil then
  begin
    TClient(Client).Writeln(format('FileSize: Invalid Internal Handle of File: %d',[filenum]));
    Result := File_AccesError;
    Exit;
  end;

  Result := MFiles[FileNum].FS.Size;
end;

function TMFiles.FilePointerPos(FileNum: Integer): Integer;
begin
  CheckFileNum(filenum);
  if MFiles[FileNum].FS = nil then
  begin
    TClient(Client).Writeln(format('FilePointerPos: Invalid Internal Handle of File: %d',[filenum]));
    Result := File_AccesError;
    Exit;
  end;
  try
    Result := MFiles[FileNum].FS.Seek(0, fsFromCurrent);
  except
    TClient(Client).Writeln('Exception in FilePointerPos');
  end;
end;

{/\
  Reads x numbers of characters from a file, and stores it into s.
/\}

function TMFiles.ReadFileString(FileNum: Integer; out s: string; x: Integer): Boolean;
begin
  CheckFileNum(filenum);
  if MFiles[FileNum].FS = nil then
  begin
    TClient(Client).Writeln(format('ReadFileString: Invalid Internal Handle of File: %d',[filenum]));
    Exit;
  end;

  SetLength(S, X);
  Result := MFiles[FileNum].FS.Read(S[1], x) = x;
  {Files[FileNum].BytesRead := Files[FileNum].BytesRead + X;
  FileRead(Files[FileNum].Handle, S[1], X);
  SetLength(S, X); }
end;

{/\
  Writes s in the given File.
/\}

function TMFiles.WriteFileString(FileNum: Integer;const  s: string): Boolean;
begin
  result := false;
  CheckFileNum(filenum);
  if(MFiles[FileNum].FS = nil) then
  begin
    TClient(Client).Writeln(format('WriteFileString: Invalid Internal Handle of File: %d',[filenum]));
    Exit;
  end;
  if (MFiles[FileNum].Mode and (fmOpenWrite or fmOpenReadWrite)) = 0 then //Checks if we have write rights..
    exit;
  try
    Result := MFiles[FileNum].FS.Write(S[1], Length(S)) <> 1;
  except
    TClient(Client).Writeln('Exception - WriteFileString.');
    Result := False;
  end;
end;

end.