#ifndef GFDDRAWINGCONTEXT_H
#define GFDDRAWINGCONTEXT_H

#include "gfd_grifo.h"
//#include "gfd_font.h"
struct gfd_font256_tag_t;

#include <stdarg.h>

class GfdDrawingContext;
class GrifoDrawingContext;

extern GfdDrawingContext& GfdDrawingContext_default;

class GfdDrawingContext
{
public:
  typedef int color_t;

  GfdDrawingContext()
  {
    initialize();
  }

  GfdDrawingContext(const GfdDrawingContext& baseCx):
    cx(baseCx.cx)
  {
  }

  struct Size
  {
    int dx;
    int dy;

    Size(int sx=0, int sy=0): dx(sx), dy(sy) {}
    Size(const Size& s): dx(s.dx), dy(s.dy) {}

    int width(int x) { return dx=x;}
    int hieght(int y) { return dy=y;}

    int width() const { return dx;}
    int height() const { return dy;}

    Size& operator+=(const Size& a) { dx+=a.dx; dy+=a.dy; return *this;}
    Size& operator+=(int k) { dx+=k; dy+=k; return *this;}
    Size& operator-=(int k) { return operator+=(-k);}
    Size operator+(const Size& a) const { return Size(dx+a.dx, dy+a.dy);}
    Size operator+(int k) const { return Size(dx+k, dy+k);}
    Size operator-(int k) const { return operator+(-k);}
    void operator=(const Size& s) { dx=s.dx; dy=s.dy;}

    bool operator==(const Size& s) const { return dx==s.dx && dy==s.dy;}
  };
  class Point: public Size {};

  int width() const { return cx.fb->dx;}
  int height() const { return cx.fb->dy;}
  int endx() const { return cx.fb->ex;}
  int endy() const { return cx.fb->ey;}

  void clear() const { gfd_fb_clear(cx.fb);}
  void clearPhy() const { gfd_fb_clear_phy(cx.fb);}
  void fill(int c) const { gfd_fb_fill(cx.fb, c);}

  // Funzioni grafiche maincolor
  void line(int x1, int y1, int x2, int y2) const { gfd_fb_draw_line(cx.fb, x1, y1, x2, y2, cx.main_color);}
  void rect(int x1, int y1, int x2, int y2) const { gfd_fb_draw_rect(cx.fb, x1, y1, x2, y2, cx.main_color);}
  void rectFill(int x, int y, int w, int h) const { gfd_fb_fill_rect(cx.fb, x, y, w, h, cx.main_color);}
  void rectHollow(int x, int y, int w, int h) const { gfd_fb_draw_rect(cx.fb, x, y, x+(w-1), y+(h-1), cx.main_color);}
  void circle(int x, int y, int r) const { gfd_fb_draw_circle(cx.fb, x, y, r, cx.main_color);}
  void arc(int x, int y, int r, double ang_start, double ang_stop) const { gfd_fb_draw_arc179(cx.fb, x, y, r, ang_start, ang_stop, cx.main_color);}
  void rect(int x1, int y1, int x2, int y2, int color) const { gfd_fb_draw_rect(cx.fb, x1, y1, x2, y2, color);}
  void rect(int x1, int y1, const Size& s) const { gfd_fb_draw_rect(cx.fb, x1, y1, (x1+s.dx)-1, (y1+s.dy)-1, cx.main_color);}

    // Funzioni grafiche arc whid 1 px e parametro per occlusione
  int cntCircPix(int r) const {return gfd_count_circle_pix(r);}
  void arcW2Occ(int x, int y, int r, double ang_start, double ang_stop, bool occlusion, int cnt) const { gfd_fb_draw_arc179_w2_occ(cx.fb, x, y, r, ang_start, ang_stop, cx.main_color, cx.occl_color, occlusion, cnt);}

  // Funzioni grafiche di occlusione
  void lineOcclusion(int x1, int y1, int x2, int y2) const { gfd_fb_draw_line(cx.fb, x1, y1, x2, y2, cx.occl_color);}
  void rectOcclusion(int x1, int y1, int x2, int y2) const { gfd_fb_draw_rect(cx.fb, x1, y1, x2, y2, cx.occl_color);}
  void rectFillOcclusion(int x, int y, int w, int h) const { gfd_fb_fill_rect(cx.fb, x, y, w, h, cx.occl_color);}
  void rectHollowOcclusion(int x, int y, int w, int h) const { gfd_fb_draw_rect(cx.fb, x, y, x+(w-1), y+(h-1), cx.occl_color);}
  void rectHollowOuterOcc(int x, int y, int w, int h) const { gfd_fb_draw_rect(cx.fb, x-1, y-1, x+w, y+h, cx.occl_color);}
  void rectHollowOuterOccLH(int x, int y, int w, int h) const { gfd_fb_draw_rect_limH(cx.fb, x-1, y-1, x+w, y+h, cx.occl_color);}
  void rectHollowOuterOccLV(int x, int y, int w, int h) const { gfd_fb_draw_rect_limV(cx.fb, x-1, y-1, x+w, y+h, cx.occl_color);}
  void circleOcclusion(int x, int y, int r) const { gfd_fb_draw_circle(cx.fb, x, y, r, cx.occl_color);}
  void arcOcclusion(int x, int y, int r, double ang_start, double ang_stop) const { gfd_fb_draw_arc179(cx.fb, x, y, r, ang_start, ang_stop, cx.occl_color);}

  // Funzioni grafiche con parametro di occlusione
  void line_w2(int x1, int y1, int x2, int y2, bool occlusion) const { gfd_fb_draw_line_w2(cx.fb, x1, y1, x2, y2, cx.main_color, cx.occl_color, occlusion);}
  void line_w1(int x1, int y1, int x2, int y2, bool occlusion) const { gfd_fb_draw_line_w1(cx.fb, x1, y1, x2, y2, cx.main_color, cx.occl_color, occlusion);}
  // Questa funzione e' messa qui solo per ricordarsi del mondo gfd_grifo.h non ancora completato e usato
  void line_w2TODO(int x1, int y1, int x2, int y2, bool /*occlusion*/) const { LineDraw_W2O(cx.fb, x1, y1, x2, y2, cx.main_color);}

  // Sprites
  void sprite(int x, int y, gfd_framebuffer_t* const bitmap, bool drawOcclusion) const { gfd_fb_draw_sprite(cx.fb, x, y, bitmap, cx.main_color, drawOcclusion?1:0);}
  void spriteOccEx(int x, int y, gfd_framebuffer_t* const bitmap, bool drawOcclusion) const { gfd_fb_draw_sprite_occ_ex(cx.fb, x, y, bitmap, cx.main_color, drawOcclusion?1:0);}
  void spriteLine_w2(int x1, int y1, int x2, int y2, gfd_framebuffer_t* const bitmap, int color) const { gfd_fb_draw_line_w2(bitmap, x1, y1, x2, y2, color, 0, false);}

  int printf(int x, int y, const char* fmt, ...);
  int vprintf(int x, int y, const char* fmt, va_list args);
  int printf(const char* fmt, ...);

  int textWidth(const char* fmt, ...) const;
  int vtextWidth(const char* fmt, va_list args) const;
  int textHeight() const;
  int textLen(const char* text, int maxCnt);
  char* textCpy(char *dest, const char *source, int count);

  color_t getColor() { return cx.main_color; }
  void setColor(color_t c) { cx.main_color = c; }
  void setOcclusionColor(color_t c) { cx.occl_color = c; }
  void setFont(const gfd_font256_tag_t& f) { cx.font = &f; }

  void setTransperentColorLevel(int level) { cx.trsp_color=level;}
  int transparentColorLevel() const { return cx.trsp_color;}

  gfd_framebuffer_t* framebuffer() const { return cx.fb;}
  void* raw_framebuffer() const { return cx.fb->buffer;}
  gfd_framebuffer_t* switch_framebuffer(gfd_framebuffer_t* newFb) { gfd_framebuffer_t* tmp=cx.fb; cx.fb=newFb;return tmp;}
  void new_subbbox(gfd_framebuffer_t& newfbBBox, int ox, int oy, int width, int height) const { gfd_fb_subbbox(&newfbBBox, cx.fb, ox, oy, width, height);}

  bool is_inside_x(int x) const { return ((unsigned int)x)<(unsigned int)cx.fb->dx;}
  bool is_inside_y(int y) const { return ((unsigned int)y)<(unsigned int)cx.fb->dy;}
  bool is_inside(int x, int y) const { return is_inside_x(x) && is_inside_y(y);}

  color_t getPixColor(int x, int y) { return gfd_fb_get_pixel2(cx.fb, x, y);}

  //The dbg interface
  void dbgLine(int x1, int y1, int x2, int y2) const { gfd_fb_draw_line_cs(cx.fb, x1, y1, x2, y2, cx.dbg_color);}
  void dbgRect(int x1, int y1, int x2, int y2) const { gfd_fb_draw_rect_cs(cx.fb, x1, y1, x2, y2, cx.dbg_color);}
  void dbgRect(int x1, int y1, const Size& s) const { gfd_fb_draw_rect_cs(cx.fb, x1, y1, (x1+s.dx)-1, (y1+s.dy)-1, cx.dbg_color);}
  void dbgLineOccMix(int x1, int y1, int x2, int y2) const { gfd_fb_draw_line_cs_2cols(cx.fb, x1, y1, x2, y2, cx.dbg_color, cx.occl_color);}
  void dbgCircleOccMix(int x, int y, int r) const { gfd_fb_draw_circle_2cols(cx.fb, x, y, r, cx.dbg_color, cx.occl_color);}

  int dbgPrintf(int x, int y, const char* fmt, ...);
  int dbgVprintf(int x, int y, const char* fmt, va_list args);

  int dbgPrintfColor(int x, int y, int color, const char* fmt, ...);
  int dbgVprintfColor(int x, int y, int color, const char* fmt, va_list args);

protected:
  struct cx_t
  {
    int main_color;
    int occl_color;
    int dbg_color;
    int trsp_color;
    const struct gfd_font256_tag_t* font;
    const struct gfd_font256_tag_t* dbg_font;
    gfd_framebuffer_t* fb;
    int crs_x;
    int crs_y;
  };
  cx_t cx;

  void initialize();
  void setFb(gfd_framebuffer_t& fb) { cx.fb=&fb;}
  friend class GrifoDrawingContext;
};


class GrifoDrawingContext
{
public:
  GrifoDrawingContext():
    current(&wholeDisplay)
  {
  }

  GfdDrawingContext& dc() const { return *current;}

  static GfdDrawingContext& phyDisplay() { return phyDeviceDisplay;}

  static GfdDrawingContext& displayDc() { return wholeDisplay;}
  static GfdDrawingContext& movingAreaDc() { return movingArea;}

  static void setup(gfd_framebuffer_t& fb,
		  int rsx, int rsy,	int rw, int rh,	//Radar sub-window
		  int sx, int sy, int mw, int mh);	//Moving symbols sub-window

  static void setFramebufferMemory(void*);

  static void setColor(GfdDrawingContext::color_t c) { displayDc().setColor(c); movingAreaDc().setColor(c); phyDeviceDisplay.setColor(c);}
  static void setOcclusionColor(GfdDrawingContext::color_t c) { displayDc().setOcclusionColor(c); movingAreaDc().setOcclusionColor(c); phyDeviceDisplay.setOcclusionColor(c);}
  static void setFont(const gfd_font256_tag_t& f) { displayDc().setFont(f); movingAreaDc().setFont(f); phyDeviceDisplay.setFont(f);}
  static void setDebug();

  static const gfd_font256_tag_t& fontSmall();
  static const gfd_font256_tag_t& fontMicro();
  static const gfd_font256_tag_t& fontLarge();
  static const gfd_font256_tag_t& fontDebug();

protected:
  GfdDrawingContext* current;
  static GfdDrawingContext wholeDisplay;
  static GfdDrawingContext movingArea;
  static GfdDrawingContext phyDeviceDisplay;

private:

};



#endif // GFDDRAWINGCONTEXT_H
