/*
 * symbio stdio/libc implementation for SDCC with SymbosMake
 * Cut down and consolidated for Zym
 *
 * This program 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
 * 2 of the License, or (at your option) any later version.
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "..\sdk\symbos.h"
#include "..\sdk\symbos_system.h"
#include "..\sdk\symbos_desktop.h"
#include "..\sdk\symbos_device.h"
#include "..\sdk\symbos_kernel.h"
#include "..\sdk\symbos_memory.h"
#include "..\sdk\symbos_shell.h"
#include "..\sdk\symbos_file.h"
#include "resource.h"

#define MSC_SHL_PTHADD 69
#define MSR_SHL_PTHADD 197

#ifndef SYMBIOBUFLEN
#define SYMBIOBUFLEN 257
#endif // SYMBIOBUFLEN

typedef unsigned long time_t;

unsigned char _termpid = 0;
unsigned char _termwidth = 80;
unsigned char _termheight = 25;
unsigned char _procid = 0;
unsigned char _reg_1, _reg_2, _reg_3, _reg_4;
unsigned int _reg2_1, _reg2_2, _reg2_3, _reg2_4;
unsigned long _reg4_1;

int argc;
char* argv[32];

// TRANSFER AREA DATA
#include "..\sdk\symbos_transfer_area_start.h"
unsigned char computerType;
char _symbio_buf[SYMBIOBUFLEN];
char _symbio_msg[14];
#include "..\sdk\symbos_transfer_area_end.h"

void symbos_exit() {
	Sys_Program_End(GET_APPLICATION_ID());
	if (_termpid != 0) Shell_Exit(_termpid, 0);
	while (1) Multitasking_SoftInterrupt();
}

void symbos_coninit() {
    unsigned int i;
    unsigned char in_quotes;
    char* command;

    // parse command-line options
	command = Shell_StartParam(CODEBASE);
	argc = 0;
	argv[0] = command;
	in_quotes = 0;
	for (i = 0; command[i] != 0; i++) {
	    if (command[i] == 34)
            in_quotes = !in_quotes;
	    if (command[i] == 32 && !in_quotes) {
            command[i] = 0;
            if (command[i+1] == 37 && command[i+2] == 115 && command[i+3] == 112) {
                _termpid    = (command[i+4]-48)*10 + (command[i+5]-48);
                _termwidth  = (command[i+6]-48)*10 + (command[i+7]-48);
                _termheight = (command[i+8]-48)*10 + (command[i+9]-48);
                break;
            } else {
                argc++; // note that the final shell parameter is not written to argv[] for compatibility.
                argv[argc] = command + i + 1;
            }
	    }
	}
	argc++;
	_procid = GET_MAIN_PROC_ID();
	if (_termpid == 0)
        symbos_exit();
}

// Borrowed from symbos_lib-SymShell.asm, with minor tweaks
void Shell_DoCommand(unsigned char command, unsigned int DE, unsigned int HL, unsigned char A, unsigned char response) {
    _reg_1 = command;
    _reg2_1 = DE;
    _reg2_2 = HL;
    _reg_2 = A;
    _reg_3 = response;
    __asm
        push ix
        ld a, (__reg_1)
        ld c, a
        ld de, (__reg2_1)
        ld hl, (__reg2_2)
        ld a, (__reg_3)
        ld b, a
        ld a, (__reg_2)

        ;SyShell_DoCommand
        push bc
        ld b,a
        call 0001$
        pop bc
        dec ixl
        jr NZ, 0005$  ;error sending message - crash out rather than waiting for a response
0004$:
        push bc
        call 0002$
        pop bc
        cp b
        jr nz,0006$
        ld a,3(iy)     ;Error state
        ld c,1(iy)     ;EOF (0=no eof)
        cp #0x1
        ccf             ;CF=0 no error
        inc c
        dec c           ;ZF=0 EOF
0005$:
        pop ix
        ret
0006$:
        push bc         ;wrong message (from another event) -> re-send
        ld a,(__termpid)
        .db #0xdd
        ld l,a
        ld a,(__procid)
        .db #0xdd
        ld h,a
        rst #0x10
        rst #0x30
        pop bc
        jr 0004$

0001$:  ;SyShell_SendMessage
        ld iy,#__symbio_msg
        ld 0(iy),c
        ld (__symbio_msg+1),de
        ld (__symbio_msg+3),hl
        ld 5(iy),b
        ld a,(__termpid)
        .db #0xdd
        ld h,a
        ld a,(__procid)
        .db #0xdd
        ld l,a
        rst #0x10
        ret

0002$:  ;SyShell_WaitMessage
        ld iy,#__symbio_msg
0003$:
        ld a,(__termpid)
        .db #0xdd
        ld h,a
        ld a,(__procid)
        .db #0xdd
        ld l,a
        rst #0x08             ;wait for a SymShell message
        .db #0xdd
        dec l
        jr nz,0003$
        ld a,0(iy)
        ret
    __endasm;
}

// The SymbosMake API function Shell_CharOutput will occasionally leave the process stuck on Idle, so we redefine it here.
#pragma save
#pragma disable_warning 85
void Shell_CharOutput(unsigned char shellPID, unsigned char channel, unsigned char byte) {
    Shell_DoCommand(MSC_SHL_CHROUT, (unsigned int)byte << 8 | channel, 0, 0, MSR_SHL_CHROUT);
}
#pragma restore

// The SymbosMake API function Shell_StringOutput will occasionally leave the process stuck on Idle, so we redefine it here.
#pragma save
#pragma disable_warning 85
void Shell_StringOutput(unsigned char shellPID, unsigned char channel, unsigned char bank, char *str, int len) {
    int truelen = strlen(str);
    if (truelen > 254)
        return; // not well-formed, passing this on would be dangerous, so we fail
    if ((unsigned int)&str[0] != (unsigned int)&_symbio_buf[0]) // ensure that all output is in the Transfer area (this shouldn't be strictly necessary but it seems to reduce the idle bug?)
        memcpy(_symbio_buf, str, strlen(str) + 1);
    Shell_DoCommand(MSC_SHL_STROUT, (unsigned int)bank << 8 | channel, (unsigned int)&_symbio_buf[0], (unsigned char)truelen, MSR_SHL_STROUT);
}
#pragma restore

int putchar(int byte) {
    if (_termpid) {
        Shell_CharOutput(_termpid, 0, byte);
        return byte;
    } else {
        return EOF;
    }
}

int getch() {
    if (_termpid) {
        putchar(0x03); // switch cursor on
        int c = Shell_CharInput(_termpid, 0);
        if (c == '\r') {
            c = '\n'; // for compatibility
            putchar('\r');
        }
        putchar(0x02); // switch cursor off
        return c;
    } else {
        return EOF;
    }
}

// The SymbosMake API function Shell_StringInput is (apparently) broken, so we redefine it here.
int Shell_StringInput(unsigned char shellPID, unsigned char channel, unsigned char bank, char *str) {
    Shell_DoCommand(MSC_SHL_STRINP, (unsigned int)bank << 8 | channel, (unsigned int)&_symbio_buf[0], 0, MSR_SHL_STRINP);
    strcpy(str, _symbio_buf);
    return 0;
}

char* gets(char* str) {
    Shell_StringInput(_termpid, 0, GET_APPLICATION_BANK(), str);
    return str;
}

int puts(char* str) {
    Shell_StringOutput(_termpid, 0, GET_APPLICATION_BANK(), str, 1);
    putchar('\r');
    putchar('\n');
    return 1;
}

/* The default Z80 stdio uses the (appropriately-named) printf_large.c; this
   minimal implementation shaves off ~2 kb. Supported formatting codes:
     %i   signed 16-bit int (decimal)
     %s   string
     %X   unsigned 32-bit int (hexadecimal)
     %0x  prefix, x = pad to this many digits with leading zeros */
int _format_string(char* buffer, const char *format, va_list ap) {
    char* old_buffer = buffer;
    char leading_zeros = 0;
    char c, i;
    char* ptr;
    while (c = *format++) {
        if (c == '%') {
get_format_insert:
            c = *format++;
            switch (c) {
            case '0':
                leading_zeros = *format++ - '0';
                goto get_format_insert;
            case '%':
                *buffer++ = c;
                continue;
            case 'i':
                __itoa(va_arg(ap, int), buffer, 10);
                break;
            case 'X':
                __ltoa(va_arg(ap, unsigned long), buffer, 16);
                break;
            case 's':
                ptr = va_arg(ap, char*);
                strcpy(buffer, ptr);
                break;
            }
            if (leading_zeros) {
                c = strlen(buffer);
                i = leading_zeros - c;
                while (c--)
                    *(buffer + c + i) = *(buffer + c);
                while (i--)
                    *(buffer + i) = '0';
                *(buffer + leading_zeros) = 0;
                leading_zeros = 0;
            }
            buffer += strlen(buffer);
        } else {
            *buffer++ = c;
        }
    }
    *buffer = 0;
    return buffer - old_buffer;
}

int vprintf(char *fmt, va_list args) {
    _symbio_buf[0] = 0;
    _format_string(_symbio_buf, fmt, args);
	Shell_StringOutput(_termpid, 0, GET_APPLICATION_BANK(), _symbio_buf, 1);
	return strlen(_symbio_buf);
}

int printf(char *fmt, ...) {
    int charswritten;
    va_list args;
    va_start(args, fmt);
    charswritten = vprintf(fmt, args);
    va_end(args);
	return charswritten;
}

int sprintf(char* buffer, char *fmt, ...) {
    int charswritten;
    va_list args;
    va_start(args, fmt);
    charswritten = _format_string(buffer, fmt, args);
    va_end(args);
	return charswritten;
}

unsigned long Multitasking_GetCounter() {
    __asm
        push ix
        ld hl, #0x8109
        rst #0x28
        ld (__reg4_1), ix
        ld (__reg4_1+2), iy
        pop ix
    __endasm;
    return _reg4_1;
}

int Shell_PathAdd(unsigned char shellPID, unsigned char bank, char *basepath, char *component, char* str) {
    _symbio_msg[0] = 0;
    while (_symbio_msg[0] != MSR_SHL_PTHADD) {
        _symbio_msg[0] = MSC_SHL_PTHADD;
        _symbio_msg[1] = (unsigned int)basepath & 255;
        _symbio_msg[2] = (unsigned int)basepath >> 8;
        _symbio_msg[3] = (unsigned int)component & 255;
        _symbio_msg[4] = (unsigned int)component >> 8;
        _symbio_msg[5] = (unsigned int)_symbio_buf & 255;
        _symbio_msg[6] = (unsigned int)_symbio_buf >> 8;
        _symbio_msg[7] = bank;
        Message_Send(GET_MAIN_PROC_ID(), shellPID, _symbio_msg);
        Message_Sleep_And_Receive(GET_MAIN_PROC_ID(), shellPID, _symbio_msg);
    }
    strcpy(str, _symbio_buf);
    return 0;
}
