/*
 * Audacious - a cross-platform multimedia player
 * Copyright (c) 2007 Tomasz Moń
 * Copyright (c) 2011 John Lindgren
 *
 * Based on:
 * BMP - Cross-platform multimedia player
 * Copyright (C) 2003-2004  BMP development team.
 * XMMS:
 * Copyright (C) 1998-2003  XMMS development team.
 *
 * 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; under version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses>.
 *
 * The Audacious team does not consider modular code linking to
 * Audacious or using our public API to be a derived work.
 */

#include <string.h>

#include <libaudcore/audstrings.h>
#include <libaudcore/hook.h>
#include <libaudcore/objects.h>

#include "skins_cfg.h"
#include "skin.h"
#include "textbox.h"

#define DELAY 50

static Index<TextBox *> textboxes;

void TextBox::draw (cairo_t * cr)
{
    if (m_scrolling)
    {
        cairo_set_source_surface (cr, m_buf.get (), -m_offset * config.scale, 0);
        cairo_paint (cr);

        if (-m_offset + m_buf_width < m_width)
        {
            cairo_set_source_surface (cr, m_buf.get (),
             (-m_offset + m_buf_width) * config.scale, 0);
            cairo_paint (cr);
        }
    }
    else
    {
        cairo_set_source_surface (cr, m_buf.get (), 0, 0);
        cairo_paint (cr);
    }
}

bool TextBox::button_press (GdkEventButton * event)
{
    return press ? press (event) : false;
}

void TextBox::scroll_timeout ()
{
    if (m_delay < DELAY)
    {
        m_delay ++;
        return;
    }

    if (m_two_way && m_backward)
        m_offset --;
    else
        m_offset ++;

    if (m_two_way && (m_backward ? (m_offset <= 0) : (m_offset + m_width >= m_buf_width)))
    {
        m_backward = ! m_backward;
        m_delay = 0;
    }

    if (! m_two_way && m_offset >= m_buf_width)
        m_offset = 0;

    draw_now ();
}

void TextBox::render_vector (const char * text)
{
    PangoLayout * layout = gtk_widget_create_pango_layout (gtk_dr (), text);
    pango_layout_set_font_description (layout, m_font.get ());

    PangoRectangle ink, logical;
    pango_layout_get_pixel_extents (layout, & ink, & logical);

    /* use logical width so as not to trim off the trailing space of the " --- " */
    /* use ink height since vertical space is quite limited */
    int width = aud::max (-ink.x + logical.width, 1);
    int height = aud::max (ink.height, 1);

    resize (m_width * config.scale, height);

    m_buf_width = aud::max ((width + config.scale - 1) / config.scale, m_width);
    m_buf.capture (cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
     m_buf_width * config.scale, height));

    cairo_t * cr = cairo_create (m_buf.get ());

    set_cairo_color (cr, skin.colors[SKIN_TEXTBG]);
    cairo_paint (cr);

    cairo_move_to (cr, -ink.x, -ink.y);
    set_cairo_color (cr, skin.colors[SKIN_TEXTFG]);
    pango_cairo_show_layout (cr, layout);

    cairo_destroy (cr);
    g_object_unref (layout);
}

static void lookup_char (const char c, int * x, int * y)
{
    int tx, ty;

    switch (c)
    {
    case '"': tx = 26; ty = 0; break;
    case '@': tx = 27; ty = 0; break;
    case ' ': tx = 29; ty = 0; break;
    case ':':
    case ';':
    case '|': tx = 12; ty = 1; break;
    case '(':
    case '{': tx = 13; ty = 1; break;
    case ')':
    case '}': tx = 14; ty = 1; break;
    case '-':
    case '~': tx = 15; ty = 1; break;
    case '`':
    case '\'': tx = 16; ty = 1; break;
    case '!': tx = 17; ty = 1; break;
    case '_': tx = 18; ty = 1; break;
    case '+': tx = 19; ty = 1; break;
    case '\\': tx = 20; ty = 1; break;
    case '/': tx = 21; ty = 1; break;
    case '[': tx = 22; ty = 1; break;
    case ']': tx = 23; ty = 1; break;
    case '^': tx = 24; ty = 1; break;
    case '&': tx = 25; ty = 1; break;
    case '%': tx = 26; ty = 1; break;
    case '.':
    case ',': tx = 27; ty = 1; break;
    case '=': tx = 28; ty = 1; break;
    case '$': tx = 29; ty = 1; break;
    case '#': tx = 30; ty = 1; break;
    case '?': tx = 3; ty = 2; break;
    case '*': tx = 4; ty = 2; break;
    default: tx = 3; ty = 2; break; /* '?' */
    }

    * x = tx * skin.hints.textbox_bitmap_font_width;
    * y = ty * skin.hints.textbox_bitmap_font_height;
}

void TextBox::render_bitmap (const char * text)
{
    int cw = skin.hints.textbox_bitmap_font_width;
    int ch = skin.hints.textbox_bitmap_font_height;

    resize (m_width * config.scale, ch * config.scale);

    long len;
    gunichar * utf32 = g_utf8_to_ucs4 (text, -1, nullptr, & len, nullptr);
    g_return_if_fail (utf32);

    m_buf_width = aud::max (cw * (int) len, m_width);
    m_buf.capture (cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
     m_buf_width * config.scale, ch * config.scale));

    cairo_t * cr = cairo_create (m_buf.get ());
    if (config.scale != 1)
        cairo_scale (cr, config.scale, config.scale);

    gunichar * s = utf32;
    for (int x = 0; x < m_buf_width; x += cw)
    {
        gunichar c = * s ? * s ++ : ' ';
        int cx = 0, cy = 0;

        if (c >= 'A' && c <= 'Z')
            cx = cw * (c - 'A');
        else if (c >= 'a' && c <= 'z')
            cx = cw * (c - 'a');
        else if (c >= '0' && c <= '9')
        {
            cx = cw * (c - '0');
            cy = ch;
        }
        else
            lookup_char (c, & cx, & cy);

        skin_draw_pixbuf (cr, SKIN_TEXT, cx, cy, x, 0, cw, ch);
    }

    cairo_destroy (cr);
    g_free (utf32);
}

void TextBox::render ()
{
    m_scrolling = false;
    m_backward = false;
    m_offset = 0;
    m_delay = 0;

    const char * text = m_text ? m_text : "";

    if (m_font)
        render_vector (text);
    else
        render_bitmap (text);

    if (m_may_scroll && m_buf_width > m_width)
    {
        m_scrolling = true;

        if (! m_two_way)
        {
            StringBuf temp = str_printf ("%s --- ", text);

            if (m_font)
                render_vector (temp);
            else
                render_bitmap (temp);
        }
    }

    queue_draw ();

    if (m_scrolling)
        scroll_timer.start ();
    else
        scroll_timer.stop ();
}

void TextBox::set_width (int width)
{
    if (m_width != width)
    {
        m_width = width;
        render ();
    }
}

void TextBox::set_text (const char * text)
{
    if (strcmp_safe (m_text, text))
    {
        m_text = String (text);
        render ();
    }
}

void TextBox::set_font (const char * font)
{
    if (font)
        m_font.capture (pango_font_description_from_string (font));
    else
        m_font.clear ();

    render ();
}

void TextBox::set_scroll (bool scroll)
{
    if (m_may_scroll != scroll || m_two_way != config.twoway_scroll)
    {
        m_may_scroll = scroll;
        m_two_way = config.twoway_scroll;
        render ();
    }
}

TextBox::~TextBox ()
{
    int idx = textboxes.find (this);
    if (idx >= 0)
        textboxes.remove (idx, 1);
}

TextBox::TextBox (int width, const char * font, bool scroll) :
    m_width (width),
    m_may_scroll (scroll),
    m_two_way (config.twoway_scroll)
{
    /* size is computed by set_font() */
    add_input (1, 1, false, true);
    set_font (font);

    textboxes.append (this);
}

void TextBox::update_all ()
{
    for (TextBox * textbox : textboxes)
        textbox->render ();
}
