{
    This file is part of the Free Pascal run time library.
    Copyright (c) 1999-2000 by Michael Van Canneyt and Peter Vreman,
    members of the Free Pascal development team

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

    This program 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.

 **********************************************************************}
{$IFNDEF FPC_DOTTEDUNITS}
Unit Dos;
{$ENDIF FPC_DOTTEDUNITS}
Interface

Const
  FileNameLen = 255;

Type
  SearchRec =
{$ifndef FPC_REQUIRES_PROPER_ALIGNMENT}
    packed
{$endif FPC_REQUIRES_PROPER_ALIGNMENT}
    Record
  {Fill : array[1..21] of byte;  Fill replaced with below}
    SearchPos  : UInt64;      {directory position}
    SearchNum  : LongInt;     {to track which search this is}
    DirFD      : LongInt;     {directory fd handle for reading directory}
    SearchType : Byte;        {0=normal, 1=open will close, 2=only 1 file}
    SearchAttr : Byte;        {attribute we are searching for}
    Mode       : Word;
    Fill       : Array[1..1] of Byte; {future use}
  {End of fill}
    Attr       : Byte;        {attribute of found file}
    Time       : LongInt;     {last modify date of found file}
    Size       : LongInt;     {file size of found file}
    Reserved   : Word;        {future use}
    Name       : String[FileNameLen]; {name of found file}
    SearchSpec : String[FileNameLen]; {search pattern}
    NamePos    : Word;        {end of path, start of name position}
  End;

{$DEFINE HAS_FILENAMELEN}
{$i dosh.inc}

{Extra Utils}
function weekday(y,m,d : longint) : longint; platform;
Procedure WasiDateToDt(NanoSecsPast: UInt64; Var Dt: DateTime); platform;
Function DTToWasiDate(DT: DateTime): UInt64; platform;

{Disk}
//Function AddDisk(const path:string) : byte; platform;

Implementation

{$IFDEF FPC_DOTTEDUNITS}
Uses
  WASIApi.WASIApi, WASIApi.WASIUtil;
{$ELSE FPC_DOTTEDUNITS}
Uses
  WasiAPI, WasiUtil;
{$ENDIF FPC_DOTTEDUNITS}

{$DEFINE HAS_GETMSCOUNT}

{$DEFINE FPC_FEXPAND_TILDE} { Tilde is expanded to home }
{$DEFINE FPC_FEXPAND_GETENVPCHAR} { GetEnv result is a PAnsiChar }

{$I dos.inc}


{******************************************************************************
                           --- Link C Lib if set ---
******************************************************************************}


{******************************************************************************
                        --- Info / Date / Time ---
******************************************************************************}

Function DosVersion:Word;
Begin
End;

function WeekDay (y,m,d:longint):longint;
{
  Calculates th day of the week. returns -1 on error
}
var
  u,v : longint;
begin
  if (m<1) or (m>12) or (y<1600) or (y>4000) or
     (d<1) or (d>30+((m+ord(m>7)) and 1)-ord(m=2)) or
     ((m*d=58) and (((y mod 4>0) or (y mod 100=0)) and (y mod 400>0))) then
   WeekDay:=-1
  else
   begin
     u:=m;
     v:=y;
     if m<3 then
      begin
        inc(u,12);
        dec(v);
      end;
     WeekDay:=(d+2*u+((3*(u+1)) div 5)+v+(v div 4)-(v div 100)+(v div 400)+1) mod 7;
   end;
end;


Procedure GetDate(Var Year, Month, MDay, WDay: Word);
var
  NanoSecsPast: __wasi_timestamp_t;
  DT: DateTime;
begin
  if __wasi_clock_time_get(__WASI_CLOCKID_REALTIME,10000000,@NanoSecsPast)=__WASI_ERRNO_SUCCESS then
  begin
    WasiDateToDT(NanoSecsPast,DT);
    Year:=DT.Year;
    Month:=DT.Month;
    MDay:=DT.Day;
    WDay:=weekday(DT.Year,DT.Month,DT.Day);
  end
  else
  begin
    Year:=0;
    Month:=0;
    MDay:=0;
    WDay:=0;
  end;
end;


procedure  SetTime(Hour,Minute,Second,sec100:word);
begin
end;

procedure SetDate(Year,Month,Day:Word);
begin
end;


Function SetDateTime(Year,Month,Day,hour,minute,second:Word) : Boolean;
begin
end;


Procedure GetTime(Var Hour, Minute, Second, Sec100: Word);
var
  NanoSecsPast: __wasi_timestamp_t;
begin
  if __wasi_clock_time_get(__WASI_CLOCKID_REALTIME,10000000,@NanoSecsPast)=__WASI_ERRNO_SUCCESS then
  begin
    { todo: convert UTC to local time, as soon as we can get the local timezone
      from WASI: https://github.com/WebAssembly/WASI/issues/239 }
    NanoSecsPast:=NanoSecsPast div 10000000;
    Sec100:=NanoSecsPast mod 100;
    NanoSecsPast:=NanoSecsPast div 100;
    Second:=NanoSecsPast mod 60;
    NanoSecsPast:=NanoSecsPast div 60;
    Minute:=NanoSecsPast mod 60;
    NanoSecsPast:=NanoSecsPast div 60;
    Hour:=NanoSecsPast mod 24;
  end
  else
  begin
    Hour:=0;
    Minute:=0;
    Second:=0;
    Sec100:=0;
  end;
end;


Function DTToWasiDate(DT: DateTime): UInt64;
var
  res: Int64;
begin
  res:={$IFDEF FPC_DOTTEDUNITS}WASIApi.{$ENDIF}WasiUtil.LocalToEpoch(DT.year,DT.month,DT.day,DT.hour,DT.min,DT.sec);
  if res<0 then
    DTToWasiDate:=0
  else
    DTToWasiDate:=res*1000000000;
end;


Procedure WasiDateToDt(NanoSecsPast: UInt64; Var Dt: DateTime);
Begin
  {$IFDEF FPC_DOTTEDUNITS}WASIApi.{$ENDIF}WasiUtil.EpochToLocal(NanoSecsPast div 1000000000,Dt.Year,Dt.Month,Dt.Day,Dt.Hour,Dt.Min,Dt.Sec);
End;


function GetMsCount: int64;
var
  NanoSecsPast: __wasi_timestamp_t;
begin
  if __wasi_clock_time_get(__WASI_CLOCKID_REALTIME,1000000,@NanoSecsPast)=__WASI_ERRNO_SUCCESS then
    GetMsCount:=NanoSecsPast div 1000000
  else
    GetMsCount:=0;
end;


{******************************************************************************
                               --- Exec ---
******************************************************************************}

Procedure Exec (Const Path: PathStr; Const ComLine: ComStr);
Begin
End;


{******************************************************************************
                               --- Disk ---
******************************************************************************}

{
  The Diskfree and Disksize functions need a file on the specified drive, since this
  is required for the fpstatfs system call.
  These filenames are set in drivestr[0..26], and have been preset to :
   0 - '.'      (default drive - hence current dir is ok.)
   1 - '/fd0/.'  (floppy drive 1 - should be adapted to local system )
   2 - '/fd1/.'  (floppy drive 2 - should be adapted to local system )
   3 - '/'       (C: equivalent of dos is the root partition)
   4..26          (can be set by you're own applications)
  ! Use AddDisk() to Add new drives !
  They both return -1 when a failure occurs.
}
Const
  FixDriveStr : array[0..3] of PAnsiChar=(
    '.',
    '/fd0/.',
    '/fd1/.',
    '/.'
    );
const
  Drives   : byte = 4;
var
  DriveStr : array[4..26] of PAnsiChar;

Function AddDisk(const path:string) : byte;
begin
{  if not (DriveStr[Drives]=nil) then
   FreeMem(DriveStr[Drives]);
  GetMem(DriveStr[Drives],length(Path)+1);
  StrPCopy(DriveStr[Drives],path);
  AddDisk:=Drives;
  inc(Drives);
  if Drives>26 then
    Drives:=4;}
end;



Function DiskFree(Drive: Byte): int64;
{var
  fs : tstatfs;}
Begin
{  if ((Drive<4) and (not (fixdrivestr[Drive]=nil)) and (fpStatFS(fixdrivestr[drive],@fs)<>-1)) or
     ((not (drivestr[Drive]=nil)) and (fpStatFS(drivestr[drive],@fs)<>-1)) then
   Diskfree:=int64(fs.bavail)*int64(fs.bsize)
  else
   Diskfree:=-1;}
End;



Function DiskSize(Drive: Byte): int64;
{var
  fs : tstatfs;}
Begin
{  if ((Drive<4) and (not (fixdrivestr[Drive]=nil)) and (fpStatFS(fixdrivestr[drive],@fs)<>-1)) or
     ((not (drivestr[Drive]=nil)) and (fpStatFS(drivestr[drive],@fs)<>-1)) then
   DiskSize:=int64(fs.blocks)*int64(fs.bsize)
  else
   DiskSize:=-1;}
End;



Procedure FreeDriveStr;
{var
  i: longint;}
begin
{  for i:=low(drivestr) to high(drivestr) do
    if assigned(drivestr[i]) then
      begin
        freemem(drivestr[i]);
        drivestr[i]:=nil;
      end;}
end;

{******************************************************************************
                       --- Findfirst FindNext ---
******************************************************************************}


procedure SearchRec2WasiSearchRec(const i: SearchRec; var o: TWasiSearchRec);
var
  DT: DateTime;
begin
  FillChar(o,SizeOf(o),0);
  o.SearchPos:=i.SearchPos;
  o.SearchNum:=i.SearchNum;
  o.DirFD:=i.DirFD;
  o.SearchType:=i.SearchType;
  o.SearchAttr:=i.SearchAttr;
  o.Attr:=i.Attr;
  UnpackTime(i.Time,DT);
  o.Time:=DTToWasiDate(DT);
  o.Size:=i.Size;
  o.Name:=i.Name;
  o.SearchSpec:=i.SearchSpec;
  o.NamePos:=i.NamePos;
end;


procedure WasiSearchRec2SearchRec(const i: TWasiSearchRec; var o: SearchRec);
var
  DT: DateTime;
begin
  FillChar(o,SizeOf(o),0);
  o.SearchPos:=i.SearchPos;
  o.SearchNum:=i.SearchNum;
  o.DirFD:=i.DirFD;
  o.SearchType:=i.SearchType;
  o.SearchAttr:=i.SearchAttr;
  o.Attr:=i.Attr;
  WasiDateToDt(i.Time,DT);
  PackTime(DT,o.Time);
  o.Size:=i.Size;
  o.Name:=i.Name;
  o.SearchSpec:=i.SearchSpec;
  o.NamePos:=i.NamePos;
end;


Procedure FindClose(Var f: SearchRec);
var
  wf: TWasiSearchRec;
Begin
  SearchRec2WasiSearchRec(f,wf);
  WasiFindClose(wf);
  WasiSearchRec2SearchRec(wf,f);
End;


Procedure FindNext(Var f: SearchRec);
var
  wf: TWasiSearchRec;
Begin
  SearchRec2WasiSearchRec(f,wf);
  doserror:=WasiFindNext(wf);
  WasiSearchRec2SearchRec(wf,f);
End;


Procedure FindFirst(Const Path: PathStr; Attr: Word; Var f: SearchRec);
var
  wf: TWasiSearchRec;
Begin
  SearchRec2WasiSearchRec(f,wf);
  doserror:=WasiFindFirst(Path,Attr,wf);
  WasiSearchRec2SearchRec(wf,f);
End;


{******************************************************************************
                               --- File ---
******************************************************************************}

Function FSearch(path: pathstr; dirlist: shortstring): pathstr;
var
  p1     : longint;
  s      : searchrec;
  newdir : pathstr;
begin
  { No wildcards allowed in these things }
  if (pos('?',path)<>0) or (pos('*',path)<>0) then
  begin
    fsearch:='';
    exit;
  end;
  { check if the file specified exists }
  findfirst(path,anyfile and not(directory),s);
  if doserror=0 then
    begin
     findclose(s);
     fsearch:=path;
     exit;
    end;
  findclose(s);
  //{ allow slash as backslash }
  //DoDirSeparators(dirlist);
 repeat
   p1:=pos(';',dirlist);
   if p1<>0 then
    begin
      newdir:=copy(dirlist,1,p1-1);
      delete(dirlist,1,p1);
    end
   else
    begin
      newdir:=dirlist;
      dirlist:='';
    end;
   if (newdir<>'') and (not (newdir[length(newdir)] in (AllowDirectorySeparators+[':']))) then
    newdir:=newdir+DirectorySeparator;
   findfirst(newdir+path,anyfile and not(directory),s);
   if doserror=0 then
    newdir:=newdir+path
   else
    newdir:='';
   findclose(s);
 until (dirlist='') or (newdir<>'');
 fsearch:=newdir;
end;

Procedure GetFAttr(var f; var attr : word);
Var
  pr: RawByteString;
  fd: __wasi_fd_t;
  Info: __wasi_filestat_t;
Begin
  DosError:=0;
  Attr:=0;
  if ConvertToFdRelativePath(textrec(f).name,fd,pr)<>0 then
    begin
      DosError:=3;
      exit;
    end;
  if __wasi_path_filestat_get(fd,__WASI_LOOKUPFLAGS_SYMLINK_FOLLOW,PAnsiChar(pr),length(pr),@Info)<>__WASI_ERRNO_SUCCESS then
    begin
      DosError:=3;
      exit;
    end;
  if Info.filetype=__WASI_FILETYPE_DIRECTORY then
    Attr:=$10;
  if filerec(f).name[0]='.' then
    Attr:=Attr or $2;
end;

Procedure getftime (var f; var time : longint);
Var
  res: __wasi_errno_t;
  Info: __wasi_filestat_t;
  DT: DateTime;
Begin
  doserror:=0;
  res:=__wasi_fd_filestat_get(filerec(f).handle,@Info);
  if res<>__WASI_ERRNO_SUCCESS then
   begin
     Time:=0;
     case res of
       __WASI_ERRNO_ACCES,
       __WASI_ERRNO_NOTCAPABLE:
         doserror:=5;
       else
         doserror:=6;
     end;
     exit
   end
  else
   WasiDateToDt(Info.mtim,DT);
  PackTime(DT,Time);
End;

Procedure setftime(var f; time : longint);
Var
  DT: DateTime;
  modtime: UInt64;
  pr: RawByteString;
  fd: __wasi_fd_t;
Begin
  doserror:=0;
  UnPackTime(Time,DT);
  modtime:=DTToWasiDate(DT);
  if ConvertToFdRelativePath(textrec(f).name,fd,pr)<>0 then
    begin
      doserror:=3;
      exit;
    end;
  if __wasi_path_filestat_set_times(fd,0,PAnsiChar(pr),length(pr),0,modtime,
     __WASI_FSTFLAGS_MTIM or __WASI_FSTFLAGS_ATIM_NOW)<>__WASI_ERRNO_SUCCESS then
    doserror:=3;
End;

{******************************************************************************
                             --- Environment ---
******************************************************************************}

Function EnvCount: Longint;
var
  envcnt : longint;
  p      : PPAnsiChar;
Begin
  envcnt:=0;
  p:=envp;      {defined in system}
  if p<>nil then
    while p^<>nil do
      begin
        inc(envcnt);
        inc(p);
      end;
  EnvCount := envcnt
End;


Function EnvStr (Index: longint): ShortString;
Var
  i : longint;
  p : PPAnsiChar;
Begin
  if (Index <= 0) or (envp=nil) then
    envstr:=''
  else
    begin
      p:=envp;      {defined in system}
      i:=1;
      while (i<Index) and (p^<>nil) do
        begin
          inc(i);
          inc(p);
        end;
      if p^=nil then
        envstr:=''
      else
        envstr:=strpas(p^)
    end;
end;


Function GetEnv(EnvVar: ShortString): ShortString;
var
  hp : PPAnsiChar;
  hs : string;
  eqpos : longint;
Begin
  getenv:='';
  hp:=envp;
  if hp<>nil then
    while assigned(hp^) do
      begin
        hs:=strpas(hp^);
        eqpos:=pos('=',hs);
        if copy(hs,1,eqpos-1)=envvar then
          begin
            getenv:=copy(hs,eqpos+1,length(hs)-eqpos);
            break;
          end;
        inc(hp);
      end;
End;


Procedure setfattr (var f;attr : word);
Begin
  {! No WASI equivalent !}
  { Fail for setting VolumeId }
  if (attr and VolumeID)<>0 then
   doserror:=5;
End;



{******************************************************************************
                            --- Initialization ---
******************************************************************************}

//Finalization
//  FreeDriveStr;
End.
