#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <termio.h>
#include <fcntl.h>
#include <osfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#pragma pack(2)
#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <exec/ports.h>
#include <exec/libraries.h>
#include <exec/tasks.h>
#include <exec/execbase.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#pragma pack()

#include "arun.h"

#define TIME_OFFSET 252460800 /* Where AmigaDOS starts its times */


#define NFUNCS 400
struct doslib {
    struct func vectors[NFUNCS];
    struct DosLibrary lib;
} doslib;
struct DosLibrary *const DOSBase = &doslib.lib;

struct Process CurrentProcess;
struct CommandLineInterface CLI;

extern void SDOSBase_Execute(void);
extern void SDOSBase_Examine(void);
extern void SDOSBase_Lock(void);
extern void SDOSBase_UnLock(void);
extern void SDOSBase_Open(void);
extern void SDOSBase_Close(void);
extern void SDOSBase_Read(void);
extern void SDOSBase_Write(void);
extern void SDOSBase_Seek(void);
extern void SDOSBase_Input(void);
extern void SDOSBase_Output(void);
extern void SDOSBase_CurrentDir(void);
extern void SDOSBase_DeleteFile(void);
extern void SDOSBase_IoErr(void);
extern void SDOSBase_IsInteractive(void);
extern void SDOSBase_DateStamp(void);
static struct libinit { void (*func)(); int offset; } libinit[] =
{
    { SDOSBase_Execute, -0xde, },
    { SDOSBase_Examine, -0x66, },
    { SDOSBase_Lock, -0x54, },
    { SDOSBase_UnLock, -0x5a, },
    { SDOSBase_Open, -0x1e, },
    { SDOSBase_Close, -0x24, },
    { SDOSBase_Read, -0x2a, },
    { SDOSBase_Write, -0x30, },
    { SDOSBase_Seek, -0x42, },
    { SDOSBase_Input, -0x36, },
    { SDOSBase_Output, -0x3c, },
    { SDOSBase_CurrentDir, -0x7e, },
    { SDOSBase_DeleteFile, -0x48, },
    { SDOSBase_IoErr, -0x84, },
    { SDOSBase_IsInteractive, -0xd8, },
    { SDOSBase_DateStamp, -0xc0, },
    { 0, 0, },
};

struct RootNode RootNode;
struct DosInfo DosInfo;
struct FileHandle standardinputH, standardoutputH, erroroutputH;
struct FileLock erroroutputL, initialdirL;
ULONG CurrentDir;

void dos_init(void)
{
    struct libinit *p2;
    struct func *p;
    int i;

    DOSBase->dl_lib.lib_Node.ln_Name = DOSNAME;
    DOSBase->dl_Root = (APTR)&RootNode;
    RootNode.rn_Info = CTOB(&DosInfo);

    standardinputH.fh_Arg1 = 0;
    standardinputH.fh_Arg2 = 1;
    standardoutputH.fh_Arg1 = 1;
    standardoutputH.fh_Arg2 = 1;
    erroroutputH.fh_Arg1 = 2;
    erroroutputH.fh_Arg2 = 1;
    erroroutputL.fl_Key = 2;
    initialdirL.fl_Key = open(".", O_RDONLY);
    CurrentDir = CTOB(&initialdirL);

    /* First, fill all vectors with "unsupp" calls */
    p = (struct func *)DOSBase;
    for ( i=0 ; i<NFUNCS ; ++i )
    {
	--p;
	p->opcode = 0x4eb9;	/* jsr N.l */
	p->addr = unsupp;
    }

    /* Now, fill in the supported vectors with the real jumps */
    for ( p2=libinit ; p2->func ; ++p2 )
    {
	p = (struct func *)((char *)DOSBase + p2->offset);
	p->opcode = 0x4ef9;	/* jmp N.l */
	p->addr = p2->func;
    }

    CurrentProcess.pr_Result2 = 1;	/* IoErr -- yuck */

    verbose("DOSBase = 0x%x", DOSBase);

    CurrentProcess.pr_Task.tc_Node.ln_Name = "FakeProcess";
    CurrentProcess.pr_CLI = CTOB(&CLI);
    ExecBase->ThisTask = &CurrentProcess.pr_Task;
    verbose("&CurrentProcess = 0x%x (&MsgPort=0x%x)",
	    &CurrentProcess, &CurrentProcess.pr_MsgPort);
    verbose("&CurrentProcess.pr_CLI = 0x%x (offset 0x%x)",
	    &CurrentProcess.pr_CLI,
	    (char *)&CurrentProcess.pr_CLI - (char *)&CurrentProcess);
}


#ifdef INTERNAL_EXECUTE
static char *pathcat(const char *dir, const char *file)
{
    char *p;
    static char *buf;
    static unsigned int buflen;

    while (strlen(dir)+strlen(file)+2 > buflen)
	buf = buf ? realloc(buf, buflen+=50) : malloc(buflen+=50);

    if (buf)
    {
	p=buf;
	while (*p++ = *dir++)
	    ;
	--p;
	if (p>buf && p[-1] != '/')
	    *p++ = '/';
	strcpy(p, file);
    }

    return buf;
}

static FILE *pathopen(const char *file)
{
    FILE *fp;
    char *p, *path;

    if (fp = fopen(file, "r"))
	return fp;

    if (!(path = getenv("PATH")))
	return 0;
    if (!(path = strdup(path)))
	return 0;

    for ( p=strtok(path, ":") ; p ; p=strtok((char *)0, ":") )
    {
	char *name = pathcat(p, file);
	if (name && (fp=fopen(name, "r")))
	{
	    free(path);
	    return fp;
	}
    }

    free(path);
    return 0;
}
#endif


ULONG DOSBase_Execute(char *command, ULONG input, ULONG output)
{
#ifdef INTERNAL_EXECUTE
    unsigned char *p;
    int n;
    ULONG savecmd;
    ULONG *seg;
    FILE *fp;

    n = strcspn(command, " \t");
    p = malloc(n + 2);
    if (!p)
	return -1;
    p[0] = n;
    strncpy((char *)p+1, command, n);
    p[n+1] = 0;

    if ((fp = pathopen((char *)p+1)) &&
	(seg = adosload(fp, (char *)p+1, 0)))
    {
	(void)fclose(fp);
	savecmd = CLI.cli_CommandName;
	CLI.cli_CommandName = CTOB(p);

	verbose("Execute(%s) (internal)", command);
	runseg(seg, (unsigned char *)command+n, strlen(command+n));

	CLI.cli_CommandName = savecmd;
    }
    else
    {
	verbose("Execute(%s) (external)", command);
	system(command);
    }

    free(p);
#else
    verbose("Execute(%s)", command);
    system(command);
#endif
}


static void stat_to_fib(struct FileInfoBlock *fibp, struct stat *statp)
{
    long temp;
    struct tm *tp;

    memset(fibp, 0, sizeof *fibp);
    fibp->fib_DiskKey = statp->st_ino;
    fibp->fib_DirEntryType = ((statp->st_mode&S_IFMT)==S_IFDIR) ? 1 : -1;
    fibp->fib_FileName[0] = '.';
    if (!(statp->st_mode&S_IREAD))
	fibp->fib_Protection |= FIBF_READ;
    if (!(statp->st_mode&S_IWRITE))
	fibp->fib_Protection |= FIBF_WRITE;
    if (!(statp->st_mode&S_IEXEC))
	fibp->fib_Protection |= FIBF_EXECUTE;
    fibp->fib_Protection |= (statp->st_uid&0xff)<<24;
    fibp->fib_Protection |= (statp->st_gid&0xff)<<16;
    fibp->fib_Size = statp->st_size;
    fibp->fib_NumBlocks = statp->st_blocks;

    temp = statp->st_mtime;
    tp = localtime(&temp);
    if (tp->tm_isdst)
	temp += 60*60;
    temp -= TIME_OFFSET;
    temp -= timezone;

    fibp->fib_Date.ds_Days = temp / (60*60*24);
    temp %= (60*60*24);
    fibp->fib_Date.ds_Minute = temp / 60;
    fibp->fib_Date.ds_Tick = (temp%60) * TICKS_PER_SECOND;
}


ULONG DOSBase_Examine(ULONG lock, struct FileInfoBlock *fibp)
{
    struct stat Stat;
    struct FileLock *lp;

    verbose("Examine(0x%x, 0x%x)", lock);
    lp = (struct FileLock *)BTOC(lock);
    if (fstat(lp->fl_Key, &Stat))
	return 0;

    stat_to_fib(fibp, &Stat);
    return 1;
}


ULONG DOSBase_Lock(char *fn, ULONG mode)
{
    int fd;
    struct FileLock *lp;

    verbose("Lock(%s, 0x%x)", fn, mode);
    if (!strcmp(fn, "*"))
	return CTOB(&erroroutputL);
    if (!fn[0])
	fn = ".";

    if ((fd=open(fn, O_RDONLY)) < 0)
	return 0;

    lp = malloc(sizeof *lp);
    if (!lp)
    {
	close(fd);
	return 0;
    }

    lp->fl_Link = lp->fl_Access = lp->fl_Volume = 0;
    lp->fl_Task = (struct MsgPort *)0;
    lp->fl_Key = fd;

    return CTOB(lp);
}

void DOSBase_UnLock(ULONG fl)
{
    struct FileLock *lp;

    verbose("UnLock(%8x)", fl);
    if (fl)
    {
	lp = (struct FileLock *)BTOC(fl);
	close(lp->fl_Key);
	free(lp);
    }
}

ULONG DOSBase_Open(char *fn, ULONG mode)
{
    int fd, umode;
    struct FileHandle *fp;

    verbose("Open(%s, 0x%x)", fn, mode);
    if (!strcmp(fn, "*"))
	return CTOB(&erroroutputH);
    if (!fn[0])
	fn = ".";

    switch (mode)
    {
    case MODE_OLDFILE:
	umode = O_RDWR;
	break;
    case MODE_NEWFILE:
	umode = O_RDWR|O_CREAT|O_TRUNC;
	break;
    default:
	return 0;
    }

    if ((fd=open(fn, umode, 0666)) < 0)
	return 0;

    fp = malloc(sizeof *fp);
    if (!fp)
    {
	close(fd);
	return 0;
    }

    fp->fh_Buf = fp->fh_Pos = fp->fh_End = 0;
    fp->fh_Func1 = fp->fh_Func2 = fp->fh_Func3 = 0;
    fp->fh_Arg1 = fd;
    fp->fh_Arg2 = 0;

    return CTOB(fp);
}

void DOSBase_Close(ULONG fh)
{
    struct FileHandle *fp;

    verbose("Close(0x%x)", fh);
    fp = (struct FileHandle *)BTOC(fh);
    if (!fp->fh_Arg2)
    {
	close(fp->fh_Arg1);
	free(fp);
    }
}

ULONG DOSBase_Read(ULONG fh, void *buf, ULONG nbytes)
{
    struct FileHandle *fp;

    fp = (struct FileHandle *)BTOC(fh);
    verbose("Read(0x%x, 0x%x, 0x%x)", fh, buf, nbytes);
    return read(fp->fh_Arg1, buf, nbytes);
}

ULONG DOSBase_Write(ULONG fh, void *buf, ULONG nbytes)
{
    struct FileHandle *fp;

    fp = (struct FileHandle *)BTOC(fh);
    verbose("Write(0x%x, 0x%x, 0x%x)", fh, buf, nbytes);
    return write(fp->fh_Arg1, buf, nbytes);
}

ULONG DOSBase_Seek(ULONG fh, LONG pos, ULONG mode)
{
    struct FileHandle *fp;
    ULONG oldpos;

    fp = (struct FileHandle *)BTOC(fh);
    verbose("Seek(0x%x, 0x%x, 0x%x)", fh, pos, mode);
    switch (mode)
    {
    case OFFSET_BEGINNING:
	oldpos = lseek(fp->fh_Arg1, 0L, 1);
	if (oldpos != -1L)
	    if (lseek(fp->fh_Arg1, pos, 0) == -1L)
		return -1L;
	return oldpos;
    case OFFSET_CURRENT:
	oldpos = lseek(fp->fh_Arg1, pos, 1);
	if (oldpos != -1L)
	    oldpos -= pos;
	return oldpos;
    case OFFSET_END:
	oldpos = lseek(fp->fh_Arg1, 0L, 1);
	if (oldpos != -1L)
	    if (lseek(fp->fh_Arg1, pos, 2) == -1L)
		return -1L;
	return oldpos;
    default:
	return -1;
    }
}

ULONG DOSBase_Input(void)
{
    verbose("Input()");
    return CTOB(&standardinputH);
}

ULONG DOSBase_Output(void)
{
    verbose("Output()");
    return CTOB(&standardoutputH);
}

ULONG DOSBase_CurrentDir(ULONG lock)
{
    struct FileLock *lp;
    ULONG prev;

    verbose("CurrentDir(0x%x)", lock);
    lp = (struct FileLock *)BTOC(lock);
    if (fchdir(lock?lp->fl_Key:initialdirL.fl_Key))
	panic("fchdir failed");
    prev = CurrentDir;
    CurrentDir = lock;
    return prev;
}

ULONG DOSBase_DeleteFile(char *fn)
{
    verbose("DeleteFile(%s)", fn);
    return unlink(fn) == 0;
}

ULONG DOSBase_IoErr(void)
{
    verbose("IoErr()");
    return CurrentProcess.pr_Result2;
}

ULONG DOSBase_IsInteractive(ULONG fh)
{
    struct FileHandle *fp;
    struct termio termio;

    fp = (struct FileHandle *)BTOC(fh);
    verbose("IsInteractive(%8x)", fh);
    return ioctl(fp->fh_Arg1, TCGETA, &termio) == 0;
}

ULONG *DOSBase_DateStamp(ULONG *v)
{
    extern long time(long *);
    long temp;
    struct tm *tp;

    verbose("DateStamp(0x%x)", v);
    (void)time(&temp);
    tp = localtime(&temp);
    if (tp->tm_isdst)
	temp += 60*60;
    temp -= TIME_OFFSET;
    temp -= timezone;

    v[0] = temp / (60*60*24);
    temp %= (60*60*24);
    v[1] = temp / 60;
    v[2] = (temp%60) * TICKS_PER_SECOND;

    return v;
}
