#ifndef GRAPH_GUARD
#define GRAPH_GUARD 1

#include "Point.h"
#include <vector>
//#include<string>
//#include<cmath>
#include "fltk.h"
#include "std_lib_facilities.h"

namespace Graph_lib {
// defense against ill-behaved Linux macros:
#undef major
#undef minor

struct Color
{
	enum Color_type {
		red = FL_RED,
		blue = FL_BLUE,
		green = FL_GREEN,
		yellow = FL_YELLOW,
		white = FL_WHITE,
		black = FL_BLACK,
		magenta = FL_MAGENTA,
		cyan = FL_CYAN,
		dark_red = FL_DARK_RED,
		dark_green = FL_DARK_GREEN,
		dark_yellow = FL_DARK_YELLOW,
		dark_blue = FL_DARK_BLUE,
		dark_magenta = FL_DARK_MAGENTA,
		dark_cyan = FL_DARK_CYAN,
		gray = FL_GRAY,
		mid_gray = 48,
		dark_gray = 38,
		light_gray = 50
	};
	enum Transparency { invisible = 0, visible = 255 };

	Color(Color_type cc) : v(visible), c(Fl_Color(cc))
	{}
	Color(Color_type cc, Transparency vv) : v(vv), c(Fl_Color(cc))
	{}
	Color(int cc) : v(visible), c(Fl_Color(cc))
	{}
	Color(Transparency vv) : v(vv), c(Fl_Color())
	{}

	int as_int() const
	{
		return c;
	}
	char visibility() const
	{
		return v;
	}
	void set_visibility(Transparency vv)
	{
		v = vv;
	}

private:
	unsigned char v; // 0 or 1 for now
	Fl_Color c;
};

struct Line_style
{
	enum Line_style_type {
		solid = FL_SOLID,		   // -------
		dash = FL_DASH,			   // - - - -
		dot = FL_DOT,			   // .......
		dashdot = FL_DASHDOT,	   // - . - .
		dashdotdot = FL_DASHDOTDOT // -..-..
	};
	Line_style(Line_style_type ss) : s(ss), w(0)
	{}
	Line_style(Line_style_type lst, int ww) : s(lst), w(ww)
	{}
	Line_style(int ss) : s(ss), w(0)
	{}

	int width() const
	{
		return w;
	}
	int style() const
	{
		return s;
	}

private:
	int s;
	int w;
};

class Font
{
public:
	enum Font_type {
		helvetica = FL_HELVETICA,
		helvetica_bold = FL_HELVETICA_BOLD,
		helvetica_italic = FL_HELVETICA_ITALIC,
		helvetica_bold_italic = FL_HELVETICA_BOLD_ITALIC,
		courier = FL_COURIER,
		courier_bold = FL_COURIER_BOLD,
		courier_italic = FL_COURIER_ITALIC,
		courier_bold_italic = FL_COURIER_BOLD_ITALIC,
		times = FL_TIMES,
		times_bold = FL_TIMES_BOLD,
		times_italic = FL_TIMES_ITALIC,
		times_bold_italic = FL_TIMES_BOLD_ITALIC,
		symbol = FL_SYMBOL,
		screen = FL_SCREEN,
		screen_bold = FL_SCREEN_BOLD,
		zapf_dingbats = FL_ZAPF_DINGBATS
	};

	Font(Font_type ff) : f(ff)
	{}
	Font(int ff) : f(ff)
	{}

	int as_int() const
	{
		return f;
	}

private:
	int f;
};

template <class T> class Vector_ref
{
	vector<T*> v;
	vector<T*> owned;

public:
	Vector_ref()
	{}

	Vector_ref(T* a, T* b = 0, T* c = 0, T* d = 0)
	{
		if (a)
			push_back(a);
		if (b)
			push_back(b);
		if (c)
			push_back(c);
		if (d)
			push_back(d);
	}

	~Vector_ref()
	{
		for (typename vector<T*>::size_type i = 0; i < owned.size(); ++i)
			delete owned[i];
	}

	void push_back(T& s)
	{
		v.push_back(&s);
	}
	void push_back(T* p)
	{
		v.push_back(p);
		owned.push_back(p);
	}

	T& operator[](int i)
	{
		return *v[i];
	}
	const T& operator[](int i) const
	{
		return *v[i];
	}
	int size() const
	{
		return v.size();
	}
	T& back()
	{
		return *v[v.size() - 1];
	}
	const T& back() const
	{
		return *v[v.size() - 1];
	}

	using iterator = typename vector<T*>::iterator;
	iterator begin()
	{
		return v.begin();
	}
	auto end()
	{
		return v.end();
	}

	auto begin() const
	{
		return v.begin();
	}
	auto end() const
	{
		return v.end();
	}
};

typedef double Fct(double);

class Shape
{ // deals with color and style, and holds sequence of lines
protected:
	Shape()
	{}
	// Not by the book:
	// Shape(initializer_list<Point> lst); // add() the Points to this Shape
	// Changed init_list constructor, i.e. implemented at all
	Shape(initializer_list<Point> lst)
	{
		for (Point p : lst)
			add(p);
	} // add() the Points to this Shape

	void add(Point p)
	{
		points.push_back(p);
	}
	void set_point(int i, Point p)
	{
		points[i] = p;
	}

public:
	void draw() const; // deal with color and draw_lines
protected:
	virtual void draw_lines() const; // simply draw the appropriate lines
public:
	virtual void move(int dx, int dy); // move the shape +=dx and +=dy

	void set_color(Color col)
	{
		lcolor = col;
	}
	Color color() const
	{
		return lcolor;
	}

	void set_style(Line_style sty)
	{
		ls = sty;
	}
	Line_style style() const
	{
		return ls;
	}

	void set_fill_color(Color col)
	{
		fcolor = col;
	}
	Color fill_color() const
	{
		return fcolor;
	}

	Point point(int i) const
	{
		return points[i];
	}
	int number_of_points() const
	{
		return int(points.size());
	}

	virtual ~Shape()
	{}
	Shape(const Shape&) = delete;
	Shape& operator=(const Shape&) = delete;

private:
	vector<Point> points; // not used by all shapes
	Color lcolor{static_cast<int>(fl_color())};
	Line_style ls{0};
	Color fcolor{Color::invisible};
};

struct Function : Shape
{
	// the function parameters are not stored
	Function(Fct f, double r1, double r2, Point orig, int count = 100, double xscale = 25,
			 double yscale = 25);
};

struct Fill
{
	Fill() : no_fill(true), fcolor(0)
	{}
	Fill(Color c) : no_fill(false), fcolor(c)
	{}

	void set_fill_color(Color col)
	{
		fcolor = col;
	}
	Color fill_color()
	{
		return fcolor;
	}

protected:
	bool no_fill;
	Color fcolor;
};

struct Line : Shape
{
	Line(Point p1, Point p2)
	{
		add(p1);
		add(p2);
	}
};

struct Rectangle : Shape
{

	Rectangle(Point xy, int ww, int hh) : h{hh}, w{ww}
	{
		if (h <= 0 || w <= 0)
			error("Bad rectangle: non-positive side");
		add(xy);
	}
	Rectangle(Point x, Point y) : h{y.y - x.y}, w{y.x - x.x}
	{
		if (h <= 0 || w <= 0)
			error("Bad rectangle: first point is not top left");
		add(x);
	}
	void draw_lines() const;

	int height() const
	{
		return h;
	}
	int width() const
	{
		return w;
	}

private:
	int h; // height
	int w; // width
};

bool intersect(Point p1, Point p2, Point p3, Point p4);


struct Open_polyline : Shape
{ // open sequence of lines
	using Shape::Shape;
	void add(Point p)
	{
		Shape::add(p);
	}
	void draw_lines() const;
};

struct Closed_polyline : Open_polyline
{ // closed sequence of lines
	using Open_polyline::Open_polyline;
	void draw_lines() const;

	//	void add(Point p) { Shape::add(p); }
};


struct Polygon : Closed_polyline
{ // closed sequence of non-intersecting lines
	using Closed_polyline::Closed_polyline;
	void add(Point p);
	void draw_lines() const;
};

struct Lines : Shape
{ // indepentdent lines
	Lines()
	{}
	Lines(initializer_list<Point> lst) : Shape{lst}
	// Not equal to book constructor.
	{
		if (lst.size() % 2)
			error("odd number of points for Lines");
	}

	Lines(initializer_list<pair<Point, Point>> lst)
	{
		// This is the constructor from the book, p.450 1st print.
		for (auto p : lst)
			add(p.first, p.second);
	}

	void draw_lines() const;
	void add(Point p1, Point p2)
	{
		Shape::add(p1);
		Shape::add(p2);
	}
};

struct Text : Shape
{
	// the point is the bottom left of the first letter
	Text(Point x, const string& s) : lab{s}
	{
		add(x);
	}

	void draw_lines() const;

	void set_label(const string& s)
	{
		lab = s;
	}
	string label() const
	{
		return lab;
	}

	void set_font(Font f)
	{
		fnt = f;
	}
	Font font() const
	{
		return Font(fnt);
	}

	void set_font_size(int s)
	{
		fnt_sz = s;
	}
	int font_size() const
	{
		return fnt_sz;
	}

private:
	string lab; // label
	Font fnt{fl_font()};
	int fnt_sz{(14 < fl_size()) ? fl_size() : 14}; // at least 14 point
};


struct Axis : Shape
{
	// representation left public
	enum Orientation { x, y, z };
	Axis(Orientation d, Point xy, int length, int nummber_of_notches = 0, string label = "");

	void draw_lines() const;
	void move(int dx, int dy);

	void set_color(Color c);

	Text label;
	Lines notches;
	//	Orientation orin;
	//	int notches;
};


struct Circle : Shape
{
	Circle(Point p, int rr) // center and radius
		: r{rr}
	{
		add(Point{p.x - r, p.y - r});
	}

	void draw_lines() const;

	Point center() const
	{
		return {point(0).x + r, point(0).y + r};
	}

	void set_radius(int rr)
	{
		r = rr;
	}
	int radius() const
	{
		return r;
	}

private:
	int r;
};


struct Ellipse : Shape
{
	Ellipse(Point p, int ww,
			int hh) // center, min, and max distance from center
		: w{ww}, h{hh}
	{
		add(Point{p.x - ww, p.y - hh});
	}

	void draw_lines() const;

	Point center() const
	{
		return {point(0).x + w, point(0).y + h};
	}
	Point focus1() const
	{
		return {center().x + int(sqrt(double(w * w - h * h))), center().y};
	}
	Point focus2() const
	{
		return {center().x - int(sqrt(double(w * w - h * h))), center().y};
	}

	void set_major(int ww)
	{
		w = ww;
	}
	int major() const
	{
		return w;
	}
	void set_minor(int hh)
	{
		h = hh;
	}
	int minor() const
	{
		return h;
	}

private:
	int w;
	int h;
};

struct Marked_polyline : Open_polyline
{
	Marked_polyline(const string& m) : mark(m)
	{}
	void draw_lines() const;

private:
	string mark;
};

struct Marks : Marked_polyline
{
	Marks(const string& m) : Marked_polyline(m)
	{
		set_color(Color(Color::invisible));
	}
};

struct Mark : Marks
{
	Mark(Point xy, char c) : Marks(string(1, c))
	{
		add(xy);
	}
};

struct Bad_image : Fl_Image
{
	Bad_image(int h, int w) : Fl_Image(h, w, 0)
	{}
	void draw(int x, int y, int, int, int, int)
	{
		draw_empty(x, y);
	}
};

struct Suffix
{
	enum Encoding { none, jpg, gif, bmp };
};

Suffix::Encoding get_encoding(const string& s);

struct Image : Shape
{
	Image(Point xy, string s, Suffix::Encoding e = Suffix::none);
	~Image()
	{
		delete p;
	}
	void draw_lines() const;
	void set_mask(Point xy, int ww, int hh)
	{
		w = ww;
		h = hh;
		cx = xy.x;
		cy = xy.y;
	}
	void move(int dx, int dy)
	{
		Shape::move(dx, dy);
		p->draw(point(0).x, point(0).y);
	}

private:
	int w, h, cx, cy; // define "masking box" within image relative to
					  // position (cx,cy)
	Fl_Image* p;
	Text fn;
};

} // namespace Graph_lib
#endif
