/*
 * Copyright (C) 2002,2003,2004 Daniel Heck
 * Copyright (C) 2006,2007      Ronald Lamprecht
 *
 * 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 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, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
#include "ecl_font.hh"
#include "ecl_geom.hh"
#include "ecl_utf.hh"
#include "ecl_video.hh"
#include <vector>
#include <string>
#include <memory>
#include <stdio.h>
#include <cstdlib>
#include <ostream>
#include <iostream>

#include <config.h>

using namespace ecl;
using namespace std;

std::string::size_type ecl::breakString(Font *font, const std::string &str,
                                        const std::string &breakChars, int targetWidth) {
    if (font->get_width(str) <= targetWidth)
        return str.size();  // the complete string fits into a line

    bool breakFound = false;
    std::string::size_type pos = 0;
    while (true) {
        std::string::size_type nextpos = str.find_first_of(breakChars, pos);

        if (nextpos == std::string::npos)
            // no more line breaks
            return breakFound ? pos : str.size();

        if (font->get_width(str.substr(0, nextpos + 1)) > targetWidth)
            // now the string is too long
            return breakFound ? pos : nextpos + 1;

        pos = nextpos + 1;
        breakFound = true;
    }
}

std::vector<std::string> ecl::breakToLines(Font *font, const std::string &str,
                                   const std::string &breakChars, int targetWidth) {
    std::vector<std::string> lines;
    if (str.size() == 0)
        return lines;
    std::string::size_type breakPos = breakString(font, str, breakChars, targetWidth);
    lines = breakToLines(font, str.substr(breakPos), breakChars, targetWidth);
    if ((breakPos > 0) && (str.substr(breakPos-1,1) == " "))
        breakPos -= 1;
    lines.insert(lines.begin(), str.substr(0, breakPos).c_str());
    return lines;
}

//
// Bitmap fonts
//

namespace {
class InvalidFont {};

class BitmapFont : public Font {
    vector<Rect> char_rects;
    vector<int> advance;
    Surface *surface;

public:
    BitmapFont(Surface *s, const char *descr);
    ~BitmapFont() { delete surface; }

    int get_lineskip() { return surface->height() + 3; }
    int get_width(char c);
    virtual int get_width(std::string text, Font *altFont = NULL);
    int get_height();

    virtual Surface *render(std::string text, Font *altFont = nullptr, int maxwidth = -1);
    virtual void render(const GC &gc, int x, int y, std::string text, Font *altFont = NULL,
                        int maxwidth = -1);
};
}

BitmapFont::BitmapFont(Surface *s, const char *descr) : char_rects(256), advance(256), surface(s) {
    // Read and interpret the font description file.
    // expected line format:
    // charno xpos width xadvance

    FILE *fp = fopen(descr, "rt");
    if (!fp)
        return;  // throw InvalidFont();

    int c;
    int x = 0, w = 0, adv = 0;
    while (fscanf(fp, "%d %d %d %d\n", &c, &x, &w, &adv) != EOF) {
        char_rects[c].x = x;
        char_rects[c].w = w;
        char_rects[c].y = 0;
        char_rects[c].h = s->height();
        advance[c] = adv;
        if (adv == 0)
            std::cout << "BitFont 0\n";
    }
}

int BitmapFont::get_width(char c) {
    return advance[int(c)];
}

int BitmapFont::get_width(std::string text, Font *altFont) {
    int width = 0;
    const char *cstr = text.c_str();
    for (const char *p = cstr; *p; ++p) {
        // utf-8 char handling
        int len = utf8NextCharSize(p);  // num of bytes that represents one real character
        if (len == 0) {
            // a spurious follow up byte
            continue;
        }

        if (len > 1 || advance[int(*p)] == 0) {
            if (altFont != nullptr) {
                std::string utf8char(p, len);
                width += altFont->get_width(utf8char);
            }
            p += len - 1;
        } else {
            width += get_width(*p);
        }
    }
    return width;
}

int BitmapFont::get_height() {
    return surface->height();
}

Surface *BitmapFont::render(std::string text, Font *altFont, int maxwidth) {
    Surface *s = MakeSurface(get_width(text, altFont), get_height());
    s->set_color_key(0, 0, 0);
    int width = 0;
    int x = 0;
    const char *cstr = text.c_str();
    for (const char *p = cstr; *p; ++p) {
        // utf-8 char handling
        int len = utf8NextCharSize(p);  // num of bytes that represents one real character
        if (len == 0) {
            // a spurious follow up byte
            continue;
        }

        if (len > 1 || advance[int(*p)] == 0) {
            if (altFont != nullptr) {
                std::string utf8char(p, len);
                int charWidth = altFont->get_width(utf8char);
                width += charWidth;
                if (maxwidth <= 0 || width < maxwidth) {
                    altFont->render(GC(s), x, 1, utf8char);
                    x += altFont->get_width(utf8char);
                }
            }
            p += len - 1;
        } else {
            int charWidth = get_width(*p);
            width += charWidth;
            if (maxwidth <= 0 || width <= maxwidth) {
                blit(GC(s), x, 0, surface, char_rects[int(*p)]);
                x += charWidth;
            }
        }
    }
    return s;
}

void BitmapFont::render(const GC &gc, int x, int y, std::string text, Font *altFont, int maxwidth) {
    Surface *s = render(text, altFont, maxwidth);
    blit(gc, x, y, s);
    delete s;
}

Font *ecl::LoadBitmapFont(const char *imgname, const char *descrname) {
    if (Surface *s = LoadImage(imgname))
        return new BitmapFont(s, descrname);
    return 0;
}

//
// TrueType fonts
//
#include "SDL_ttf.h"

namespace {

class TrueTypeFont : public Font {
    // Variables
    TTF_Font *font;
    SDL_Color fgcolor;

    // Inhibit copying
    TrueTypeFont(const TrueTypeFont &);
    TrueTypeFont &operator=(const TrueTypeFont &);

public:
    TrueTypeFont(TTF_Font *font_, int r, int g, int b);

    ~TrueTypeFont();

    // Font interface
    int get_lineskip();
    int get_height();
    int get_width(char c);
    virtual int get_width(std::string text, Font *altFont = NULL);

    Surface *render(std::string text, Font *altFont = nullptr, int maxwidth = -1);
    void render(const GC &gc, int x, int y, std::string text, Font *altFont = nullptr, int maxwidth = -1);

private:
    SDL_PixelFormat *pixel_format;
};

}  // namespace

TrueTypeFont::TrueTypeFont(TTF_Font *font_, int r, int g, int b) : font(font_) {
    fgcolor.r = r;
    fgcolor.g = g;
    fgcolor.b = b;
    pixel_format = SDL_AllocFormat(SDL_PIXELFORMAT_RGB888);
}

TrueTypeFont::~TrueTypeFont() {
    SDL_FreeFormat(pixel_format);
    TTF_CloseFont(font);
}

int TrueTypeFont::get_lineskip() {
    return TTF_FontLineSkip(font);
}
int TrueTypeFont::get_height() {
    return TTF_FontHeight(font);
}

int TrueTypeFont::get_width(char c) {
    int minx, maxx, miny, maxy, advance;
    TTF_GlyphMetrics(font, c, &minx, &maxx, &miny, &maxy, &advance);
    return advance;
}

Surface *TrueTypeFont::render(std::string text, Font *altFont, int maxwidth) {
    // Note: altFont is only used by BitmapFont.
    // TODO: Implement maxwidth. (Not actually used right now.)
    SDL_Color bgcolor = {0, 0, 0, 0};
    SDL_Surface *si = TTF_RenderUTF8_Shaded(font, text.c_str(), fgcolor, bgcolor);
    if (si) {
        SDL_Surface *s = SDL_ConvertSurface(si, pixel_format, 0);
        SDL_FreeSurface(si);
        SDL_SetColorKey(s, SDL_TRUE, 0);
        return Surface::make_surface(s);
    }
    return MakeSurface(0, get_height());
}

void TrueTypeFont::render(const GC &gc, int x, int y, std::string text, Font *altFont, int maxwidth) {
    std::unique_ptr<Surface> s(render(text, altFont, maxwidth));
    if (s.get())
        blit(gc, x, y, s.get());
}

int TrueTypeFont::get_width(std::string text, Font *altFont) {
    int w, h;
    TTF_SizeUTF8(font, text.c_str(), &w, &h);
    return w;
}

Font *ecl::LoadTTF(const char *filename, int ptsize, int r, int g, int b) {
    if (!TTF_WasInit() && TTF_Init() == -1) {
        fprintf(stderr, "Couldn't initialize SDL_ttf: %s\n", SDL_GetError());
        exit(1);
    }
    TTF_Font *font = TTF_OpenFont(filename, ptsize);
    return (font) ? new TrueTypeFont(font, r, g, b) : 0;
}
