/**********************************************************************
 *
 *   FreeDoko a Doppelkopf-Game
 *    
 *   Copyright (C) 2001-2006  by Diether Knof and Borg Enders
 *
 *   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 can find this license in the file 'gpl.txt'.
 *
 *   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., 59 Temple Place, Suite 330, Boston, 
 *   MA  02111-1307  USA
 *
 *  Contact:
 *    Diether Knof dknof@gmx.de
 *    Borg Enders  borg@borgsoft.de
 *
 **********************************************************************/

#include "constants.h"

#ifdef USE_UI_GTKMM

#include "party_points.graph.h"

#include "ui.h"
#include "translations.h"

#include "../../party/party.h"
#include "../../party/rule.h"

#include "../../utils/string.h"

#include <gdkmm/pixmap.h>
#include <gdkmm/gc.h>
#include <pangomm/layout.h>

namespace UI_GTKMM_NS {

  /**
   ** constructor
   **
   ** @param	-
   **
   ** @return	-
   **
   ** @author	Diether Knof
   ** 
   ** @version	0.7.3
   **/
  PartyPoints::Graph::Graph(Base* const parent) :
    Base(parent),
    Gtk::DrawingArea(),
    pixmap(NULL),
    gc(NULL)
  {
    //this->show();

    return ;
  } // PartyPoints::Graph::Graph(Base* const parent)

  /**
   ** destructor
   **
   ** @param	-
   **
   ** @return	-
   **
   ** @author	Diether Knof
   ** 
   ** @version	0.7.3
   **/
  PartyPoints::Graph::~Graph()
  { }

  /**
   ** -> result
   **
   ** @param	-
   **
   ** @return	the width of the graph
   **
   ** @author	Diether Knof
   **
   ** @version	0.7.3
   **/
  unsigned
    PartyPoints::Graph::width() const
    {
      return this->get_width();
    } // unsigned PartyPoints::Graph::width() const

  /**
   ** -> result
   **
   ** @param	-
   **
   ** @return	the height of the graph
   **
   ** @author	Diether Knof
   **
   ** @version	0.7.3
   **/
  unsigned
    PartyPoints::Graph::height() const
    {
      return this->get_height();
    } // unsigned PartyPoints::Graph::height() const

  /**
   ** -> result
   **
   ** @param	-
   **
   ** @return	the depth of the graph
   **
   ** @author	Diether Knof
   **
   ** @version	0.7.3
   **/
  unsigned
    PartyPoints::Graph::depth() const
    {
      return this->get_window()->get_depth();
    } // unsigned PartyPoints::Graph::depth() const

  /**
   ** event: redraw part of the window
   **
   ** @param	event	the event
   **
   ** @return	true
   **
   ** @author	Diether Knof
   **
   ** @version	0.7.3
   **/
  bool
    PartyPoints::Graph::on_expose_event(GdkEventExpose* event)
    {
      this->update(Gdk::Rectangle(event->area.x, event->area.y,
				  event->area.width, event->area.height));

      return true;
    } // bool PartyPoints::Graph::on_expose_event(GdkEventExpose* event)

  /**
   ** event: the size has changed
   **
   ** @param	event	the event
   **
   ** @return	true
   **
   ** @author	Diether Knof
   **
   ** @version	0.7.3
   **/
  bool
    PartyPoints::Graph::on_configure_event(GdkEventConfigure* event)
    {
      this->pixmap = Gdk::Pixmap::create(this->get_window(),
					 this->width(), this->height(),
					 this->depth());

      if (!this->gc) {
	this->gc = Gdk::GC::create(this->pixmap);
	this->gc->set_background(Gdk::Color("white"));
	this->gc->set_fill(Gdk::SOLID);
      } // if (!this->gc)

      this->draw_all();

      return true;
    } // bool PartyPoints::Graph::on_configure_event(GdkEventConfigure* event)

  /**
   ** update the full area
   ** (just copy the pixmap to the drawing_area)
   **
   ** @param	-
   **
   ** @return	-
   **
   ** @author	Diether Knof
   **
   ** @version	0.7.3
   **/
  void
    PartyPoints::Graph::update_full_area()
    {
      this->update(Gdk::Rectangle(0, 0, -1, -1));

      return ;
    } // void PartyPoints::Graph::update_full_area()

  /**
   ** update the area of the drawing area
   ** (just copy the area from the pixmap)
   **
   ** @param	area	area to update
   **
   ** @return	-
   **
   ** @author	Diether Knof
   **
   ** @version	0.7.3
   **/
  void
    PartyPoints::Graph::update(Gdk::Rectangle const& area)
    {
      this->get_window()->draw_drawable(this->gc,
					this->pixmap,
					area.get_x(), area.get_y(),
					area.get_x(), area.get_y(),
					area.get_width(), area.get_height());

      return ;
    } // void PartyPoints::Graph::update(Gdk::Rectangle const& area);

  /**
   ** draw the whole graph
   **
   ** @param	-
   **
   ** @return	-
   **
   ** @author	Diether Knof
   **
   ** @version	0.7.3
   **
   ** @todo     add game numbers
   **/
  void
    PartyPoints::Graph::draw_all()
    {
      if (!this->pixmap)
	return ;

      // clear the whole area
      this->gc->set_rgb_fg_color(Gdk::Color("white"));
      this->pixmap->draw_rectangle(this->gc, true,
				   0, 0, -1, -1);


      if (::game_status <= GAMESTATUS::PARTY_NEW) {
	this->update_full_area();
	return ;
      }

      Party const& party = this->ui->party();

      if (   (party.gameno() == 0)
	  && (::game_status < GAMESTATUS::GAME_FINISHED) ) {
	this->update_full_area();
	return ;
      }

      Counting const counting = party.rule()(Rule::COUNTING);

      // calculate the minimum and maximum points made by the players
      int min_points = 0; // minimal points made by the players
      int max_points = 0; // maximal points made by the players

      for (unsigned g = 0; g <= party.gameno(); ++g) {
	if (   (g == party.gameno())
	    && (::game_status != GAMESTATUS::GAME_FINISHED) )
	  break;
	for (unsigned p = 0; p < party.playerno(); ++p) {
	  int const points = party.pointsum_till_game(g, p);
	  if (points < min_points)
	    min_points = points;
	  if (points > max_points)
	    max_points = points;
	} // for (p < party.playerno())
      } // for (g < party.gameno())
      if (max_points == min_points)
	max_points += 1;

      // how many rounds to display
      unsigned const roundsno
	= (party.rule()(Rule::NUMBER_OF_ROUNDS_LIMITED)
	   ? party.rule()(Rule::NUMBER_OF_ROUNDS)
	   : party.roundno());
      // how many games to display
      unsigned gamesno;
      if (party.rule()(Rule::NUMBER_OF_ROUNDS_LIMITED)) {
	gamesno = party.round_startgame(party.rule()(Rule::NUMBER_OF_ROUNDS));
      } else {
	gamesno = party.gameno();
	if (::game_status == GAMESTATUS::GAME_FINISHED)
	  gamesno += 1;
      }


      // layout:
      // +-----------------------------+ border_y
      // | 10+       /                 |
      // |   |     _/                  |
      // |   |   _/  _                 |
      // |  5+  /   /                  | height_graph
      // |   | / __/                   |
      // |   |/ /                      |
      // |  0+----+----+----+----+----+| border_roundno
      // |   0    5   10   15   20   25| height_roundno, border_names
      // |    player1 player2 p3  p4   | height_names
      // +-----------------------------+ border_x
      // border_x width_points border_points width_graph border_x

      // the layout values
      unsigned border_y;
      unsigned height_graph;
      unsigned border_roundno;
      unsigned height_roundno;
      unsigned border_names;
      unsigned height_names;

      unsigned border_x;
      unsigned width_points;
      unsigned border_points;
      unsigned width_graph;

      int zero_x;           // the x position of the zero point of the graph
      int zero_y;           // the y position of the zero point of the graph
      double scale_games;   // the distance beween two games
      double scale_points;  // the distance between two points
      // note: the 'scale_points' distance is negative, so that the points go up
      unsigned points_step; // the steps beween two points markers

      // whether the player names are shown on the bottom (else on the top)
      bool const names_on_bottom = (counting != COUNTING_NS::MINUS);

      // the layout for the name and the description
      Glib::RefPtr<Pango::Layout> text_layout
	= this->create_pango_layout("Serif Bold 14");

      // size of the markers (in each direction)
      int marker_pixel_size = 10;

      { // marker size
	int layout_width, layout_height;
	text_layout->set_text("0");
	text_layout->get_pixel_size(layout_width, layout_height);
	marker_pixel_size = layout_height / 2;
      } // marker size


      { // set the layout values
	{ // y
	  border_y = std::max((this->height() / 50), 2u);
	  border_names = border_y / 2;
	  { // height name
	    height_names = 0;
	    int layout_width, layout_height;
	    for (unsigned p = 0; p < party.playerno(); ++p) {
	      text_layout->set_text(party.player(p).name());
	      text_layout->get_pixel_size(layout_width, layout_height);
	      if (static_cast<unsigned>(layout_height) > height_names)
		height_names = layout_height;
	    }
	  } // height name
	  border_roundno = border_y / 2;
	  if (counting == COUNTING_NS::PLUSMINUS) {
	    height_roundno = 0;
#ifdef WORKAROUND
	    scale_points = 0;
#endif
	    // ensure that there is enough place for the roundno below the graph
	    if (static_cast<unsigned>(min_points * scale_points)
		< height_names + border_names + marker_pixel_size)
	      height_roundno = ((height_names + border_names
				 + marker_pixel_size)
				- static_cast<int>(min_points * scale_points));
	  } else { // if !(counting = PLUSMINUS)
	    height_roundno = height_names;
	  } // if !(counting = PLUSMINUS)
	  if (this->height() < (2 * border_y
				+ height_names + border_names
				+ height_roundno + border_roundno)) {
	    this->update_full_area();
	    return ;
	  }
	  height_graph = (this->height() - (2 * border_y
					    + height_names + border_names
					    + height_roundno + border_roundno));
	  scale_points = - (height_graph
			    / static_cast<double>(max_points - min_points));
	  zero_y = (border_y - static_cast<int>(max_points * scale_points));
	  if (!names_on_bottom)
	    zero_y += (height_names + border_names
		       + height_roundno + border_roundno);
	} // y

	{ // x
	  border_x = std::max((this->width() / 50), 2u);
	  border_points = border_x / 2;
	  { // width points
	    list<unsigned> steps;
	    steps.push_back(1);
	    steps.push_back(2);
	    steps.push_back(5);

	    int layout_width, layout_height;
	    text_layout->set_text("0");
	    text_layout->get_pixel_size(layout_width, layout_height);

	    unsigned const min_pixel_distance
	      = 2 * static_cast<int>(layout_height);
	    points_step = 0;
	    for (list<unsigned>::const_iterator s = steps.begin(); 
		 s != steps.end();
		 ++s) {
	      if (height_graph / ((max_points - min_points) / *s)
		  >= min_pixel_distance) {
		points_step = *s;
		break;
	      }
	      steps.push_back(10 * *s);
	    } // for (s \in steps)
	    if (points_step == 0) {
	      this->update_full_area();
	      return ;
	    }

	    width_points = 0;
	    for (int p = -((-min_points / points_step) * points_step);
		 p <= max_points;
		 p += points_step)  {
	      text_layout->set_text(DK::Utils::String::to_string(p));
	      text_layout->get_pixel_size(layout_width, layout_height);
	      if (width_points < static_cast<unsigned>(layout_width))
		width_points = static_cast<unsigned>(layout_width);
	    }
	  } // width points
	  zero_x = border_x + width_points + border_points;
	  if (this->width() < zero_x + border_x) {
	    this->update_full_area();
	    return ;
	  }
	  width_graph = this->width() - zero_x - border_x;

	  scale_games = (width_graph / static_cast<double>(gamesno));
	} // x
      } // set the layout values

#if 0
      COUT << "border_y      = " << border_y << endl;
      COUT << "height_names  = " << height_names << endl;
      COUT << "border_x      = " << border_x << endl;
      COUT << "width_points  = " << width_points << endl;
      COUT << "width_graph   = " << width_graph << endl;
      COUT << "height_graph  = " << height_graph << endl;
      COUT << "zero_x        = " << zero_x << endl;
      COUT << "zero_y        = " << zero_y << endl;
      COUT << "scale_games   = " << scale_games << endl;
      COUT << "scale_points  = " << scale_points << endl;
      COUT << "points_step   = " << points_step << endl;
#endif

      { // zero lines
	this->gc->set_rgb_fg_color(Gdk::Color("black"));
	this->pixmap->draw_line(this->gc,
				zero_x, zero_y,
				zero_x + width_graph, zero_y);
	this->pixmap->draw_line(this->gc,
				zero_x,
				zero_y
				+ static_cast<int>(scale_points * min_points),
				zero_x,
				zero_y
				+ static_cast<int>(scale_points * max_points));
      } // zero lines
      { // round markers
	unsigned round_step = 0;

	list<unsigned> steps;
	steps.push_back(1);
	steps.push_back(2);
	steps.push_back(5);

	int layout_width, layout_height;
	text_layout->set_text(DK::Utils::String::to_string(roundsno));
	text_layout->get_pixel_size(layout_width, layout_height);

	// minimal pixel distance between two markers
	unsigned const min_pixel_distance = 2 * layout_width;

	if (roundsno == 0) {
	  round_step = 2 * width_graph;
	} else { // if !(roundsno == 0)
	  for (list<unsigned>::const_iterator s = steps.begin(); 
	       s != steps.end();
	       ++s) {
	    if (width_graph / (roundsno / *s) >= min_pixel_distance) {
	      round_step = *s;
	      break;
	    }
	    steps.push_back(10 * *s);
	  } // for (s \in steps)
	} // if !(roundsno == 0)
	if (round_step == 0) {
	  this->update_full_area();
	  return ;
	}

	// how many pixel the marker goes up/down
	int const marker_y_up = ( (counting == COUNTING_NS::MINUS)
				 ? 0
				 : marker_pixel_size);

	int const marker_y_down = ( (counting == COUNTING_NS::PLUS)
				   ? 0
				   : marker_pixel_size);

	for (unsigned r = 0; r <= roundsno; r += round_step) {
	  int const x = (zero_x
			 + static_cast<int>(scale_games
					    * party.round_startgame(r)));
	  this->pixmap->draw_line(this->gc,
				  x, zero_y + marker_y_down,
				  x, zero_y - marker_y_up);
	  int layout_width, layout_height;
	  text_layout->set_text(DK::Utils::String::to_string(r));
	  text_layout->get_pixel_size(layout_width, layout_height);
	  if (counting == COUNTING_NS::MINUS) {
	    this->pixmap->draw_layout(this->gc,
				      zero_x
				      + static_cast<int>(scale_games
							 * party.round_startgame(r))
				      - (layout_width / 2),
				      zero_y - marker_y_up - border_roundno
				      - layout_height,
				      text_layout);
	  } else { // if (counting == COUNTING_NS::MINUS)
	    this->pixmap->draw_layout(this->gc,
				      zero_x
				      + static_cast<int>(scale_games
							 * party.round_startgame(r))
				      - (layout_width / 2),
				      zero_y + marker_y_down + border_roundno,
				      text_layout);
	  } // if (counting == COUNTING_NS::MINUS)
	}
      } // round markers
      { // point markers
	int layout_width, layout_height;
	for (int p = -((-min_points / points_step) * points_step);
	     p <= max_points;
	     p += points_step)  {
	  text_layout->set_text(DK::Utils::String::to_string(p));
	  text_layout->get_pixel_size(layout_width, layout_height);
	  this->pixmap->draw_layout(this->gc,
				    border_x + width_points
				    - layout_width,
				    zero_y + static_cast<int>(scale_points * p)
				    - layout_height / 2,
				    text_layout);
	  this->pixmap->draw_line(this->gc,
				  zero_x,
				  zero_y + static_cast<int>(scale_points * p),
				  zero_x + marker_pixel_size,
				  zero_y + static_cast<int>(scale_points * p)
				 );
	}
      } // point markers


      { // draw the curves of the points of the players
	// reorder the players so that the human players are drawn last
	vector<unsigned> players;
	for (unsigned p = 0; p < party.playerno(); ++p)
	  if (party.player(p).type() != Player::HUMAN)
	    players.push_back(p);
	for (unsigned p = 0; p < party.playerno(); ++p)
	  if (party.player(p).type() == Player::HUMAN)
	    players.push_back(p);

	for (vector<unsigned>::const_iterator p_it = players.begin();
	     p_it != players.end();
	     ++p_it) {
	  unsigned const p = *p_it;
	  this->gc->set_rgb_fg_color(Gdk::Color(party.player(p).color()));

	  // the name
	  text_layout->set_text(party.player(p).name());
	  int layout_width, layout_height;
	  text_layout->get_pixel_size(layout_width, layout_height);
	  this->pixmap->draw_layout(this->gc,
				    zero_x
				    + (width_graph * p / party.playerno())
				    + (width_graph / party.playerno()
				       - layout_width) / 2,
				    ( names_on_bottom
				     ? (this->height()
					- border_y
					- height_names)
				     : border_y
				    ),
				    text_layout);

	  // the curves
	  this->pixmap->draw_line(this->gc,
				  zero_x, zero_y,
				  zero_x
				  + static_cast<int>(scale_games * 1),
				  zero_y
				  + static_cast<int>(scale_points
						     * party.pointsum_till_game(0, p)));
	  for (unsigned g = 1; g <= party.gameno(); ++g) {
	    if (   (g == party.gameno())
		&& (::game_status != GAMESTATUS::GAME_FINISHED) )
	      break;

	    this->pixmap->draw_line(this->gc,
				    zero_x
				    + static_cast<int>(scale_games * g),
				    zero_y
				    + static_cast<int>(scale_points
						       * party.pointsum_till_game(g - 1, p)),
				    zero_x
				    + static_cast<int>(scale_games * (g + 1)),
				    zero_y
				    + static_cast<int>(scale_points
						       * party.pointsum_till_game(g, p)));
	  } // for (g <= party.gameno())

	} // for (p < party.playerno())
      } // draw the curves of the points of the players

      this->update_full_area();

      return ;
    } // void PartyPoints::Graph::draw_all()

} // namespace UI_GTKMM_NS

#endif // #ifdef USE_UI_GTKMM
