/*
 * Copyright (C) 2000-2007 Carsten Haitzler, Geoff Harrison and various contributors
 * Copyright (C) 2004-2023 Kim Woelders
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies of the Software, its documentation and marketing & publicity
 * materials, and acknowledgment shall be given in the documentation, materials
 * and software packages that this Software was used.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include "E.h"
#include "conf.h"
#include "emodule.h"
#include "iclass.h"
#include "list.h"
#include "tclass.h"
#include "xwin.h"

#define ENABLE_DESTROY 0        /* Broken */

static          LIST_HEAD(tclass_list);

static TextClass *TextclassGetFallback(void);

static char    *
TextstateFontLookup(const char *name)
{
    const char     *font;

    if (*name == '*')
    {
        font = FontLookup(name + 1);
        if (font)
            name = font;
    }
    return Estrdup(name);
}

static TextState *
TextstateCreate(const char *font)
{
    TextState      *ts;

    ts = ECALLOC(TextState, 1);
    if (!ts)
        return NULL;

    ts->style.orientation = FONT_TO_RIGHT;

    if (font)
        ts->fontname = TextstateFontLookup(font);

    return ts;
}

static void
TextstateDestroy(TextState *ts)
{
    if (!ts)
        return;

    Efree(ts->fontname);
    if (ts->ops)
        ts->ops->Destroy(ts);

    Efree(ts);
}

static TextState *
TextstateSet(TextState **tsp, const char *name)
{
    TextState      *ts;

    ts = TextstateCreate(name);

    if (*tsp)
        TextstateDestroy(*tsp);
    *tsp = ts;

    return ts;
}

static TextClass *
TextclassCreate(const char *name)
{
    TextClass      *tc;

    tc = ECALLOC(TextClass, 1);
    if (!tc)
        return NULL;

    LIST_PREPEND(TextClass, &tclass_list, tc);

    tc->name = Estrdup(name);
    tc->justification = 512;

    return tc;
}

#if ENABLE_DESTROY
static void
TextclassDestroy(TextClass *tc)
{
    if (tc->ref_count > 0)
    {
        DialogOK("TextClass Error!", _("%u references remain"), tc->ref_count);
        return;
    }
    Efree(tc->name);
    TextStateDestroy(tc->norm.normal);
    TextStateDestroy(tc->norm.hilited);
    TextStateDestroy(tc->norm.clicked);
    TextStateDestroy(tc->norm.disabled);
    TextStateDestroy(tc->active.normal);
    TextStateDestroy(tc->active.hilited);
    TextStateDestroy(tc->active.clicked);
    TextStateDestroy(tc->active.disabled);
    TextStateDestroy(tc->sticky.normal);
    TextStateDestroy(tc->sticky.hilited);
    TextStateDestroy(tc->sticky.clicked);
    TextStateDestroy(tc->sticky.disabled);
    TextStateDestroy(tc->sticky_active.normal);
    TextStateDestroy(tc->sticky_active.hilited);
    TextStateDestroy(tc->sticky_active.clicked);
    TextStateDestroy(tc->sticky_active.disabled);

    Efree(tc);
}
#endif                          /* ENABLE_DESTROY */

TextClass      *
TextclassAlloc(const char *name, int fallback)
{
    TextClass      *tc;

    if (!name || !name[0])
        return NULL;

    tc = TextclassFind(name, fallback);
    if (tc)
        tc->ref_count++;

    return tc;
}

void
TextclassFree(TextClass *tc)
{
    if (tc)
        tc->ref_count--;
}

int
TextclassGetJustification(TextClass *tc)
{
    return tc->justification;
}

void
TextclassSetJustification(TextClass *tc, int just)
{
    tc->justification = just;
}

#define TSTATE_SET_STATE(which, fallback) \
   if (!tc->which) tc->which = tc->fallback;

static void
TextclassPopulate(TextClass *tc)
{
    if (!tc || !tc->norm.normal)
        return;

    TSTATE_SET_STATE(norm.hilited, norm.normal);
    TSTATE_SET_STATE(norm.clicked, norm.normal);
    TSTATE_SET_STATE(norm.disabled, norm.normal);

    TSTATE_SET_STATE(active.normal, norm.normal);
    TSTATE_SET_STATE(active.hilited, active.normal);
    TSTATE_SET_STATE(active.clicked, active.normal);
    TSTATE_SET_STATE(active.disabled, active.normal);

    TSTATE_SET_STATE(sticky.normal, norm.normal);
    TSTATE_SET_STATE(sticky.hilited, sticky.normal);
    TSTATE_SET_STATE(sticky.clicked, sticky.normal);
    TSTATE_SET_STATE(sticky.disabled, sticky.normal);

    TSTATE_SET_STATE(sticky_active.normal, norm.normal);
    TSTATE_SET_STATE(sticky_active.hilited, sticky_active.normal);
    TSTATE_SET_STATE(sticky_active.clicked, sticky_active.normal);
    TSTATE_SET_STATE(sticky_active.disabled, sticky_active.normal);
}

static int
_TextclassMatchName(const void *data, const void *match)
{
    return strcmp(((const TextClass *)data)->name, (const char *)match);
}

TextClass      *
TextclassFind(const char *name, int fallback)
{
    TextClass      *tc = NULL;

    if (name)
        tc = LIST_FIND(TextClass, &tclass_list, _TextclassMatchName, name);
    if (tc || !fallback)
        return tc;

#if 0
    Eprintf("%s: Get fallback (%s)\n", __func__, name);
#endif
    return TextclassGetFallback();
}

int
TextclassConfigLoad(FILE *fs)
{
    int             err = 0;
    char            s[FILEPATH_LEN_MAX];
    char            s2[FILEPATH_LEN_MAX];
    int             i1, r, g, b;
    TextClass      *tc = NULL;
    TextState      *ts = NULL;

    while (GetLine(s, sizeof(s), fs))
    {
        i1 = ConfigParseline1(s, s2, NULL, NULL);

        /* tc not needed */
        switch (i1)
        {
        case CONFIG_CLOSE:
            TextclassPopulate(tc);
            goto done;
        case CONFIG_CLASSNAME:
            if (TextclassFind(s2, 0))
            {
                SkipTillEnd(fs);
                goto done;
            }
            tc = TextclassCreate(s2);
            continue;
        }

        /* tc needed */
        if (!tc)
            break;

        switch (i1)
        {
        case TEXT_JUSTIFICATION:
            tc->justification = atoi(s2);
            continue;
        case ICLASS_NORMAL:
            ts = TextstateSet(&tc->norm.normal, s2);
            continue;
        case ICLASS_CLICKED:
            ts = TextstateSet(&tc->norm.clicked, s2);
            continue;
        case ICLASS_HILITED:
            ts = TextstateSet(&tc->norm.hilited, s2);
            continue;
        case ICLASS_DISABLED:
            ts = TextstateSet(&tc->norm.disabled, s2);
            continue;
        case ICLASS_STICKY_NORMAL:
            ts = TextstateSet(&tc->sticky.normal, s2);
            continue;
        case ICLASS_STICKY_CLICKED:
            ts = TextstateSet(&tc->sticky.clicked, s2);
            continue;
        case ICLASS_STICKY_HILITED:
            ts = TextstateSet(&tc->sticky.hilited, s2);
            continue;
        case ICLASS_STICKY_DISABLED:
            ts = TextstateSet(&tc->sticky.disabled, s2);
            continue;
        case ICLASS_ACTIVE_NORMAL:
            ts = TextstateSet(&tc->active.normal, s2);
            continue;
        case ICLASS_ACTIVE_CLICKED:
            ts = TextstateSet(&tc->active.clicked, s2);
            continue;
        case ICLASS_ACTIVE_HILITED:
            ts = TextstateSet(&tc->active.hilited, s2);
            continue;
        case ICLASS_ACTIVE_DISABLED:
            ts = TextstateSet(&tc->active.disabled, s2);
            continue;
        case ICLASS_STICKY_ACTIVE_NORMAL:
            ts = TextstateSet(&tc->sticky_active.normal, s2);
            continue;
        case ICLASS_STICKY_ACTIVE_CLICKED:
            ts = TextstateSet(&tc->sticky_active.clicked, s2);
            continue;
        case ICLASS_STICKY_ACTIVE_HILITED:
            ts = TextstateSet(&tc->sticky_active.hilited, s2);
            continue;
        case ICLASS_STICKY_ACTIVE_DISABLED:
            ts = TextstateSet(&tc->sticky_active.disabled, s2);
            continue;
        }

        /* ts needed */
        if (!ts)
            break;

        switch (i1)
        {
        case TEXT_ORIENTATION:
            ts->style.orientation = atoi(s2);
            continue;
        case TEXT_EFFECT:
            ts->style.effect = atoi(s2);
            continue;
        case TEXT_FG_COL:
            r = g = b = 0;
            sscanf(s, "%*s %i %i %i", &r, &g, &b);
            COLOR32_FROM_RGB(ts->fg_col, r, g, b);
            continue;
        case TEXT_BG_COL:
            r = g = b = 0;
            sscanf(s, "%*s %i %i %i", &r, &g, &b);
            COLOR32_FROM_RGB(ts->bg_col, r, g, b);
            continue;
        default:
            ConfigParseError("TextClass", s);
            continue;
        }
    }
    err = -1;

  done:
    return err;
}

static TextClass *
TextclassGetFallback(void)
{
    TextClass      *tc;

    tc = TextclassFind("__fb_tc", 0);
    if (tc)
        return tc;

    /* Create fallback textclass */
    tc = TextclassCreate("__fb_tc");
    if (!tc)
        return tc;

    tc->norm.normal =
        TextstateCreate("-*-helvetica-medium-r-*-*-12-*-*-*-*-*-*-*");
    COLOR32_FROM_RGB(tc->norm.normal->fg_col, 0, 0, 0);
    TextclassPopulate(tc);

    return tc;
}

/*
 * Textclass Module
 */

static void
TextclassIpc(const char *params)
{
    char            param1[1024];
    char            param2[1024];
    int             l;
    const char     *p;
    TextClass      *tc;

    if (!params)
    {
        IpcPrintf("Please specify...\n");
        return;
    }

    p = params;
    l = 0;
    param1[0] = param2[0] = '\0';
    sscanf(p, "%1000s %1000s %n", param1, param2, &l);
    p += l;

    if (!strncmp(param1, "list", 2))
    {
        LIST_FOR_EACH(TextClass, &tclass_list, tc) IpcPrintf("%s\n", tc->name);
        return;
    }

    if (!param1[0])
    {
        IpcPrintf("TextClass not specified\n");
        return;
    }

    tc = TextclassFind(param1, 0);
    if (!tc)
    {
        IpcPrintf("TextClass not found: %s\n", param1);
        return;
    }

    if (!strcmp(param2, "apply"))
    {
        EX_Window       xwin;
        Win             win;
        char            state[20];
        int             x, y, st;

        /* 3:xwin 4:x 5:y 6:state 7-:txt */
        xwin = NoXID;
        x = y = 0;
        state[0] = '\0';
        l = 0;
        sscanf(p, "%x %d %d %16s %n", &xwin, &x, &y, state, &l);
        p += l;

        if (!strcmp(state, "normal"))
            st = STATE_NORMAL;
        else if (!strcmp(state, "hilited"))
            st = STATE_HILITED;
        else if (!strcmp(state, "clicked"))
            st = STATE_CLICKED;
        else if (!strcmp(state, "disabled"))
            st = STATE_DISABLED;
        else
            st = STATE_NORMAL;

        if (l == 0)
            return;

        win = ECreateWinFromXwin(xwin);
        if (!win)
            return;

        TextDraw(tc, win, NoXID, 0, 0, st, p, x, y, 99999, 99999, 0);
        EDestroyWin(win);
    }
    else if (!strcmp(param2, "query_size"))
    {
        int             w, h;

        /* 3-:txt */

        if (l == 0)
            return;

        w = h = 0;
        TextSize(tc, 0, 0, STATE_NORMAL, p, &w, &h);
        IpcPrintf("%i %i\n", w, h);
    }
    else if (!strcmp(param2, "query"))
    {
        IpcPrintf("TextClass %s found\n", tc->name);
    }
    else if (!strcmp(param2, "ref_count"))
    {
        IpcPrintf("%u references remain\n", tc->ref_count);
    }
    else
    {
        IpcPrintf("Error: unknown operation specified\n");
    }
}

static const IpcItem TextclassIpcArray[] = {
    {
     TextclassIpc,
     "textclass", "tc",
     "List textclasses, apply a textclass",
     NULL }
    ,
};

/*
 * Module descriptor
 */
extern const EModule ModTextclass;

const EModule   ModTextclass = {
    "textclass", "tc",
    NULL,
    MOD_ITEMS(TextclassIpcArray),
    { 0, NULL }
};
