/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2008 Dan Everton (safetydan)
 * Copyright (C) 2018 William Wilgus
 * String function implementations taken from dietlibc 0.31 (GPLv2 License)
 *
 * 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.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

#include "plugin.h"
#define _ROCKCONF_H_ /* Protect against unwanted include */
#include "lua.h"
#include "lib/pluginlib_actions.h"

extern long strtol(const char *nptr, char **endptr, int base);
extern const char *strpbrk_n(const char *s, int smax, const char *accept);

#if (CONFIG_PLATFORM & PLATFORM_NATIVE)
int errno = 0;
#endif

char *strerror(int errnum)
{
    (void) errnum;

    DEBUGF("strerror(%d)\n", errnum);

    return NULL;
}

/* splash string and allow user to scroll around
 * provides rudimentary text reflow
 * timeout is disabled on user interaction
 * returns the action that caused quit
 * [ACTION_NONE, ACTION_STD_CANCEL, ACTION_STD_OK]
 * !ACTION_NONE (only on initial timeout)!
 * TIMEOUT can be TIMEOUT_BLOCK or time in ticks
*/
int splash_scroller(int timeout, const char* str)
{
    if (!str)
        str = "[nil]";
    int w, ch_w, ch_h;
    rb->lcd_getstringsize("W", &ch_w, &ch_h);

    const int max_w = LCD_WIDTH - (ch_w * 2);
    const int max_lines = LCD_HEIGHT / ch_h - 1;
    const int wrap_thresh = (LCD_WIDTH / 3);
    const char *ch;
    const char *brk;

    const int max_ch = (LCD_WIDTH / ch_w - 1) * 2;
    char line[max_ch + 2]; /* display buffer */
    const char break_chars[] = "@#$%^&*+-{}[]()/\\|<>:;.,? _\n\r\t";

    int linepos, curline, linesdisp, realline, chars_next_break;
    int action = ACTION_NONE;
    int firstline = 0;
    int cycles = 2; /* get action timeout returns immediately on first call */

    while (cycles > 0)
    {
        /* walk whole buffer every refresh, only display relevant portion */
        rb->lcd_clear_display();
        curline = 0;
        linepos = 0;
        linesdisp = 0;
        ch = str;
        for (; *ch && linepos < max_ch; ch++)
        {
            if (ch[0] == '\t')
            {
                line[linepos++] = ' ';
                line[linepos] = ' ';
            }
            else if (ch[0] == '\b' && timeout > 0)
            {
                line[linepos] = ' ';
                rb->beep_play(1000, HZ, 1000);
            }
            else if (ch[0] < ' ') /* Dont copy control characters */
                line[linepos] = (linepos == 0) ? '\0' : ' ';
            else
                line[linepos] = ch[0];

            line[linepos + 1] = '\0'; /* terminate to check text extent */
            rb->lcd_getstringsize(line, &w, NULL);

            /* try to not split in middle of words */
            if (w + wrap_thresh >= max_w &&
                strpbrk_n(ch, 1, break_chars))
            {
                brk = strpbrk_n(ch+1, max_ch, break_chars);
                chars_next_break = (brk - ch);
                if (brk &&
                 (chars_next_break < 2 || w + (ch_w * chars_next_break) > max_w))
                {
                    if (!isprint(line[linepos]))
                    {
                        line[linepos] = '\0';
                        ch--; /* back-up we want it on the next line */
                    }
                    w += max_w;
                }
            }

            if (w > max_w ||
               (ch[0] >= '\n' && iscntrl(ch[0])) ||
                ch[1] == '\0')
            {
                realline = curline - firstline;
                if (realline >= 0 && realline < max_lines)
                {
                    rb->lcd_putsxy(0, realline * ch_h, line);
                    linesdisp++;
                }
                linepos = 0;
                curline++;
                continue;
            }
            linepos++;
        }

        rb->lcd_update();
        if (timeout >= TIMEOUT_BLOCK)
        {
            action = rb->get_action(CONTEXT_STD, timeout);
            switch(action)
            {
                case ACTION_STD_OK:
                case ACTION_STD_CANCEL:
                    cycles--;
                /* Fall Through */
                case ACTION_NONE:
                    cycles--;
                    break;
                case ACTION_STD_PREV:
                    timeout = TIMEOUT_BLOCK; /* disable timeout */
                    if(firstline > 0)
                        firstline--;
                    break;
                case ACTION_STD_NEXT:
                    timeout = TIMEOUT_BLOCK; /* disable timeout */
                    if (linesdisp == max_lines)
                        firstline++;
                    break;
            }
        }
        else
            break;
    }
    return action;
}

long rb_pow(long x, long n)
{
    long pow = 1;
    unsigned long u;

    if(n <= 0)
    {
        if(n == 0 || x == 1)
            return 1;

        if(x != -1)
            return x != 0 ? 1/x : 0;

        n = -n;
    }

    u = n;
    while(1)
    {
        if(u & 01)
            pow *= x;

        if(u >>= 1)
            x *= x;
        else
            break;
    }

    return pow;
}

int strcoll(const char * str1, const char * str2)
{
    return rb->strcmp(str1, str2);
}

#ifndef _WIN32
struct tm * gmtime(const time_t *timep)
{
    static struct tm time;
    return rb->gmtime_r(timep, &time);
}
#endif

int get_current_path(lua_State *L, int level)
{
    lua_Debug ar;

    if(lua_getstack(L, level, &ar))
    {
        /* Try determining the base path of the current Lua chunk
            and write it to dest. */
        lua_getinfo(L, "S", &ar);

        const char* curfile = &ar.source[1];
        const char* pos = rb->strrchr(curfile, '/');
        if(pos != NULL)
        {
            lua_pushlstring (L, curfile, pos - curfile + 1);
            return 1;
        }
    }

    lua_pushnil(L);
    return 1;
}

/* filetol()
   reads long int from an open file, skips preceding
   whitespaces returns -1 if error, 1 on success.
   *num set to LONG_MAX or LONG_MIN on overflow.
   If number of digits is > than LUAI_MAXNUMBER2STR
   filepointer will continue till the next non digit
   but buffer will stop being filled with characters.
   Preceding zero is ignored
*/
int filetol(int fd, long *num)
{
    static char buffer[LUAI_MAXNUMBER2STR];
    int    retn = -1;
    char   chbuf = 0;
    size_t count = 0;
    bool   neg   = false;
    long   val;

    while (rb->read(fd, &chbuf, 1) == 1)
    {
        if(retn || !isspace(chbuf))
        {
            switch(chbuf)
            {
                case '-':
                {
                    if (retn > 0) /* 0 preceeds, this negative sign must be in error */
                        goto get_digits;
                    neg = true;
                    continue;
                }
                case '0':  /* strip preceeding zeros */
                {
                    *num = 0;
                    retn = 1;
                    continue;
                }
                default:
                    goto get_digits;
            }
        }
    }

    while (rb->read(fd, &chbuf, 1) == 1)
    {
get_digits:
        if(!isdigit(chbuf))
        {
            rb->lseek(fd, -1, SEEK_CUR);
            break;
        }
        else if (count < LUAI_MAXNUMBER2STR - 2)
            buffer[count++] = chbuf;
    }

    if(count)
    {
        buffer[count] = '\0';
        val = strtol(buffer, NULL, 10);
        *num = (neg)? -val:val;
        retn = 1;
    }

    return retn;
}

int get_plugin_action(int timeout, bool with_remote)
{
    static const struct button_mapping *m1[] = { pla_main_ctx };
    int btn;

#ifndef HAVE_REMOTE_LCD
    (void) with_remote;
#else
    static const struct button_mapping *m2[] = { pla_main_ctx, pla_remote_ctx };

    if (with_remote)
        btn = pluginlib_getaction(timeout, m2, 2);
    else
#endif
    btn = pluginlib_getaction(timeout, m1, 1);

    return btn;
}
