/*
 *  libcaca     Colour ASCII-Art library
 *  Copyright © 2002—2018 Sam Hocevar <sam@hocevar.net>
 *              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.
 */

/*
 *  This file contains various canvas handling functions such as character
 *  and string drawing.
 */

#include "config.h"

#if !defined(__KERNEL__)
#   include <stdio.h> /* BUFSIZ */
#   include <string.h>
#   include <stdlib.h>
#   include <stdarg.h>
#   if defined(HAVE_UNISTD_H)
#       include <unistd.h>
#   endif
#   if defined(HAVE_SIGNAL_H)
#       include <signal.h>
#   endif
#   if defined(HAVE_SYS_IOCTL_H)
#       include <sys/ioctl.h>
#   endif
#endif

#include "caca.h"
#include "caca_internals.h"

#if defined _WIN32 && defined __GNUC__ && __GNUC__ >= 3
#   if !HAVE_VSNPRINTF_S
int vsnprintf_s(char *s, size_t n, size_t c,
                const char *fmt, va_list ap) CACA_WEAK;
#   endif
#   if !HAVE_VSNPRINTF
int vsnprintf(char *s, size_t n, const char *fmt, va_list ap) CACA_WEAK;
#   endif
#endif

/** \brief Set cursor position.
 *
 *  Put the cursor at the given coordinates. Functions making use of the
 *  cursor will use the new values. Setting the cursor position outside the
 *  canvas is legal but the cursor will not be shown.
 *
 *  This function never fails.
 *
 *  \param cv A handle to the libcaca canvas.
 *  \param x X cursor coordinate.
 *  \param y Y cursor coordinate.
 *  \return This function always returns 0.
 */
int caca_gotoxy(caca_canvas_t *cv, int x, int y)
{
    cv->frames[cv->frame].x = x;
    cv->frames[cv->frame].y = y;

    return 0;
}

/** \brief Get X cursor position.
 *
 *  Retrieve the X coordinate of the cursor's position.
 *
 *  This function never fails.
 *
 *  \param cv A handle to the libcaca canvas.
 *  \return The cursor's X coordinate.
 */
int caca_wherex(caca_canvas_t const *cv)
{
    return cv->frames[cv->frame].x;
}

/** \brief Get Y cursor position.
 *
 *  Retrieve the Y coordinate of the cursor's position.
 *
 *  This function never fails.
 *
 *  \param cv A handle to the libcaca canvas.
 *  \return The cursor's Y coordinate.
 */
int caca_wherey(caca_canvas_t const *cv)
{
    return cv->frames[cv->frame].y;
}

/** \brief Print an ASCII or Unicode character.
 *
 *  Print an ASCII or Unicode character at the given coordinates, using
 *  the default foreground and background colour values.
 *
 *  If the coordinates are outside the canvas boundaries, nothing is printed.
 *  If a fullwidth Unicode character gets overwritten, its remaining visible
 *  parts are replaced with spaces. If the canvas' boundaries would split the
 *  fullwidth character in two, a space is printed instead.
 *
 *  The behaviour when printing non-printable characters or invalid UTF-32
 *  characters is undefined. To print a sequence of bytes forming an UTF-8
 *  character instead of an UTF-32 character, use the caca_put_str() function.
 *
 *  This function returns the width of the printed character. If it is a
 *  fullwidth character, 2 is returned. Otherwise, 1 is returned.
 *
 *  This function never fails.
 *
 *  \param cv A handle to the libcaca canvas.
 *  \param x X coordinate.
 *  \param y Y coordinate.
 *  \param ch The character to print.
 *  \return The width of the printed character: 2 for a fullwidth character,
 *          1 otherwise.
 */
int caca_put_char(caca_canvas_t *cv, int x, int y, uint32_t ch)
{
    uint32_t *curchar, *curattr, attr;
    int fullwidth, xmin, xmax, ret;

    if(ch == CACA_MAGIC_FULLWIDTH)
        return 1;

    fullwidth = caca_utf32_is_fullwidth(ch);
    ret = fullwidth ? 2 : 1;

    if(x >= (int)cv->width || y < 0 || y >= (int)cv->height)
        return ret;

    if(x == -1 && fullwidth)
    {
        x = 0;
        ch = ' ';
        fullwidth = 0;
    }
    else if(x < 0)
        return ret;

    curchar = cv->chars + x + y * cv->width;
    curattr = cv->attrs + x + y * cv->width;
    attr = cv->curattr;

    xmin = xmax = x;

    /* When overwriting the right part of a fullwidth character,
     * replace its left part with a space. */
    if(x && curchar[0] == CACA_MAGIC_FULLWIDTH)
    {
        curchar[-1] = ' ';
        xmin--;
    }

    if(fullwidth)
    {
        if(x + 1 == (int)cv->width)
            ch = ' ';
        else
        {
            xmax++;

            /* When overwriting the left part of a fullwidth character,
             * replace its right part with a space. */
            if(x + 2 < (int)cv->width && curchar[2] == CACA_MAGIC_FULLWIDTH)
            {
                curchar[2] = ' ';
                xmax++;
            }

            curchar[1] = CACA_MAGIC_FULLWIDTH;
            curattr[1] = attr;
        }
    }
    else
    {
        /* When overwriting the left part of a fullwidth character,
         * replace its right part with a space. */
        if(x + 1 != (int)cv->width && curchar[1] == CACA_MAGIC_FULLWIDTH)
        {
            curchar[1] = ' ';
            xmax++;
        }
    }

    /* Only add a dirty rectangle if we are pasting a different character
     * or attribute at that place. This does not account for inconsistencies
     * in the canvas, ie. if CACA_MAGIC_FULLWIDTH lies at illegal places,
     * but it's the caller's responsibility not to corrupt the contents. */
    if(!cv->dirty_disabled
        && (curchar[0] != ch || curattr[0] != attr))
        caca_add_dirty_rect(cv, xmin, y, xmax - xmin + 1, 1);

    curchar[0] = ch;
    curattr[0] = attr;

    return ret;
}

/** \brief Get the Unicode character at the given coordinates.
 *
 *  Get the ASCII or Unicode value of the character at the given
 *  coordinates. If the value is less or equal to 127 (0x7f),
 *  the character can be printed as ASCII. Otherise, it must be handled
 *  as a UTF-32 value.
 *
 *  If the coordinates are outside the canvas boundaries, a space (0x20)
 *  is returned.
 *
 *  A special exception is when CACA_MAGIC_FULLWIDTH is returned. This
 *  value is guaranteed not to be a valid Unicode character, and indicates
 *  that the character at the left of the requested one is a fullwidth
 *  character.
 *
 *  This function never fails.
 *
 *  \param cv A handle to the libcaca canvas.
 *  \param x X coordinate.
 *  \param y Y coordinate.
 *  \return The Unicode character at the given coordinates.
 */
uint32_t caca_get_char(caca_canvas_t const *cv, int x, int y)
{
    if(x < 0 || x >= (int)cv->width || y < 0 || y >= (int)cv->height)
        return ' ';

    return cv->chars[x + y * cv->width];
}

/** \brief Print a string.
 *
 *  Print an UTF-8 string at the given coordinates, using the default
 *  foreground and background values. The coordinates may be outside the
 *  canvas boundaries (eg. a negative Y coordinate) and the string will
 *  be cropped accordingly if it is too long.
 *
 *  See caca_put_char() for more information on how fullwidth characters
 *  are handled when overwriting each other or at the canvas' boundaries.
 *
 *  This function returns the number of cells printed by the string. It is
 *  not the number of characters printed, because fullwidth characters
 *  account for two cells.
 *
 *  This function never fails.
 *
 *  \param cv A handle to the libcaca canvas.
 *  \param x X coordinate.
 *  \param y Y coordinate.
 *  \param s The string to print.
 *  \return The number of cells printed.
 */
int caca_put_str(caca_canvas_t *cv, int x, int y, char const *s)
{
    size_t rd;
    int len = 0;

    if (y < 0 || y >= (int)cv->height || x >= (int)cv->width)
    {
        while (*s)
        {
            len += caca_utf32_is_fullwidth(caca_utf8_to_utf32(s, &rd)) ? 2 : 1;
            s += rd ? rd : 1;
        }
        return len;
    }

    while (*s)
    {
        uint32_t ch = caca_utf8_to_utf32(s, &rd);

        if (x + len >= -1 && x + len < (int)cv->width)
            caca_put_char(cv, x + len, y, ch);

        len += caca_utf32_is_fullwidth(ch) ? 2 : 1;
        s += rd ? rd : 1;
    }

    return len;
}

/** \brief Print a formated string.
 *
 *  Format a string at the given coordinates, using the default foreground
 *  and background values. The coordinates may be outside the canvas
 *  boundaries (eg. a negative Y coordinate) and the string will be cropped
 *  accordingly if it is too long. The syntax of the format string is the
 *  same as for the C printf() function.
 *
 *  This function returns the number of cells printed by the string. It is
 *  not the number of characters printed, because fullwidth characters
 *  account for two cells.
 *
 *  This function never fails.
 *
 *  \param cv A handle to the libcaca canvas.
 *  \param x X coordinate.
 *  \param y Y coordinate.
 *  \param format The format string to print.
 *  \param ... Arguments to the format string.
 *  \return The number of cells printed.
 */
int caca_printf(caca_canvas_t *cv, int x, int y, char const *format, ...)
{
    va_list args;
    int ret;
    va_start(args, format);
    ret = caca_vprintf(cv, x, y, format, args);
    va_end(args);
    return ret;
}

/** \brief Print a formated string (va_list version).
 *
 *  Format a string at the given coordinates, using the default foreground
 *  and background values. The coordinates may be outside the canvas
 *  boundaries (eg. a negative X coordinate) and the string will be cropped
 *  accordingly if it is too long. The syntax of the format string is the
 *  same as for the C vprintf() function.
 *
 *  This function returns the number of cells printed by the string. It is
 *  not the number of characters printed, because fullwidth characters
 *  account for two cells.
 *
 *  This function never fails.
 *
 *  \param cv A handle to the libcaca canvas.
 *  \param x X coordinate.
 *  \param y Y coordinate.
 *  \param format The format string to print.
 *  \param args A va_list containting the arguments to the format string.
 *  \return The number of cells printed.
 */
int caca_vprintf(caca_canvas_t *cv, int x, int y, char const *format,
                 va_list args)
{
    char tmp[BUFSIZ];
    char *buf = tmp;
    int bufsize = BUFSIZ, ret;

    if(cv->width - x + 1 > BUFSIZ)
    {
        bufsize = cv->width - x + 1;
        buf = malloc(bufsize);
    }

#if defined(HAVE_VSNPRINTF_S)
    vsnprintf_s(buf, bufsize, _TRUNCATE, format, args);
#elif defined(HAVE_VSNPRINTF)
    vsnprintf(buf, bufsize, format, args);
#else
    vsprintf(buf, format, args);
#endif
    buf[bufsize - 1] = '\0';

    ret = caca_put_str(cv, x, y, buf);

    if(buf != tmp)
        free(buf);

    return ret;
}

/** \brief Clear the canvas.
 *
 *  Clear the canvas using the current foreground and background colours.
 *
 *  This function never fails.
 *
 *  \param cv The canvas to clear.
 *  \return This function always returns 0.
 */
int caca_clear_canvas(caca_canvas_t *cv)
{
    uint32_t attr = cv->curattr;
    int n;

    for(n = cv->width * cv->height; n--; )
    {
        cv->chars[n] = (uint32_t)' ';
        cv->attrs[n] = attr;
    }

    if(!cv->dirty_disabled)
        caca_add_dirty_rect(cv, 0, 0, cv->width, cv->height);

    return 0;
}

/** \brief Set cursor handle.
 *
 *  Set the canvas' handle. Blitting functions will use the handle value
 *  to put the canvas at the proper coordinates.
 *
 *  This function never fails.
 *
 *  \param cv A handle to the libcaca canvas.
 *  \param x X handle coordinate.
 *  \param y Y handle coordinate.
 *  \return This function always returns 0.
 */
int caca_set_canvas_handle(caca_canvas_t *cv, int x, int y)
{
    cv->frames[cv->frame].handlex = x;
    cv->frames[cv->frame].handley = y;

    return 0;
}

/** \brief Get X handle position.
 *
 *  Retrieve the X coordinate of the canvas' handle.
 *
 *  This function never fails.
 *
 *  \param cv A handle to the libcaca canvas.
 *  \return The canvas' handle's X coordinate.
 */
int caca_get_canvas_handle_x(caca_canvas_t const *cv)
{
    return cv->frames[cv->frame].handlex;
}

/** \brief Get Y handle position.
 *
 *  Retrieve the Y coordinate of the canvas' handle.
 *
 *  This function never fails.
 *
 *  \param cv A handle to the libcaca canvas.
 *  \return The canvas' handle's Y coordinate.
 */
int caca_get_canvas_handle_y(caca_canvas_t const *cv)
{
    return cv->frames[cv->frame].handley;
}

/** \brief Blit a canvas onto another one.
 *
 *  Blit a canvas onto another one at the given coordinates.
 *  An optional mask canvas can be used.
 *
 *  If an error occurs, -1 is returned and \b errno is set accordingly:
 *  - \c EINVAL A mask was specified but the mask size and source canvas
 *    size do not match.
 *
 *  \param dst The destination canvas.
 *  \param x X coordinate.
 *  \param y Y coordinate.
 *  \param src The source canvas.
 *  \param mask The mask canvas.
 *  \return 0 in case of success, -1 if an error occurred.
 */
int caca_blit(caca_canvas_t *dst, int x, int y,
              caca_canvas_t const *src, caca_canvas_t const *mask)
{
    int i, j, starti, startj, endi, endj, stride, bleed_left, bleed_right;

    if(mask && (src->width != mask->width || src->height != mask->height))
    {
        seterrno(EINVAL);
        return -1;
    }

    x -= src->frames[src->frame].handlex;
    y -= src->frames[src->frame].handley;

    starti = x < 0 ? -x : 0;
    startj = y < 0 ? -y : 0;
    endi = (x + src->width >= dst->width) ? dst->width - x : src->width;
    endj = (y + src->height >= dst->height) ? dst->height - y : src->height;
    stride = endi - starti;

    if(starti > src->width || startj > src->height
        || starti >= endi || startj >= endj)
        return 0;

    bleed_left = bleed_right = 0;

    for(j = startj; j < endj; j++)
    {
        int dstix = (j + y) * dst->width + starti + x;
        int srcix = j * src->width + starti;

        /* FIXME: we are ignoring the mask here */
        if((starti + x) && dst->chars[dstix] == CACA_MAGIC_FULLWIDTH)
        {
            dst->chars[dstix - 1] = ' ';
            bleed_left = 1;
        }

        if(endi + x < dst->width
                && dst->chars[dstix + stride] == CACA_MAGIC_FULLWIDTH)
        {
            dst->chars[dstix + stride] = ' ';
            bleed_right = 1;
        }

        if(mask)
        {
            for(i = 0; i < stride; i++)
            {
                if(mask->chars[srcix + i] == (uint32_t)' ')
                    continue;

                if(dst->chars[dstix + i] != src->chars[srcix + i] ||
                   dst->attrs[dstix + i] != src->attrs[srcix + i])
                {
                    dst->chars[dstix + i] = src->chars[srcix + i];
                    dst->attrs[dstix + i] = src->attrs[srcix + i];
                    if(!dst->dirty_disabled)
                        caca_add_dirty_rect(dst, x + starti + i, y + j, 1, 1);
                }
            }
        }
        else
        {
            if(memcmp(dst->chars + dstix, src->chars + srcix, stride * 4) ||
               memcmp(dst->attrs + dstix, src->attrs + srcix, stride * 4))
            {
                /* FIXME be more precise ? */
                memcpy(dst->chars + dstix, src->chars + srcix, stride * 4);
                memcpy(dst->attrs + dstix, src->attrs + srcix, stride * 4);
                if(!dst->dirty_disabled)
                    caca_add_dirty_rect(dst, x + starti, y + j, stride, 1);
            }
        }

        /* Fix split fullwidth chars */
        if(src->chars[srcix] == CACA_MAGIC_FULLWIDTH)
            dst->chars[dstix] = ' ';

        if(endi < src->width && src->chars[endi] == CACA_MAGIC_FULLWIDTH)
            dst->chars[dstix + stride - 1] = ' ';
    }


    return 0;
}

/** \brief Set a canvas' new boundaries.
 *
 *  Set new boundaries for a canvas. This function can be used to crop a
 *  canvas, to expand it or for combinations of both actions. All frames
 *  are affected by this function.
 *
 *  If an error occurs, -1 is returned and \b errno is set accordingly:
 *  - \c EINVAL Specified width or height is invalid.
 *  - \c EBUSY The canvas is in use by a display driver and cannot be resized.
 *  - \c ENOMEM Not enough memory for the requested canvas size. If this
 *    happens, the canvas handle becomes invalid and should not be used.
 *
 *  \param cv The canvas to crop.
 *  \param x X coordinate of the top-left corner.
 *  \param y Y coordinate of the top-left corner.
 *  \param w The width of the cropped area.
 *  \param h The height of the cropped area.
 *  \return 0 in case of success, -1 if an error occurred.
 */
int caca_set_canvas_boundaries(caca_canvas_t *cv, int x, int y, int w, int h)
{
    caca_canvas_t *new;
    int f, saved_f, framecount;

    if(cv->refcount)
    {
        seterrno(EBUSY);
        return -1;
    }

    if(w < 0 || h < 0)
    {
        seterrno(EINVAL);
        return -1;
    }

    new = caca_create_canvas(w, h);

    framecount = caca_get_frame_count(cv);
    saved_f = cv->frame;

    for(f = 0; f < framecount; f++)
    {
        if(f)
            caca_create_frame(new, framecount);

        caca_set_frame(cv, f);
        caca_set_frame(new, f);
        caca_blit(new, -x, -y, cv, NULL);
        free(cv->frames[f].chars);
        free(cv->frames[f].attrs);
    }
    free(cv->frames);

    cv->frames = new->frames;
    free(new);

    caca_set_frame(cv, saved_f);
    _caca_load_frame_info(cv);

    /* FIXME: this may be optimised somewhat */
    if(!cv->dirty_disabled)
        caca_add_dirty_rect(cv, 0, 0, cv->width, cv->height);

    return 0;
}

/*
 * Functions for the mingw32 runtime
 */

#if defined _WIN32 && defined __GNUC__ && __GNUC__ >= 3
#   if !HAVE_VSNPRINTF_S
int vsnprintf_s(char *s, size_t n, size_t c, const char *fmt, va_list ap)
{
    return vsnprintf(s, n, fmt, ap);
}
#   endif

#   if !HAVE_VSNPRINTF
int vsnprintf(char *s, size_t n, const char *fmt, va_list ap)
{
    return 0;
}
#   endif
#endif

