/*
  page-spacing.cc - implement routines for spacing
  systems vertically on pages

  source file of the GNU LilyPond music typesetter

  (c) 2006--2007 Joe Neeman <joeneeman@gmail.com>
*/

#include "page-spacing.hh"

#include "matrix.hh"
#include "warn.hh"

/*
  A much simplified rods-and-springs problem.
 */
struct Page_spacing
{
  Real force_;
  Real page_height_;
  Real rod_height_;
  Real spring_len_;
  Real inverse_spring_k_;

  Line_details last_line_;

  Page_spacing (Real page_height)
  {
    page_height_ = page_height;
    clear ();
  }

  void calc_force ();

  void append_system (const Line_details &line);
  void prepend_system (const Line_details &line);
  void clear ();
};

/* In order to prevent possible division by zero, we require every line
   to have a spring of non-zero length. */
static Real
line_space (const Line_details &line)
{
  return max (0.1, line.space_);
}

void
Page_spacing::calc_force ()
{
  if (rod_height_ + last_line_.bottom_padding_ >= page_height_ || !inverse_spring_k_)
    force_ = infinity_f;
  else
    force_ = (page_height_ - rod_height_ - last_line_.bottom_padding_ - spring_len_) / inverse_spring_k_;
}

void
Page_spacing::append_system (const Line_details &line)
{
  rod_height_ += last_line_.padding_;

  rod_height_ += line.extent_.length ();
  spring_len_ += line_space (line);
  inverse_spring_k_ += max (0.1, line.inverse_hooke_);

  last_line_ = line;

  calc_force ();
}

void
Page_spacing::prepend_system (const Line_details &line)
{
  if (rod_height_)
    rod_height_ += line.padding_;
  else
    last_line_ = line;

  rod_height_ += line.extent_.length ();
  spring_len_ += line_space (line);
  inverse_spring_k_ += max (0.1, line.inverse_hooke_);

  calc_force ();
}

void
Page_spacing::clear ()
{
  force_ = rod_height_ = spring_len_ = 0;
  inverse_spring_k_ = 0;
}

/* for each forbidden page break, merge the systems around it into one system. */
static vector<Line_details>
compress_lines (const vector<Line_details> &orig)
{
  vector<Line_details> ret;

  for (vsize i = 0; i < orig.size (); i++)
    {
      if (ret.size () && !scm_is_symbol (ret.back ().page_permission_))
	{
	  Line_details const &old = ret.back ();
	  Line_details compressed = orig[i];
	  compressed.extent_[DOWN] = old.extent_[DOWN];
	  compressed.extent_[UP] = old.extent_[UP] + orig[i].extent_.length () + old.padding_;
	  compressed.space_ += old.space_;
	  compressed.inverse_hooke_ += old.inverse_hooke_;

	  /* we don't need the force_ field for the vertical spacing,
	     so we use force_ = n to signal that the line was compressed,
	     reducing the number of lines by n (and force_ = 0 otherwise).
	     This makes uncompression much easier. */
	  compressed.force_ = old.force_ + 1;
	  ret.back () = compressed;
	}
      else
	{
	  ret.push_back (orig[i]);
	  ret.back ().force_ = 0;
	}
    }
  return ret;
}

/* translate the number of systems-per-page into something meaningful for
   the uncompressed lines.
*/
static vector<vsize>
uncompress_solution (vector<vsize> const &systems_per_page,
		     vector<Line_details> const &compressed)
{
  vector<vsize> ret;
  vsize start_sys = 0;

  for (vsize i = 0; i < systems_per_page.size (); i++)
    {
      int compressed_count = 0;
      for (vsize j = start_sys; j < start_sys + systems_per_page[i]; j++)
	compressed_count += (int)compressed[j].force_;

      ret.push_back (systems_per_page[i] + compressed_count);
      start_sys += systems_per_page[i];
    }
  return ret;
}

/* the cases for page_count = 1 or 2 can be done in O (n) time. Since they
   are by far the most common cases, we have special functions for them */
static Spacing_result
space_systems_on_1_page (vector<Line_details> const &lines, Real page_height, bool ragged)
{
  Page_spacing space (page_height);
  Spacing_result ret;

  for (vsize i = 0; i < lines.size (); i++)
    space.append_system (lines[i]);

  ret.systems_per_page_.push_back (lines.size ());
  ret.force_.push_back (ragged ? min (space.force_, 0.0) : space.force_);
  ret.penalty_ = lines.back ().page_penalty_ + lines.back ().turn_penalty_;
  ret.demerits_ = ret.force_.back () * ret.force_.back () + ret.penalty_;

  return ret;
}

static Spacing_result
space_systems_on_2_pages (vector<Line_details> const &lines,
			  Real page_height,
			  bool ragged,
			  bool ragged_last)
{
  /* if there is a forced break, this reduces to 2 1-page problems */
  for (vsize i = 0; i + 1 < lines.size (); i++)
    if (lines[i].page_permission_ == ly_symbol2scm ("force"))
      {
	vector<Line_details> lines1 (lines.begin (), lines.begin () + i + 1);
	vector<Line_details> lines2 (lines.begin () + i + 1, lines.end ());
	Spacing_result p1 = space_systems_on_1_page (lines1, page_height, ragged);
	Spacing_result p2 = space_systems_on_1_page (lines2, page_height, ragged || ragged_last);

	p1.systems_per_page_.push_back (p2.systems_per_page_[0]);
	p1.force_.push_back (p2.force_[0]);
	p1.penalty_ += p2.penalty_ - lines[i].turn_penalty_;
	p1.demerits_ += p2.demerits_ - lines[i].turn_penalty_;
	return p1;
      }

  vector<Real> page1_force;
  vector<Real> page2_force;
  Page_spacing page1 (page_height);
  Page_spacing page2 (page_height);

  page1_force.resize (lines.size () - 1, infinity_f);
  page2_force.resize (lines.size () - 1, infinity_f);

  for (vsize i = 0; i < page1_force.size (); i++)
    {
      page1.append_system (lines[i]);
      page2.prepend_system (lines[lines.size () - 1 - i]);
      page1_force[i] = (ragged && page1.force_ < 0 && i > 0) ? infinity_f : page1.force_;

      if (ragged || ragged_last)
	page2_force[page2_force.size () - 1 - i] =
	  (page2.force_ < 0 && i + 1 < page1_force.size ()) ? infinity_f : 0;
      else
	page2_force[page2_force.size () - 1 - i] = page2.force_;
    }

  vsize best_sys_count = 1;
  Real best_demerits = infinity_f;
  for (vsize i = 0; i < page1_force.size (); i++)
    {
      Real dem = page1_force[i] * page1_force[i]
	+ page2_force[i] * page2_force[i]
	+ lines[i+1].page_penalty_
	+ lines.back ().page_penalty_ + lines.back ().turn_penalty_;
      if (dem < best_demerits)
	{
	  best_demerits = dem;
	  best_sys_count = i+1;
	}
    }

  Spacing_result ret;
  ret.systems_per_page_.push_back (best_sys_count);
  ret.systems_per_page_.push_back (lines.size () - best_sys_count);
  ret.force_.push_back (page1_force[best_sys_count-1]);
  ret.force_.push_back (page2_force[best_sys_count-1]);
  ret.penalty_ = lines[best_sys_count-1].page_penalty_
    + lines.back ().page_penalty_
    + lines.back ().turn_penalty_;
  ret.demerits_ = best_demerits;

  return ret;
}

Page_spacer::Page_spacer (vector<Line_details> const &lines, Real page_height, bool ragged, bool ragged_last)
  : lines_ (lines)
{
  page_height_ = page_height;
  max_page_count_ = 0;
  ragged_ = ragged;
  ragged_last_ = ragged_last;
}

Spacing_result
Page_spacer::solve (vsize page_count)
{
  if (page_count > max_page_count_)
    resize (page_count);

  Spacing_result ret;
  ret.force_.resize (page_count);
  ret.systems_per_page_.resize (page_count);

  vsize system = lines_.size () - 1;
  vsize tack_onto_the_end = 0;

  if (isinf (state_.at (system, page_count-1).demerits_))
    {
      programming_error ("tried to space systems on a bad number of pages");
      /* Usually, this means that we tried to cram too many systems into
	 to few pages. To avoid crashing, we look for the largest number of
	 systems that we can fit properly onto the right number of pages.
	 All the systems that don't fit get tacked onto the last page.
      */
      vsize i;
      for (i = system; isinf (state_.at (i, page_count-1).demerits_) && i--; )
	;

      if (i)
	{
	  tack_onto_the_end = system - i;
	  system = i;
	}
      else
	return Spacing_result (); /* couldn't salvage it -- probably going to crash */
    }

  ret.penalty_ = state_.at (system, page_count-1).penalty_
    + lines_.back ().page_penalty_ + lines_.back ().turn_penalty_;

  ret.demerits_ = 0;
  for (vsize p = page_count; p--;)
    {
      assert (system != VPOS);

      Page_spacing_node const &ps = state_.at (system, p);
      ret.force_[p] = ps.force_;
      ret.demerits_ += ps.force_ * ps.force_;
      if (p == 0)
	ret.systems_per_page_[p] = system + 1;
      else
	ret.systems_per_page_[p] = system - ps.prev_ + tack_onto_the_end;
      system = ps.prev_;
    }
  ret.demerits_ += ret.penalty_;
  return ret;
}

void
Page_spacer::resize (vsize page_count)
{
  assert (page_count > 0);

  if (max_page_count_ >= page_count)
    return;

  state_.resize (lines_.size (), page_count, Page_spacing_node ());
  for (vsize page = max_page_count_; page < page_count; page++)
    for (vsize line = page; line < lines_.size (); line++)
      if (!calc_subproblem (page, line))
	break;

  max_page_count_ = page_count;
}

bool
Page_spacer::calc_subproblem (vsize page, vsize line)
{
  Page_spacing space (page_height_);
  Page_spacing_node &cur = state_.at (line, page);
  bool ragged = ragged_ || (ragged_last_ && line == lines_.size () - 1);

  for (vsize page_start = line+1; page_start > page && page_start--;)
    {
      Page_spacing_node const *prev = page > 0 ? &state_.at (page_start-1, page-1) : 0;

      space.prepend_system (lines_[page_start]);
      if (page_start < line && (isinf (space.force_) || (space.force_ < 0 && ragged)))
	break;

      if (page > 0 || page_start == 0)
	{
	  if (line == lines_.size () - 1 && ragged_last_ && space.force_ > 0)
	    space.force_ = 0;

	  /* we may have to deal with single lines that are taller than a page */
	  if (isinf (space.force_) && page_start == line)
	    space.force_ = -200000;

	  Real dem = fabs (space.force_) + (prev ? prev->demerits_ : 0);
	  Real penalty = 0;
	  if (page_start > 0)
	    penalty = lines_[page_start-1].page_penalty_
	      + (page % 2 == 0) ? lines_[page_start-1].turn_penalty_ : 0;

	  dem += penalty;
	  if (dem < cur.demerits_ || page_start == line)
	    {
	      cur.demerits_ = dem;
	      cur.force_ = space.force_;
	      cur.penalty_ = penalty + (prev ? prev->penalty_ : 0);
	      cur.prev_ = page_start - 1;
	    }
	}

      if (page_start > 0
	  && lines_[page_start-1].page_permission_ == ly_symbol2scm ("force"))
	break;
    }
  return !isinf (cur.demerits_);
}

vsize
min_page_count (vector<Line_details> const &uncompressed_lines,
		Real page_height, bool ragged, bool ragged_last)
{
  vsize ret = 1;
  Real cur_rod_height = 0;
  vector<Line_details> lines = compress_lines (uncompressed_lines);

  assert (lines.size ());
  for (vsize i = lines.size (); i--;)
    {
      bool rag = ragged || (ragged_last && ret == 1);
      Real ext_len = lines[i].extent_.length ();
      Real next_height = cur_rod_height + ext_len
	+ (rag ? line_space (lines[i]) : 0)
	+ ((cur_rod_height > 0) ? lines[i].padding_: 0);

      if ((next_height > page_height && cur_rod_height > 0)
	  || (i + 1 < lines.size () && lines[i].page_permission_ == ly_symbol2scm ("force")))
	{
	  ret++;
	  cur_rod_height = ext_len + (rag ? line_space (lines[i]) : 0);
	}
      else
	cur_rod_height = next_height;
    }

  return ret;
}

Spacing_result
space_systems_on_n_pages (vector<Line_details> const &lines,
			  vsize n,
			  Real page_height,
			  bool ragged,
			  bool ragged_last)
{
  vector<Line_details> compressed_lines = compress_lines (lines);
  Spacing_result ret;
  assert (n >= min_page_count (lines, page_height, ragged, ragged_last));

  if (n > compressed_lines.size ())
    return Spacing_result ();
  if (n == 1)
    ret = space_systems_on_1_page (compressed_lines, page_height, ragged || ragged_last);
  else if (n == 2)
    ret = space_systems_on_2_pages (compressed_lines, page_height, ragged, ragged_last);

  Page_spacer ps (compressed_lines, page_height, ragged, ragged_last);
  ret = ps.solve (n);

  ret.systems_per_page_ = uncompress_solution (ret.systems_per_page_, compressed_lines);
  return ret;
}

Spacing_result
space_systems_on_n_or_one_more_pages (vector<Line_details> const &lines,
				      vsize n,
				      Real page_height,
				      Real odd_pages_penalty,
				      bool ragged,
				      bool ragged_last)
{
  Spacing_result n_res = space_systems_on_n_pages (lines, n, page_height, ragged, ragged_last);
  Spacing_result m_res = space_systems_on_n_pages (lines, n+1, page_height, ragged, ragged_last);
  n_res.demerits_ += odd_pages_penalty;
  n_res.force_.back () += odd_pages_penalty;

  if (n_res.demerits_ < m_res.demerits_)
    return n_res;
  return m_res;
}

Spacing_result
space_systems_on_best_pages (vector<Line_details> const &lines,
			     Real page_height,
			     Real odd_pages_penalty,
			     bool ragged,
			     bool ragged_last)
{
  vector<Line_details> compressed_lines = compress_lines (lines);
  vsize min_p_count = min_page_count (compressed_lines, page_height, ragged, ragged_last);

  Page_spacer ps (compressed_lines, page_height, ragged, ragged_last);
  Spacing_result best = ps.solve (min_p_count);
  best.force_.back () += (min_p_count % 2) ? odd_pages_penalty : 0;
  best.demerits_ += (min_p_count % 2) ? odd_pages_penalty : 0;

  for (vsize i = min_p_count+1; i <= compressed_lines.size (); i++)
    {
      Spacing_result cur = ps.solve (i);
      cur.demerits_ += (i % 2) ? odd_pages_penalty : 0;
      if (cur.demerits_ < best.demerits_)
	best = cur;
    }

  best.systems_per_page_ = uncompress_solution (best.systems_per_page_, compressed_lines);
  return best;
}
