/*
 *  libcaca       Colour ASCII-Art library
 *  Copyright (c) 2006 Sam Hocevar <sam@hocevar.net>
 *                2009 Jean-Yves Lamoureux <jylam@lnxscene.org>
 *                All Rights Reserved
 *
 *  This library is free software. It comes without any warranty, to
 *  the extent permitted by applicable law. You can redistribute it
 *  and/or modify it under the terms of the Do What the Fuck You Want
 *  to Public License, Version 2, as published by Sam Hocevar. See
 *  http://www.wtfpl.net/ for more details.
 */

#include "config.h"
#include "caca_types.h"

#include "klibc.h"
#include "drivers/timer.h"
#include "kernel.h"


void htoa(unsigned int value, char s[]);

#define IS_DIGIT(x) (x>='0' && x<='9')
#define IS_ALPHA(x) (x>='A' && x<='z')
#define IS_UPPER(x) (x>='A' && x<='Z')
#define IS_LOWER(x) (x>='a' && x<='z')
#define UPPER(x) (IS_LOWER(x)?(x+('A'-'a')):x)
#define LOWER(x) (IS_UPPER(x)?(x-('a'-'A')):x)

/* Our default seed for random number generator */
static int seed = 0x68743284;

/* Our memory mapping */
static uint32_t *freemem = (uint32_t *) 0x00200000;
int kX = 0;
int kY = 0;

void scroll(void)
{
    unsigned char *video, *tmp;

    for (video = (unsigned char *)0xB8000; video < (unsigned char *)0xB8FA0;
         video++)
    {
        tmp = (unsigned char *)(video + 1 * 160);

        if (tmp < (unsigned char *)0xB8FA0)
            *video = *tmp;
        else
            *video = 0;
    }

    kY -= 1;
    if (kY < 0)
        kY = 0;
}

void putcar(unsigned char c)
{
    unsigned char *video;

    if (c == 10)
    {
        kX = 0;
        kY++;
    }
    else
    {
        video = (unsigned char *)(0xB8000 + 2 * kX + 160 * kY);
        *video = c;
        *(video + 1) = 0x07;

        kX++;
        if (kX > 79)
        {
            kX = 0;
            kY++;
        }
        if (kY >= 24)
        {
            scroll();
        }
    }
}

void print(char *str)
{
    char const *ptr = str;
    while (*ptr)
    {
        putcar(*ptr++);
    }
}

void clearscreen(void)
{
    int x, y;
    kX = 0;
    kY = 0;
    for (y = 0; y < 25; y++)
        for (x = 0; x < 80; x++)
        {
            putcar(' ');
        }
    kX = 0;
    kY = 0;
}

/* stdlib.h functions */
void *malloc(size_t size)
{
    uint32_t *p = freemem;
    if (!size)
        return NULL;
    size = (size + 0x7) / 4;
    *p = size;
    freemem += size + 1;
    return p + 1;
}

void free(void *ptr)
{
    return;
}

void *realloc(void *ptr, size_t size)
{
    uint32_t oldsize;
    void *p;

    if (!size)
        return NULL;

    if (!ptr)
        oldsize = 0;
    else
    {
        oldsize = ((uint32_t *) ptr)[-1];
        if (oldsize >= size)
            return ptr;
    }

    p = malloc(size);
    memcpy(p, ptr, oldsize);
    return p;
}

char *getenv(const char *name)
{
    return NULL;
}

int getpid(void)
{
    return 0x1337;
}

void srand(unsigned int s)
{
    seed = rand();
}

int time(void *dummy)
{
    return rand();
}

int rand(void)
{
    seed = (seed * 0x7f32ba17) ^ 0xf893a735;
    return seed % RAND_MAX;
}

int abs(int j)
{
    if (j < 0)
        return -j;
    return j;
}

void exit(int status)
{
    /* FIXME: reboot? */
    while (1);
}

int atexit(void (*function) (void))
{
    /* FIXME: register function */
    return 0;
}

/* string.h functions */
void *memset(void *s, int c, size_t n)
{
    uint8_t *ptr = s;

    while (n--)
        *ptr++ = c;

    return s;
}

void *memcpy(void *dest, const void *src, size_t n)
{
    uint8_t *destptr = dest;
    uint8_t const *srcptr = src;

    while (n--)
        *destptr++ = *srcptr++;

    return dest;
}

void *memmove(void *dest, const void *src, size_t n)
{
    memcpy(freemem, src, n);
    memcpy(dest, freemem, n);
    return dest;
}

size_t strlen(const char *s)
{
    int len = 0;

    while (*s++)
        len++;

    return len;
}

int strcmp(const char *s1, const char *s2)
{
    while (*s1 && *s1 == *s2)
    {
        s1++;
        s2++;
    }

    return (int)*s1 - (int)*s2;
}

int strcasecmp(const char *s1, const char *s2)
{
    while (*s1 && *s2 && UPPER(*s1) == UPPER(*s2))
    {
        s1++;
        s2++;
    }

    return (int)UPPER(*s1) - (int)UPPER(*s2);
}

int memcmp(const void *_s1, const void *_s2, size_t n)
{
    uint8_t const *s1 = _s1, *s2 = _s2;

    while (n--)
    {
        if (*s1 != *s2)
            return (int)*s1 - (int)*s2;
        s1++;
        s2++;
    }
    return 0;
}

char *strdup(const char *s)
{
    char *new;
    unsigned int len = strlen(s);

    new = malloc(len + 1);
    memcpy(new, s, len + 1);

    return new;
}

char *strchr(const char *s, int c)
{
    do
        if (*s == c)
            return (char *)(intptr_t) s;
    while (*s++);

    return NULL;
}

/* stdarg.h functions */
int vsnprintf(char *str, size_t size, const char *format, va_list ap)
{
    /* FIXME */
    return 0;
}

/* stdio.h functions */
FILE *fopen(const char *path, const char *mode)
{
    /* FIXME */
    return NULL;
}

int feof(FILE * stream)
{
    /* FIXME */
    return 0;
}

char *fgets(char *s, int size, FILE * stream)
{
    /* FIXME */
    return NULL;
}

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE * stream)
{
    return 0;
}

int fclose(FILE * fp)
{
    /* FIXME */
    return 0;
}

int printf(const char *fmt, ...)
{
    char str[200];
    char tmp[100];
    args_list args;
    args_start(args, fmt);

    char *s;
    int ptr = 0;
    int i = 0;

    for (; fmt[i]; ++i)
    {
        if ((fmt[i] != '%') && (fmt[i] != '\\'))
        {
            str[ptr++] = fmt[i];
            continue;
        }
        else if (fmt[i] == '\\')
        {
            switch (fmt[++i])
            {
            case 'a':
                str[ptr++] = '\a';
                break;
            case 'b':
                str[ptr++] = '\b';
                break;
            case 't':
                str[ptr++] = '\t';
                break;
            case 'n':
                str[ptr++] = '\n';
                break;
            case 'r':
                str[ptr++] = '\r';
                break;
            case '\\':
                str[ptr++] = '\\';
                break;
            }
            continue;
        }

        switch (fmt[++i])
        {
        case 's':
            s = (char *)args_next(args, char *);
            while (*s)
                str[ptr++] = *s++;
            break;
        case 'c':
            str[ptr++] = (char)args_next(args, int);
            break;
        case 'p':
        case 'x':
            htoa((unsigned long)args_next(args, unsigned long), tmp);
            memcpy(&str[ptr], tmp, strlen(tmp));
            ptr += strlen(tmp);
            break;
        case 'd':
            itoa((unsigned long)args_next(args, unsigned long), tmp);
            memcpy(&str[ptr], tmp, strlen(tmp));
            ptr += strlen(tmp);
            break;
        case '%':
            str[ptr++] = '%';
            break;
        default:
            str[ptr++] = fmt[i];
            break;
        }
    }

    str[ptr] = '\0';
    args_end(args);

    print(str);

    return 0;
}

int fprintf(FILE * stream, const char *format, ...)
{
    /* FIXME */
    return 0;
}

int fflush(FILE * stream)
{
    /* FIXME */
    return 0;
}

int sprintf(char *str, const char *fmt, ...)
{
    char tmp[100];
    args_list args;
    args_start(args, fmt);

    char *s;
    int ptr = 0;
    int i = 0;

    for (; fmt[i]; ++i)
    {
        if ((fmt[i] != '%') && (fmt[i] != '\\'))
        {
            str[ptr++] = fmt[i];
            continue;
        }
        else if (fmt[i] == '\\')
        {
            switch (fmt[++i])
            {
            case 'a':
                str[ptr++] = '\a';
                break;
            case 'b':
                str[ptr++] = '\b';
                break;
            case 't':
                str[ptr++] = '\t';
                break;
            case 'n':
                str[ptr++] = '\n';
                break;
            case 'r':
                str[ptr++] = '\r';
                break;
            case '\\':
                str[ptr++] = '\\';
                break;
            }
            continue;
        }

        switch (fmt[++i])
        {
        case 's':
            s = (char *)args_next(args, char *);
            while (*s)
                str[ptr++] = *s++;
            break;
        case 'c':
            str[ptr++] = (char)args_next(args, int);
            break;
        case 'p':
        case 'x':
            htoa((unsigned long)args_next(args, unsigned long), tmp);
            memcpy(&str[ptr], tmp, strlen(tmp));
            ptr += strlen(tmp);
            break;
        case 'd':
            itoa((unsigned long)args_next(args, unsigned long), tmp);
            memcpy(&str[ptr], tmp, strlen(tmp));
            ptr += strlen(tmp);
            break;
        case '%':
            str[ptr++] = '%';
            break;
        default:
            str[ptr++] = fmt[i];
            break;
        }
    }

    str[ptr] = '\0';
    args_end(args);

    return 0;
}

int sscanf(const char *str, const char *format, ...)
{
    /* FIXME */
    return 0;
}

/* unistd.h functions */
void usleep(unsigned long usec)
{
    u32 start = ticks;
    signed int diff = 0;

    while (1)
    {
        diff = (signed int)(ticks - start);
        if (diff >= (signed int)(usec / 20))
            break;
    }
}

void sleep(unsigned long sec)
{
    usleep(sec * 1000);
}


int gettimeofday(struct timeval *tv, struct timezone *tz)
{
    static int usec = 0;
    static int sec = 0;

    /* FIXME */
    usec += 10000;
    if (usec > 1000000)
    {
        sec++;
        usec -= 1000000;
    }

    tv->tv_sec = sec;
    tv->tv_usec = usec;

    return 0;
}

/* math.h functions */
double cos(double x)
{
    double ret = 0.0;
#ifdef HAVE_FSIN_FCOS
    asm volatile ("fcos":"=t" (ret):"0"(x));
#else
    double x2;
    double num = 1.0;
    double fact = 1.0;
    int i;

    x = x - ((double)(int)(x / (2 * M_PI))) * (2 * M_PI);
    x2 = x * x;

    /* cos(x) = 1/0! - x^2/2! + x^4/4! - x^6/6! ... */
    for (i = 0; i < 10; i++)
    {
        ret += num / fact;
        num *= -x2;
        fact *= (2 * i + 1) * (2 * i + 2);
    }
#endif
    return ret;
}

double sin(double x)
{
    double ret = 0.0;
#ifdef HAVE_FSIN_FCOS
    asm volatile ("fsin":"=t" (ret):"0"(x));
#else
    double x2;
    double num;
    double fact = 1.0;
    int i;

    x = x - ((double)(int)(x / (2 * M_PI))) * (2 * M_PI);
    x2 = x * x;
    num = x;

    /* sin(x) = x/1! - x^3/3! + x^5/5! - x^7/7! ... */
    for (i = 0; i < 10; i++)
    {
        ret += num / fact;
        num *= -x2;
        fact *= (2 * i + 2) * (2 * i + 3);
    }
#endif
    return ret;
}

double sqrt(double x)
{
    double ret = x;
    int i;

    /* This is Newton's method */
    for (i = 0; i < 10; i++)
        ret = (ret * ret + x) / (ret * 2.0);

    return ret;
}


/* reverse: reverse string s in place */
void reverse(char s[])
{
    int i, j;
    char c;

    for (i = 0, j = strlen(s) - 1; i < j; i++, j--)
    {
        c = s[i];
        s[i] = s[j];
        s[j] = c;
    }
}


/* itoa implementation, by Kernighan and Ritchie's The C Programming Language */
void itoa(int n, char s[])
{
    int i, sign;

    if ((sign = n) < 0)         /* record sign */
        n = -n;                 /* make n positive */
    i = 0;
    do
    {                           /* generate digits in reverse order */
        s[i++] = n % 10 + '0';  /* get next digit */
    }
    while ((n /= 10) > 0);      /* delete it */
    if (sign < 0)
        s[i++] = '-';
    s[i] = '\0';
    reverse(s);
}

void htoa(unsigned int value, char s[])
{
    int i = 8;
    int ptr = 0;
    while (i-- > 0)
    {
        s[ptr++] = "0123456789abcdef"[(value >> (i * 4)) & 0xf];
    }
    s[ptr] = 0;
}


/* errno.h stuff */
int errno = 0;
