#include "SDL.h"
#include "SDL_ttf.h"

#include <map>
#include <string>
#include <vector>
#include <exception>


namespace Style
{
    enum {
        BOLD      = 1 << 24,  // bold font
        UNDERLINE = 1 << 25,  // underline text
        STANDOUT  = 1 << 26,  // inverse bg/fg
        BLINK     = 1 << 27,  // blinking
    };
};


namespace KeyMod
{
    enum {
        SHIFT = 1<<0,
        ALT   = 1<<1,
        CTRL  = 1<<2,
        META  = 1<<3,
    };
};


class ColorMap
{
private:
    Uint8 _map[16][3] = {
        {0,0,0},        // 0 - black
        {23,23,178},    // 1 - blue
        {23,178,23},    // 2 - green
        {23,178,178},   // 3 - cyan
        {178,23,23},    // 4 - red
        {178,23,178},   // 5 - magenta
        {178,103,23},   // 6 - brown
        {178,178,178},  // 7 - light gray
        {104,104,104},  // 8 - gray
        {84,84,255},    // 9 - light blue
        {84,255,84},    // 10 - light green
        {84,255,255},   // 11 - light cyan
        {255,84,84},    // 12 - light red
        {255,84,255},   // 13 - light magenta
        {255,255,84},   // 14 - yellow
        {255,255,255},  // 15 - white
    };

public:
    void index_to_rgb(int index, SDL_Color &color) const;
};


class GlyphCache
{
public:
    // lookup glyph in cache, id is combined char and attr
    SDL_Surface *lookup_glyph(Uint64 id);
    void put_glyph(Uint64 id, SDL_Surface *srf);
    void flush();

private:
    std::map<Uint64, SDL_Surface*> _glyph_map;
};


/* TTF font glyph renderer
 *
 * Renders uniformly sized cells with single character.
 * Wraps SDL_ttf functions, see its documentation here:
 *
 * http://www.libsdl.org/projects/SDL_ttf/docs/SDL_ttf.html
 *
 */
class GlyphRenderer
{
public:
    GlyphRenderer();
    ~GlyphRenderer();

    void open_font(const char *fname_regular, const char *fname_bold, int ptsize);
    void close_font();

    // do not free surface returned!
    SDL_Surface *render_cell(Uint32 ch, Uint32 attr, bool blink_state);

    int get_cell_width() const { return _cell_width; };
    int get_cell_height() const { return _cell_height; };

private:
    TTF_Font *_font_regular;
    TTF_Font *_font_bold;
    int _cell_width;
    int _cell_height;
    GlyphCache _cache;
    ColorMap _colormap;

    void _render_glyph(SDL_Surface *cell_surface, TTF_Font *font, Uint32 ch,
            SDL_Color fgcolor, SDL_Color bgcolor);
};


/* One cell of terminal window
 *
 * Each cell contains one character, its color and attributes.
 * Character is encoded in 32bit unicode.
 * Other 32 bits are attributes:
 *   0-7   (8b) - foreground color index
 *   8-15  (8b) - background color index
 *   16-23 (8b) - RESERVED for alpha channel
 *   24    (1b) - bold font
 *   25    (1b) - underline
 *   26    (1b) - standout (swap fg/bg color)
 *   27    (1b) - blink
 *   28-31 (4b) - RESERVED
 */
struct TerminalCell
{
    Uint32 ch;
    Uint32 attr;

    TerminalCell() : ch(0), attr(7) {};
    bool operator !=(const TerminalCell &rhs) const { return ch != rhs.ch || attr != rhs.attr; };
};


class TerminalScreen
{
public:
    TerminalScreen():
        _screen_surface(NULL), _cells_front(0), _cells_back(0), _render(),
        _pixel_width(0), _pixel_height(0), _width(0), _height(0),
        _cell_width(0), _cell_height(0), _blink_state(1) {};
    ~TerminalScreen() {};

    void select_font(const char *fname_regular, const char *fname_bold, int ptsize);
    void resize(int pxwidth, int pxheight);

    void erase();
    void putch(int x, int y, Uint32 ch, Uint32 attr);
    void toggle_cursor(int x, int y);
    void toggle_blink() { _blink_state = !_blink_state; _draw_blink(); };
    void commit();

    // force full redraw on next commit()
    void redraw();

    int get_width() const { return _width; };
    int get_height() const { return _height; };
    int get_cell_width() const { return _cell_width; };
    int get_cell_height() const { return _cell_height; };

private:
    SDL_Surface *_screen_surface;
    std::vector<TerminalCell> _cells_front;
    std::vector<TerminalCell> _cells_back;
    GlyphRenderer _render;

    int _pixel_width;  // terminal window width in pixels
    int _pixel_height;
    int _width;  // width in characters
    int _height; // height in characters
    int _cell_width;  // character cell width in pixels
    int _cell_height;

    int _blink_state; // 0 - blink chars hidden, 1 - visible

    void _reset_cells();
    void _draw_blink();
};


struct Event
{
    enum { QUIT, RESIZE, KEYPRESS, MOUSEDOWN, MOUSEUP, MOUSEMOVE, MOUSEHOVER, MOUSEWHEEL };
    int type;

    union
    {
        struct
        {
            char keyname[10];
            Uint32 unicode;
            int mod;
        } key;
        struct
        {
            int x, y;
            int relx, rely;
            int button;
        } mouse;
    };
};


class Terminal
{
public:
    Terminal();
    ~Terminal();

    void select_font(const char *fname_regular, const char *fname_bold, int ptsize)
        { _screen.select_font(fname_regular, fname_bold, ptsize); };
    void resize(int pxwidth, int pxheight) { _screen.resize(pxwidth, pxheight); };

    void erase() { _screen.erase(); };
    void putch(int x, int y, Uint32 ch) { _screen.putch(x, y, ch, _attr); };
    void commit();

    Uint32 prepare_attr(Uint8 fg, Uint8 bg, Uint32 style) { return (Uint32)fg | (Uint32)bg << 8 | (style & 0xFF000000); };
    void set_attr(Uint32 value) { _attr = value; };

    void show_cursor(int x, int y) { _cursor_x = x; _cursor_y = y; _cursor_visible = true; };
    void hide_cursor() { _cursor_visible = false; };

    /* Wait for an event.
     *
     * Timeout is in miliseconds, zero means wait indefinitely.
     * Returns false on timeout, true on event.
     */
    bool wait_event(Event &event, Uint32 timeout);

    int get_width() const { return _screen.get_width(); };
    int get_height() const { return _screen.get_height(); };

private:
    TerminalScreen _screen;
    Uint32 _attr;
    int _cursor_x;
    int _cursor_y;
    bool _cursor_visible;

    int _mousemove_last_x;
    int _mousemove_last_y;

    bool _handle_event(const SDL_Event &sdl_event, Event &event);
    const char *_translate_keyname(SDLKey sym);
    int _translate_mod(SDLMod mod);
    static Uint32 _wait_event_callback(Uint32 interval, void *param);
    static Uint32 _blink_toggle_callback(Uint32 interval, void *param);
};


class SDLTermError: public std::exception
{
public:
    SDLTermError(const std::string &a_msg)
        : std::exception(), _msg(a_msg) {};
    virtual ~SDLTermError() throw() {};

    virtual const char *what() const throw() { return _msg.c_str(); };

private:
    std::string _msg;
};

