/*****************************************************************************/
/*                                                                           */
/*                                paravirt.c                                 */
/*                                                                           */
/*                Paravirtualization for the sim65 6502 simulator            */
/*                                                                           */
/*                                                                           */
/*                                                                           */
/* (C) 2013-2013 Ullrich von Bassewitz                                       */
/*               Roemerstrasse 52                                            */
/*               D-70794 Filderstadt                                         */
/* EMail:        uz@cc65.org                                                 */
/*                                                                           */
/*                                                                           */
/* This software is provided 'as-is', without any expressed or implied       */
/* warranty.  In no event will the authors be held liable for any damages    */
/* arising from the use of this software.                                    */
/*                                                                           */
/* Permission is granted to anyone to use this software for any purpose,     */
/* including commercial applications, and to alter it and redistribute it    */
/* freely, subject to the following restrictions:                            */
/*                                                                           */
/* 1. The origin of this software must not be misrepresented; you must not   */
/*    claim that you wrote the original software. If you use this software   */
/*    in a product, an acknowledgment in the product documentation would be  */
/*    appreciated but is not required.                                       */
/* 2. Altered source versions must be plainly marked as such, and must not   */
/*    be misrepresented as being the original software.                      */
/* 3. This notice may not be removed or altered from any source              */
/*    distribution.                                                          */
/*                                                                           */
/*****************************************************************************/



#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#if defined(_WIN32)
#  define O_INITIAL O_BINARY
#else
#  define O_INITIAL 0
#endif
#if defined(_MSC_VER)
/* Microsoft compiler */
#  include <io.h>
#else
/* Anyone else */
#  include <unistd.h>
#endif
#ifndef S_IREAD
#  define S_IREAD  S_IRUSR
#endif
#ifndef S_IWRITE
#  define S_IWRITE S_IWUSR
#endif

/* common */
#include "cmdline.h"
#include "print.h"
#include "xmalloc.h"

/* sim65 */
#include "6502.h"
#include "error.h"
#include "memory.h"
#include "paravirt.h"



/*****************************************************************************/
/*                                   Data                                    */
/*****************************************************************************/



typedef void (*PVFunc) (CPURegs* Regs);

static unsigned ArgStart;
static unsigned char SPAddr;



/*****************************************************************************/
/*                                   Code                                    */
/*****************************************************************************/



static unsigned GetAX (CPURegs* Regs)
{
    return Regs->AC + (Regs->XR << 8);
}



static void SetAX (CPURegs* Regs, unsigned Val)
{
    Regs->AC = Val & 0xFF;
    Val >>= 8;
    Regs->XR = Val;
}



static unsigned char Pop (CPURegs* Regs)
{
    return MemReadByte (0x0100 + (++Regs->SP & 0xFF));
}



static unsigned PopParam (unsigned char Incr)
{
    unsigned SP = MemReadZPWord (SPAddr);
    unsigned Val = MemReadWord (SP);
    MemWriteWord (SPAddr, SP + Incr);
    return Val;
}



static void PVExit (CPURegs* Regs)
{
    Print (stderr, 1, "PVExit ($%02X)\n", Regs->AC);
    SimExit (Regs->AC); /* Error code in range 0-255. */
}



static void PVArgs (CPURegs* Regs)
{
    unsigned ArgC = ArgCount - ArgStart;
    unsigned ArgV = GetAX (Regs);
    unsigned SP   = MemReadZPWord (SPAddr);
    unsigned Args = SP - (ArgC + 1) * 2;

    Print (stderr, 2, "PVArgs ($%04X)\n", ArgV);

    MemWriteWord (ArgV, Args);

    SP = Args;
    while (ArgStart < ArgCount) {
        unsigned I = 0;
        const char* Arg = ArgVec[ArgStart++];
        SP -= strlen (Arg) + 1;
        do {
            MemWriteByte (SP + I, Arg[I]);
        }
        while (Arg[I++]);

        MemWriteWord (Args, SP);
        Args += 2;
    }
    MemWriteWord (Args, SPAddr);

    MemWriteWord (SPAddr, SP);
    SetAX (Regs, ArgC);
}

/* Match between standard POSIX whence and cc65 whence. */
static unsigned SEEK_MODE_MATCH[3] = {
  SEEK_CUR,
  SEEK_END,
  SEEK_SET
};

static void PVLseek (CPURegs* Regs)
{
    unsigned RetVal;

    unsigned Whence = GetAX (Regs);
    unsigned Offset = PopParam (4);
    unsigned FD     = PopParam (2);

    Print (stderr, 2, "PVLseek ($%04X, $%08X, $%04X (%d))\n",
           FD, Offset, Whence, SEEK_MODE_MATCH[Whence]);

    RetVal = lseek(FD, (off_t)Offset, SEEK_MODE_MATCH[Whence]);
    Print (stderr, 2, "PVLseek returned %04X\n", RetVal);

    SetAX (Regs, RetVal);
}



static void PVOpen (CPURegs* Regs)
{
    char Path[PV_PATH_SIZE];
    int OFlag = O_INITIAL;
    int OMode = 0;
    unsigned RetVal, I = 0;

    unsigned Mode  = PopParam (Regs->YR - 4);
    unsigned Flags = PopParam (2);
    unsigned Name  = PopParam (2);

    if (Regs->YR - 4 < 2) {
        /* If the caller didn't supply the mode
        ** argument, use a reasonable default.
        */
        Mode = 0x01 | 0x02;
    }

    do {
        if (!(Path[I] = MemReadByte ((Name + I) & 0xFFFF))) {
            break;
        }
        ++I;
        if (I >= PV_PATH_SIZE) {
            Error("PVOpen path too long at address $%04X",Name);
        }
    }
    while (1);

    Print (stderr, 2, "PVOpen (\"%s\", $%04X)\n", Path, Flags);

    switch (Flags & 0x03) {
        case 0x01:
            OFlag |= O_RDONLY;
            break;
        case 0x02:
            OFlag |= O_WRONLY;
            break;
        case 0x03:
            OFlag |= O_RDWR;
            break;
    }
    if (Flags & 0x10) {
        OFlag |= O_CREAT;
    }
    if (Flags & 0x20) {
        OFlag |= O_TRUNC;
    }
    if (Flags & 0x40) {
        OFlag |= O_APPEND;
    }
    if (Flags & 0x80) {
        OFlag |= O_EXCL;
    }

    if (Mode & 0x01) {
        OMode |= S_IREAD;
    }
    if (Mode & 0x02) {
        OMode |= S_IWRITE;
    }

    RetVal = open (Path, OFlag, OMode);

    SetAX (Regs, RetVal);
}



static void PVClose (CPURegs* Regs)
{
    unsigned RetVal;

    unsigned FD = GetAX (Regs);

    Print (stderr, 2, "PVClose ($%04X)\n", FD);

    if (FD != 0xFFFF) {
        RetVal = close (FD);
    } else {
        /* test/val/constexpr.c "abuses" close, expecting close(-1) to return -1.
        ** This behaviour is not the same on all target platforms.
        ** MSVC's close treats it as a fatal error instead and terminates.
        */
        RetVal = 0xFFFF;
    }

    SetAX (Regs, RetVal);
}



static void PVSysRemove (CPURegs* Regs)
{
    char Path[PV_PATH_SIZE];
    unsigned RetVal, I = 0;

    unsigned Name  = GetAX (Regs);

    Print (stderr, 2, "PVSysRemove ($%04X)\n", Name);

    do {
        if (!(Path[I] = MemReadByte ((Name + I) & 0xFFFF))) {
            break;
        }
        ++I;
        if (I >= PV_PATH_SIZE) {
            Error("PVSysRemove path too long at address $%04X", Name);
        }
    }
    while (1);

    Print (stderr, 2, "PVSysRemove (\"%s\")\n", Path);

    RetVal = remove (Path);

    SetAX (Regs, RetVal);
}



static void PVRead (CPURegs* Regs)
{
    unsigned char* Data;
    unsigned RetVal, I = 0;

    unsigned Count = GetAX (Regs);
    unsigned Buf   = PopParam (2);
    unsigned FD    = PopParam (2);

    Print (stderr, 2, "PVRead ($%04X, $%04X, $%04X)\n", FD, Buf, Count);

    Data = xmalloc (Count);

    RetVal = read (FD, Data, Count);

    if (RetVal != (unsigned) -1) {
        while (I < RetVal) {
            MemWriteByte (Buf++, Data[I++]);
        }
    }
    xfree (Data);

    SetAX (Regs, RetVal);
}



static void PVWrite (CPURegs* Regs)
{
    unsigned char* Data;
    unsigned RetVal, I = 0;

    unsigned Count = GetAX (Regs);
    unsigned Buf   = PopParam (2);
    unsigned FD    = PopParam (2);

    Print (stderr, 2, "PVWrite ($%04X, $%04X, $%04X)\n", FD, Buf, Count);

    Data = xmalloc (Count);
    while (I < Count) {
        Data[I++] = MemReadByte (Buf++);
    }

    RetVal = write (FD, Data, Count);

    xfree (Data);

    SetAX (Regs, RetVal);
}



static void PVOSMapErrno (CPURegs* Regs)
{
    unsigned err = GetAX(Regs);
    SetAX (Regs, err != 0 ? -1 : 0);
}



static const PVFunc Hooks[] = {
    PVLseek,
    PVSysRemove,
    PVOSMapErrno,
    PVOpen,
    PVClose,
    PVRead,
    PVWrite,
    PVArgs,
    PVExit,
};



void ParaVirtInit (unsigned aArgStart, unsigned char aSPAddr)
/* Initialize the paravirtualization subsystem */
{
    ArgStart = aArgStart;
    SPAddr = aSPAddr;
};



void ParaVirtHooks (CPURegs* Regs)
/* Potentially execute paravirtualization hooks */
{
    unsigned lo;

    /* Check for paravirtualization address range */
    if (Regs->PC <  PARAVIRT_BASE ||
        Regs->PC >= PARAVIRT_BASE + sizeof (Hooks) / sizeof (Hooks[0])) {
        return;
    }

    /* Call paravirtualization hook */
    Hooks[Regs->PC - PARAVIRT_BASE] (Regs);

    /* Simulate RTS */
    lo = Pop (Regs);
    Regs->PC = lo + (Pop (Regs) << 8) + 1;
}
