/*
  showfont:  An example of using the SDL_ttf library with 2D graphics.
  Copyright (C) 2001-2025 Sam Lantinga <slouken@libsdl.org>

  This software is provided 'as-is', without any express or implied
  warranty.  In no event will the authors be held liable for any damages
  arising from the use of this software.

  Permission is granted to anyone to use this software for any purpose,
  including commercial applications, and to alter it and redistribute it
  freely, subject to the following restrictions:

  1. The origin of this software must not be misrepresented; you must not
     claim that you wrote the original software. If you use this software
     in a product, an acknowledgment in the product documentation would be
     appreciated but is not required.
  2. Altered source versions must be plainly marked as such, and must not be
     misrepresented as being the original software.
  3. This notice may not be removed or altered from any source distribution.
*/

/* A simple program to test the text rendering feature of the TTF library */

#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3_ttf/SDL_ttf.h>

#include <stdlib.h>

#include "editbox.h"

#define DEFAULT_PTSIZE  18.0f
#define DEFAULT_TEXT    "The quick brown fox jumped over the lazy dog"
// Newlines
//#define DEFAULT_TEXT    "The quick brown fox\njumped over the lazy dog"
// Combining characters
//#define DEFAULT_TEXT    "\xc5\xab\xcc\x80\x20\xe1\xba\x83\x20\x6e\xcc\x82\x20\x48\xcc\xa8\x20\x6f\xcd\x9c\x75"
// Chinese text
//#define DEFAULT_TEXT    "\xe5\xad\xa6\xe4\xb9\xa0\xe6\x9f\x90\xe8\xaf\xbe\xe7\xa8\x8b\xe5\xbf\x85\xe8\xaf\xbb\xe7\x9a\x84"
// Hebrew text
//#define DEFAULT_TEXT      "\xd7\xa6\xd7\xa4\xd7\xa2\x20\xd7\x97\xd7\x96\xd7\xa7\x20\xd7\xa0\xd7\xa9\xd7\x9a\x20\xd7\x93\xd7\x92\x20\xd7\x9e\xd7\xaa\x20\xd7\x91\xd7\x90\xd7\x95\xd7\xa1\xd7\x98\xd7\xa8\xd7\x9c\xd7\x99\xd7\x94"
// Mixed English, Chinese, Hebrew, and emoji text
//#define DEFAULT_TEXT    "The quick brown fox\njumped over the \xe5\xad\xa6\xe4\xb9\xa0\xe6\x9f\x90\xe8\xaf\xbe\xe7\xa8\x8b\xe5\xbf\x85\xe8\xaf\xbb\xe7\x9a\x84 \xd7\x90\xd7\x91\xd7\x92 \xf0\x9f\x98\x89"
#define WIDTH   640
#define HEIGHT  480
#define MAX_FALLBACKS 4


#define TTF_SHOWFONT_USAGE \
"Usage: %s [--textengine surface|renderer] [--solid] [--shaded] [--blended] [-b] [-i] [-u] [-s] [--outline size] [--hintlight|--hintmono|--hintnone] [--nokerning] [--wrap] [--align left|center|right] [--fgcol r,g,b,a] [--bgcol r,g,b,a] [--disable-editbox] [--fallback <font>.ttf>] <font>.ttf [ptsize] [text]\n"

typedef enum
{
    TextEngineNone,
    TextEngineSurface,
    TextEngineRenderer
} TextEngine;

typedef enum
{
    TextRenderSolid,
    TextRenderShaded,
    TextRenderBlended
} TextRenderMethod;

typedef struct {
    bool done;
    SDL_Window *window;
    SDL_Surface *window_surface;
    SDL_Renderer *renderer;
    TTF_Font *font;
    TTF_Text *caption;
    SDL_Rect captionRect;
    SDL_Texture *message;
    SDL_FRect messageRect;
    TextEngine textEngine;
    SDL_FRect textRect;
    EditBox *edit;
} Scene;

static void DrawScene(Scene *scene)
{
    SDL_Renderer *renderer = scene->renderer;

    /* Clear the background to background color */
    SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);
    SDL_RenderClear(renderer);

    if (scene->edit) {
        /* Clear the text rect to light gray */
        SDL_SetRenderDrawColor(renderer, 0xCC, 0xCC, 0xCC, 0xFF);
        SDL_RenderFillRect(renderer, &scene->textRect);

        if (scene->edit->has_focus) {
            SDL_FRect focusRect = scene->textRect;
            focusRect.x -= 1;
            focusRect.y -= 1;
            focusRect.w += 2;
            focusRect.h += 2;
            SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF);
            SDL_RenderRect(renderer, &focusRect);
        }

        EditBox_Draw(scene->edit);
    }

    switch (scene->textEngine) {
    case TextEngineSurface:
        /* Flush the renderer so we can draw directly to the window surface */
        SDL_FlushRenderer(renderer);
        TTF_DrawSurfaceText(scene->caption, scene->captionRect.x, scene->captionRect.y, scene->window_surface);
        break;
    case TextEngineRenderer:
        TTF_DrawRendererText(scene->caption, (float)scene->captionRect.x, (float)scene->captionRect.y);
        break;
    default:
        SDL_assert(!"Unknown text engine");
        break;
    }

    SDL_RenderTexture(renderer, scene->message, NULL, &scene->messageRect);
    SDL_RenderPresent(renderer);

    if (scene->window_surface) {
        SDL_UpdateWindowSurface(scene->window);
    }
}

static void AdjustTextOffset(TTF_Text *text, int xoffset, int yoffset)
{
    int x, y;

    TTF_GetTextPosition(text, &x, &y);
    x += xoffset;
    y += yoffset;
    TTF_SetTextPosition(text, x, y);
}

static void HandleKeyDown(Scene *scene, SDL_Event *event)
{
    int style, outline;
    float ptsize;

    switch (event->key.key) {
    case SDLK_A:
        /* Cycle alignment */
        switch (TTF_GetFontWrapAlignment(scene->font)) {
        case TTF_HORIZONTAL_ALIGN_LEFT:
            TTF_SetFontWrapAlignment(scene->font, TTF_HORIZONTAL_ALIGN_CENTER);
            break;
        case TTF_HORIZONTAL_ALIGN_CENTER:
            TTF_SetFontWrapAlignment(scene->font, TTF_HORIZONTAL_ALIGN_RIGHT);
            break;
        case TTF_HORIZONTAL_ALIGN_RIGHT:
            TTF_SetFontWrapAlignment(scene->font, TTF_HORIZONTAL_ALIGN_LEFT);
            break;
        default:
            SDL_Log("Unknown wrap alignment: %d", TTF_GetFontWrapAlignment(scene->font));
            break;
        }
        break;

    case SDLK_B:
        /* Toggle bold style */
        style = TTF_GetFontStyle(scene->font);
        if (style & TTF_STYLE_BOLD) {
            style &= ~TTF_STYLE_BOLD;
        } else {
            style |= TTF_STYLE_BOLD;
        }
        TTF_SetFontStyle(scene->font, style);
        break;

    case SDLK_I:
        /* Toggle italic style */
        style = TTF_GetFontStyle(scene->font);
        if (style & TTF_STYLE_ITALIC) {
            style &= ~TTF_STYLE_ITALIC;
        } else {
            style |= TTF_STYLE_ITALIC;
        }
        TTF_SetFontStyle(scene->font, style);
        break;

    case SDLK_O:
        /* Toggle scene->font outline */
        outline = TTF_GetFontOutline(scene->font);
        if (outline) {
            outline = 0;
        } else {
            outline = 1;
        }
        TTF_SetFontOutline(scene->font, outline);
        break;

    case SDLK_R:
        /* Toggle layout direction */
        if (TTF_GetFontDirection(scene->font) == TTF_DIRECTION_INVALID ||
            TTF_GetFontDirection(scene->font) == TTF_DIRECTION_LTR) {
            TTF_SetFontDirection(scene->font, TTF_DIRECTION_RTL);
        } else if (TTF_GetFontDirection(scene->font) == TTF_DIRECTION_RTL) {
            TTF_SetFontDirection(scene->font, TTF_DIRECTION_LTR);
        } else if (TTF_GetFontDirection(scene->font) == TTF_DIRECTION_TTB) {
            TTF_SetFontDirection(scene->font, TTF_DIRECTION_BTT);
        } else if (TTF_GetFontDirection(scene->font) == TTF_DIRECTION_BTT) {
            TTF_SetFontDirection(scene->font, TTF_DIRECTION_TTB);
        }
        break;

    case SDLK_S:
        /* Toggle strike-through style */
        style = TTF_GetFontStyle(scene->font);
        if (style & TTF_STYLE_STRIKETHROUGH) {
            style &= ~TTF_STYLE_STRIKETHROUGH;
        } else {
            style |= TTF_STYLE_STRIKETHROUGH;
        }
        TTF_SetFontStyle(scene->font, style);
        break;

    case SDLK_U:
        /* Toggle underline style */
        style = TTF_GetFontStyle(scene->font);
        if (style & TTF_STYLE_UNDERLINE) {
            style &= ~TTF_STYLE_UNDERLINE;
        } else {
            style |= TTF_STYLE_UNDERLINE;
        }
        TTF_SetFontStyle(scene->font, style);
        break;

    case SDLK_LEFT:
        if (event->key.mod & SDL_KMOD_CTRL) {
            AdjustTextOffset(scene->edit->text, -1, 0);
        }
        break;

    case SDLK_RIGHT:
        if (event->key.mod & SDL_KMOD_CTRL) {
            AdjustTextOffset(scene->edit->text, 1, 0);
        }
        break;

    case SDLK_UP:
        if (event->key.mod & SDL_KMOD_CTRL) {
            AdjustTextOffset(scene->edit->text, 0, -1);
        } else {
            /* Increase font size */
            ptsize = TTF_GetFontSize(scene->font);
            TTF_SetFontSize(scene->font, ptsize + 1.0f);
        }
        break;

    case SDLK_DOWN:
        if (event->key.mod & SDL_KMOD_CTRL) {
            AdjustTextOffset(scene->edit->text, 0, 1);
        } else {
            /* Decrease font size */
            ptsize = TTF_GetFontSize(scene->font);
            TTF_SetFontSize(scene->font, ptsize - 1.0f);
        }
        break;

    case SDLK_ESCAPE:
        scene->done = true;
        break;

    default:
        break;
    }
}

int main(int argc, char *argv[])
{
    char *argv0 = argv[0];
    TTF_Font *font = NULL;
    SDL_Surface *text = NULL;
    Scene scene;
    float ptsize;
    int i;
    SDL_Color white = { 0xFF, 0xFF, 0xFF, SDL_ALPHA_OPAQUE };
    SDL_Color black = { 0x00, 0x00, 0x00, SDL_ALPHA_OPAQUE };
    SDL_Color *forecol;
    SDL_Color *backcol;
    SDL_Event event;
    TTF_TextEngine *engine = NULL;
    TextRenderMethod rendermethod = TextRenderShaded;
    int renderstyle = TTF_STYLE_NORMAL;
    int outline = 0;
    int hinting = TTF_HINTING_NORMAL;
    int kerning = 1;
    bool wrap = false;
    TTF_HorizontalAlignment align = TTF_HORIZONTAL_ALIGN_LEFT;
    bool editbox = true;
    bool dump = false;
    char *message, string[128];
    int num_fallbacks = 0;
    const char *fallback_font_files[MAX_FALLBACKS];
    TTF_Font *fallback_fonts[MAX_FALLBACKS];
    int result = 0;

    SDL_zero(scene);
    scene.textEngine = TextEngineRenderer;

    SDL_zeroa(fallback_fonts);

    /* Default is black and white */
    forecol = &black;
    backcol = &white;
    for (i=1; argv[i] && argv[i][0] == '-'; ++i) {
        if (SDL_strcmp(argv[i], "--fallback") == 0 && argv[i+1]) {
            ++i;
            if (num_fallbacks < MAX_FALLBACKS) {
                fallback_font_files[num_fallbacks++] = argv[i];
            } else {
                SDL_Log("Too many fallback fonts (maximum = %d)", MAX_FALLBACKS);
                return(1);
            }
        } else
        if (SDL_strcmp(argv[i], "--textengine") == 0 && argv[i+1]) {
            ++i;
            if (SDL_strcmp(argv[i], "surface") == 0) {
                scene.textEngine = TextEngineSurface;
            }  else if (SDL_strcmp(argv[i], "renderer") == 0) {
                scene.textEngine = TextEngineRenderer;
            } else {
                SDL_Log(TTF_SHOWFONT_USAGE, argv0);
                return(1);
            }
        } else
        if (SDL_strcmp(argv[i], "--solid") == 0) {
            rendermethod = TextRenderSolid;
        } else
        if (SDL_strcmp(argv[i], "--shaded") == 0) {
            rendermethod = TextRenderShaded;
        } else
        if (SDL_strcmp(argv[i], "--blended") == 0) {
            rendermethod = TextRenderBlended;
        } else
        if (SDL_strcmp(argv[i], "-b") == 0) {
            renderstyle |= TTF_STYLE_BOLD;
        } else
        if (SDL_strcmp(argv[i], "-i") == 0) {
            renderstyle |= TTF_STYLE_ITALIC;
        } else
        if (SDL_strcmp(argv[i], "-u") == 0) {
            renderstyle |= TTF_STYLE_UNDERLINE;
        } else
        if (SDL_strcmp(argv[i], "-s") == 0) {
            renderstyle |= TTF_STYLE_STRIKETHROUGH;
        } else
        if (SDL_strcmp(argv[i], "--outline") == 0 && argv[i+1]) {
            if (SDL_sscanf(argv[++i], "%d", &outline) != 1) {
                SDL_Log(TTF_SHOWFONT_USAGE, argv0);
                return(1);
            }
        } else
        if (SDL_strcmp(argv[i], "--hintlight") == 0) {
            hinting = TTF_HINTING_LIGHT;
        } else
        if (SDL_strcmp(argv[i], "--hintmono") == 0) {
            hinting = TTF_HINTING_MONO;
        } else
        if (SDL_strcmp(argv[i], "--hintnone") == 0) {
            hinting = TTF_HINTING_NONE;
        } else
        if (SDL_strcmp(argv[i], "--nokerning") == 0) {
            kerning = 0;
        } else
        if (SDL_strcmp(argv[i], "--wrap") == 0) {
            wrap = true;
        } else
        if (SDL_strcmp(argv[i], "--align") == 0 && argv[i+1]) {
            ++i;
            if (SDL_strcmp(argv[i], "left") == 0) {
                align = TTF_HORIZONTAL_ALIGN_LEFT;
            } else if (SDL_strcmp(argv[i], "center") == 0) {
                align = TTF_HORIZONTAL_ALIGN_CENTER;
            } else if (SDL_strcmp(argv[i], "right") == 0) {
                align = TTF_HORIZONTAL_ALIGN_RIGHT;
            } else {
                SDL_Log(TTF_SHOWFONT_USAGE, argv0);
                return (1);
            }
        } else
        if (SDL_strcmp(argv[i], "--fgcol") == 0 && argv[i+1]) {
            int r, g, b, a = SDL_ALPHA_OPAQUE;
            if (SDL_sscanf(argv[++i], "%d,%d,%d,%d", &r, &g, &b, &a) < 3) {
                SDL_Log(TTF_SHOWFONT_USAGE, argv0);
                return(1);
            }
            forecol->r = (Uint8)r;
            forecol->g = (Uint8)g;
            forecol->b = (Uint8)b;
            forecol->a = (Uint8)a;
        } else
        if (SDL_strcmp(argv[i], "--bgcol") == 0 && argv[i+1]) {
            int r, g, b, a = SDL_ALPHA_OPAQUE;
            if (SDL_sscanf(argv[++i], "%d,%d,%d,%d", &r, &g, &b, &a) < 3) {
                SDL_Log(TTF_SHOWFONT_USAGE, argv0);
                return(1);
            }
            backcol->r = (Uint8)r;
            backcol->g = (Uint8)g;
            backcol->b = (Uint8)b;
            backcol->a = (Uint8)a;
        } else
        if (SDL_strcmp(argv[i], "--disable-editbox") == 0) {
            editbox = false;
        } else
        if (SDL_strcmp(argv[i], "--dump") == 0) {
            dump = true;
        } else {
            SDL_Log(TTF_SHOWFONT_USAGE, argv0);
            return(1);
        }
    }
    argv += i;
    argc -= i;

    /* Check usage */
    if (!argv[0]) {
        SDL_Log(TTF_SHOWFONT_USAGE, argv0);
        return(1);
    }

    /* Initialize the TTF library */
    if (!TTF_Init()) {
        SDL_Log("Couldn't initialize TTF: %s",SDL_GetError());
        result = 2;
        goto done;
    }

    /* Open the font file with the requested point size */
    ptsize = 0.0f;
    if (argc > 1) {
        ptsize = (float)SDL_atof(argv[1]);
    }
    if (ptsize == 0.0f) {
        i = 2;
        ptsize = DEFAULT_PTSIZE;
    } else {
        i = 3;
    }
    font = TTF_OpenFont(argv[0], ptsize);
    if (font == NULL) {
        SDL_Log("Couldn't load %g pt font from %s: %s",
                    ptsize, argv[0], SDL_GetError());
        result = 2;
        goto done;
    }
    TTF_SetFontStyle(font, renderstyle);
    TTF_SetFontOutline(font, outline);
    TTF_SetFontKerning(font, kerning);
    TTF_SetFontHinting(font, hinting);
    TTF_SetFontWrapAlignment(font, align);
    scene.font = font;

    for (i = 0; i < num_fallbacks; ++i) {
        fallback_fonts[i] = TTF_OpenFont(fallback_font_files[i], ptsize);
        if (!fallback_fonts[i]) {
            SDL_Log("Couldn't load %g pt font from %s: %s",
                        ptsize, fallback_font_files[i], SDL_GetError());
            result = 2;
            goto done;
        }
        TTF_AddFallbackFont(font, fallback_fonts[i]);
    }

    if(dump) {
        for(i = 48; i < 123; i++) {
            SDL_Surface* glyph = NULL;

            glyph = TTF_RenderGlyph_Shaded(font, i, *forecol, *backcol);

            if(glyph) {
                char outname[64];
                SDL_snprintf(outname, sizeof(outname), "glyph-%d.bmp", i);
                SDL_SaveBMP(glyph, outname);
            }

        }
        result = 0;
        goto done;
    }

    /* Create a window */
    scene.window = SDL_CreateWindow("showfont demo", WIDTH, HEIGHT, 0);
    if (!scene.window) {
        SDL_Log("SDL_CreateWindow() failed: %s", SDL_GetError());
        result = 2;
        goto done;
    }
    if (scene.textEngine == TextEngineSurface) {
        scene.window_surface = SDL_GetWindowSurface(scene.window);
        if (!scene.window_surface) {
            SDL_Log("SDL_CreateWindowSurface() failed: %s", SDL_GetError());
            result = 2;
            goto done;
        }
        SDL_SetWindowSurfaceVSync(scene.window, 1);

        scene.renderer = SDL_CreateSoftwareRenderer(scene.window_surface);
    } else {
        scene.renderer = SDL_CreateRenderer(scene.window, NULL);
        if (scene.renderer) {
            SDL_SetRenderVSync(scene.renderer, 1);
        }
    }
    if (!scene.renderer) {
        SDL_Log("SDL_CreateRenderer() failed: %s", SDL_GetError());
        result = 2;
        goto done;
    }

    switch (scene.textEngine) {
    case TextEngineSurface:
        engine = TTF_CreateSurfaceTextEngine();
        if (!engine) {
            SDL_Log("Couldn't create surface text engine: %s", SDL_GetError());
            result = 2;
            goto done;
        }
        break;
    case TextEngineRenderer:
        engine = TTF_CreateRendererTextEngine(scene.renderer);
        if (!engine) {
            SDL_Log("Couldn't create renderer text engine: %s", SDL_GetError());
            result = 2;
            goto done;
        }
        break;
    default:
        break;
    }

    /* Show which font file we're looking at */
    SDL_snprintf(string, sizeof(string), "Font file: %s", argv[0]);  /* possible overflow */
    scene.caption = TTF_CreateText(engine, font, string, 0);
    TTF_SetTextColor(scene.caption, forecol->r, forecol->g, forecol->b, forecol->a);
    scene.captionRect.x = 4;
    scene.captionRect.y = 4;
    TTF_GetTextSize(scene.caption, &scene.captionRect.w, &scene.captionRect.h);

    /* Render and center the message */
    if (argc > 2) {
        message = argv[2];
    } else {
        message = DEFAULT_TEXT;
    }
    switch (rendermethod) {
    case TextRenderSolid:
        if (wrap) {
            text = TTF_RenderText_Solid_Wrapped(font, message, 0, *forecol, 0);
        } else {
            text = TTF_RenderText_Solid(font, message, 0, *forecol);
        }
        break;
    case TextRenderShaded:
        if (wrap) {
            text = TTF_RenderText_Shaded_Wrapped(font, message, 0, *forecol, *backcol, 0);
        } else {
            text = TTF_RenderText_Shaded(font, message, 0, *forecol, *backcol);
        }
        break;
    case TextRenderBlended:
        if (wrap) {
            text = TTF_RenderText_Blended_Wrapped(font, message, 0, *forecol, 0);
        } else {
            text = TTF_RenderText_Blended(font, message, 0, *forecol);
        }
        break;
    }
    if (text == NULL) {
        SDL_Log("Couldn't render text: %s", SDL_GetError());
        result = 2;
        goto done;
    }
    scene.messageRect.x = (float)((WIDTH - text->w)/2);
    scene.messageRect.y = (float)((HEIGHT - text->h)/2);
    scene.messageRect.w = (float)text->w;
    scene.messageRect.h = (float)text->h;
    scene.message = SDL_CreateTextureFromSurface(scene.renderer, text);
    SDL_Log("Font is generally %d big, and string is %d big",
                        TTF_GetFontHeight(font), text->h);

    if (editbox) {
        scene.textRect.x = 8.0f;
        scene.textRect.y = scene.captionRect.y + scene.captionRect.h + 4.0f;
        scene.textRect.w = WIDTH / 2 - scene.textRect.x * 2;
        scene.textRect.h = scene.messageRect.y - scene.textRect.y - 16.0f;

        SDL_FRect editRect = scene.textRect;
        editRect.x += 4.0f;
        editRect.y += 4.0f;
        editRect.w -= 8.0f;
        editRect.w -= 8.0f;
        scene.edit = EditBox_Create(scene.window, scene.renderer, engine, font, &editRect);
        if (scene.edit) {
            TTF_SetTextColor(scene.edit->text, forecol->r, forecol->g, forecol->b, forecol->a);

            EditBox_Insert(scene.edit, message);
        }
    }

    /* Wait for a keystroke, and blit text on mouse press */
    while (!scene.done) {
        while (SDL_PollEvent(&event)) {
            SDL_ConvertEventToRenderCoordinates(scene.renderer, &event);

            switch (event.type) {
                case SDL_EVENT_MOUSE_BUTTON_DOWN:
                    if (!EditBox_HandleEvent(scene.edit, &event)) {
                        scene.messageRect.x = (event.button.x - text->w/2);
                        scene.messageRect.y = (event.button.y - text->h/2);
                        scene.messageRect.w = (float)text->w;
                        scene.messageRect.h = (float)text->h;
                    }
                    break;

                case SDL_EVENT_KEY_DOWN:
                    if (!EditBox_HandleEvent(scene.edit, &event)) {
                        HandleKeyDown(&scene, &event);
                    }
                    break;

                case SDL_EVENT_QUIT:
                    scene.done = true;
                    break;

                default:
                    EditBox_HandleEvent(scene.edit, &event);
                    break;
            }
        }
        DrawScene(&scene);
    }
    result = 0;

done:
    SDL_DestroySurface(text);
    EditBox_Destroy(scene.edit);
    TTF_DestroyText(scene.caption);
    for (i = 0; i < num_fallbacks; ++i) {
        TTF_CloseFont(fallback_fonts[i]);
    }
    TTF_CloseFont(font);
    switch (scene.textEngine) {
    case TextEngineSurface:
        TTF_DestroySurfaceTextEngine(engine);
        break;
    case TextEngineRenderer:
        TTF_DestroyRendererTextEngine(engine);
        break;
    default:
        break;
    }
    SDL_DestroyTexture(scene.message);
    TTF_Quit();
    SDL_Quit();
    return result;
}
