#pragma once

#include "tilecell.h"
#include "coord-def.h"

struct screen_cell_t
{
    // Console output part.
    char32_t glyph;
    unsigned short colour; // TODO: check if this is real colour (8 bit)
    // Tiles output part.
#ifdef USE_TILE
    unsigned short flash_colour = 0;
    unsigned short flash_alpha = 0;
    packed_cell tile;
#endif
};

class crawl_view_buffer
{
public:
    crawl_view_buffer();
    crawl_view_buffer(const coord_def &sz);
    crawl_view_buffer(const crawl_view_buffer &rhs);
    ~crawl_view_buffer();

    coord_def size() const { return m_size; }
    bool empty() const;

    operator screen_cell_t * () { return m_buffer; }
    operator const screen_cell_t * () const { return m_buffer; }
    const crawl_view_buffer & operator =(crawl_view_buffer rhs);

    template<class Indexer>
    screen_cell_t& operator () (const Indexer &i)
    {
        ASSERT(i.x >= 0 && i.y >= 0 && i.x < m_size.x && i.y < m_size.y);
        return m_buffer[i.y*m_size.x + i.x];
    }

    void fill(const screen_cell_t& value);
    void clear();
    void draw();
private:
    void resize(const coord_def &sz);
    coord_def m_size;
    screen_cell_t *m_buffer;
};

struct crawl_view_geometry
{
public:
    coord_def termp;               // Left-top pos of terminal.
    coord_def termsz;              // Size of the terminal.
    coord_def viewp;               // Left-top pos of viewport.
    coord_def viewsz;              // Size of the viewport (play area).
    coord_def hudp;                // Left-top pos of status area.
    coord_def hudsz;               // Size of the status area.
    coord_def msgp;                // Left-top pos of the message pane.
    coord_def msgsz;               // Size of the message pane.
    coord_def mlistp;              // Left-top pos of the monster list.
    coord_def mlistsz;             // Size of the monster list.

    coord_def vgrdc;               // What grid pos is at the centre of the view
                                   // usually you.pos().

    coord_def viewhalfsz;

    coord_def glos1, glos2;        // LOS limit grid coords (inclusive)
    coord_def vlos1, vlos2;        // LOS limit viewport coords (inclusive)

    coord_def mousep;              // Where the mouse is.

private:
    coord_def last_player_pos;

public:
    crawl_view_geometry();
    void init_geometry();

    void init_view();
    void set_player_at(const coord_def &c, bool force_centre = false);
    // Set new location, but preserve scrolling as if the player didn't move.
    void shift_player_to(const coord_def &c);
    // Recalculate vlos1 and vlos2.
    void calc_vlos();

    inline coord_def view2grid(const coord_def &pos) const
    {
        return pos - viewhalfsz + vgrdc - coord_def(1, 1);
    }

    inline coord_def grid2view(const coord_def &pos) const
    {
        return pos - vgrdc + viewhalfsz + coord_def(1, 1);
    }

    inline coord_def view2show(const coord_def &pos) const
    {
        return pos - vlos1;
    }

    inline coord_def show2view(const coord_def &pos) const
    {
        return pos + vlos1;
    }

    inline coord_def grid2show(const coord_def &pos) const
    {
        return view2show(grid2view(pos));
    }

    inline coord_def show2grid(const coord_def &pos) const
    {
        return view2grid(show2view(pos));
    }

    inline coord_def screen2view(const coord_def& pos) const
    {
        return pos - viewp + termp;
    }

    inline coord_def view2screen(const coord_def& pos) const
    {
        return pos + viewp - termp;
    }

    inline coord_def screen2grid(const coord_def& pos) const
    {
        return view2grid(screen2view(pos));
    }

    inline coord_def grid2screen(const coord_def& pos) const
    {
        return view2screen(grid2view(pos));
    }

    coord_def glosc() const
    {
        return (glos1 + glos2) / 2;
    }

    bool in_los_bounds_g(const coord_def &c) const
    {
        return c.x >= glos1.x && c.x <= glos2.x
               && c.y >= glos1.y && c.y <= glos2.y;
    }

    bool in_los_bounds_v(const coord_def &c) const
    {
        return in_los_bounds_g(view2grid(c));
    }

    bool in_viewport_v(const coord_def &c) const
    {
        return c.x > 0 && c.y > 0
               && c.x <= viewsz.x && c.y <= viewsz.y;
    }

    bool in_viewport_s(const coord_def &c) const
    {
        return in_viewport_v(screen2view(c));
    }

    bool in_viewport_g(const coord_def &c) const
    {
        return in_viewport_v(grid2view(c));
    }
};

extern crawl_view_geometry crawl_view;

static inline coord_def view2grid(const coord_def &pos)
{
    return crawl_view.view2grid(pos);
}

static inline coord_def grid2view(const coord_def &pos)
{
    return crawl_view.grid2view(pos);
}

static inline coord_def view2show(const coord_def &pos)
{
    return crawl_view.view2show(pos);
}

static inline coord_def show2view(const coord_def &pos)
{
    return crawl_view.show2view(pos);
}

static inline coord_def grid2show(const coord_def &pos)
{
    return crawl_view.grid2show(pos);
}

static inline coord_def show2grid(const coord_def &pos)
{
    return crawl_view.show2grid(pos);
}

static inline bool in_los_bounds_v(const coord_def& pos)
{
    return crawl_view.in_los_bounds_v(pos);
}

static inline bool in_los_bounds_g(const coord_def& pos)
{
    return crawl_view.in_los_bounds_g(pos);
}
