/*
  vaticana-ligature-engraver.cc -- implement Vaticana_ligature_engraver
  
  source file of the GNU LilyPond music typesetter
  
  (c)  2003 Juergen Reuter <reuter@ipd.uka.de>
 */

#include "gregorian-ligature-engraver.hh"
#include "gregorian-ligature.hh"
#include "vaticana-ligature.hh"
#include "item.hh"
#include "spanner.hh"
#include "staff-symbol-referencer.hh"
#include "font-interface.hh"
#include "warn.hh"
#include "paper-def.hh"
#include "paper-column.hh"

/*
 * This class implements the notation specific aspects of Vaticana
 * style ligatures for Gregorian chant notation.
 */
class Vaticana_ligature_engraver : public Gregorian_ligature_engraver
{

private:
  Real finish_primitive (Item *first_primitive,
			 Item *primitive,
			 int prefix_set,
			 int context_info,
			 String glyph_name,
			 int pitch_delta,
			 Real flexa_width,
			 Real join_thickness,
			 Real distance);

public:
  TRANSLATOR_DECLARATIONS(Vaticana_ligature_engraver);

protected:
  virtual Spanner *create_ligature_spanner ();
  virtual void transform_heads (Spanner *ligature,
				Array<Grob_info> primitives);
};

Vaticana_ligature_engraver::Vaticana_ligature_engraver ()
{
}

Spanner *
Vaticana_ligature_engraver::create_ligature_spanner ()
{
  return new Spanner (get_property ("VaticanaLigature"));
}

Real
Vaticana_ligature_engraver::finish_primitive (Item *first_primitive,
					      Item *primitive,
					      int prefix_set,
					      int context_info,
					      String glyph_name,
					      int pitch_delta,
					      Real flexa_width,
					      Real join_thickness,
					      Real distance)
{
  Real next_distance = distance;
  if (primitive)
    { // determine width of previous head and x-offset
      Real head_width;
      Real x_offset;
      bool is_stacked;

      // upper head of pes is stacked upon lower head of pes ...
      is_stacked = context_info & PES_UPPER;

      // ... unless this note starts a flexa
      if (context_info & FLEXA_LEFT)
	is_stacked = false;

      // ... or another pes
      if (context_info & PES_LOWER)
	is_stacked = false;

      // ... or the previous note is a semivocalis or inclinatum
      if (context_info & AFTER_DEMINUTUM)
	is_stacked = false;

      // auctum head is never stacked upon preceding note
      if (prefix_set & AUCTUM)
	is_stacked = false;

      // virga is never stacked upon preceding note
      if (prefix_set & VIRGA)
	is_stacked = false;

      // oriscus is never stacked upon preceding note
      if (prefix_set & ORISCUS)
	is_stacked = false;

      if ((prefix_set & DEMINUTUM) &&
	  !(prefix_set & INCLINATUM) &&
	  (context_info & FLEXA_RIGHT))
	is_stacked = true; // semivocalis head of deminutus form

      if (is_stacked)
	{
	  /*
	   * This head is stacked upon the previous one; hence, it
	   * does not contribute to the total width of the ligature,
	   * and its width is assumed to be 0.0.  Moreover, it is
	   * shifted to the left by its width such that the right side
	   * of this and the other head are horizontally aligned.
	   */
	  head_width = 0.0;
	  x_offset = join_thickness -
	    Font_interface::get_default_font (primitive)->
	    find_by_name ("noteheads-" + glyph_name).extent (X_AXIS).length ();
	}
      else if (!String::compare (glyph_name, "flexa") ||
	       !String::compare (glyph_name, ""))
	{
	  /*
	   * This head represents either half of a flexa shape.
	   * Hence, it is assigned half the width of this shape.
	   */
	  head_width = 0.5 * flexa_width;
	  x_offset = 0.0;
	}
      else // retrieve width from corresponding font
	{
	  head_width =
	    Font_interface::get_default_font (primitive)->
	    find_by_name ("noteheads-" + glyph_name).extent (X_AXIS).length ();
	  x_offset = 0.0;
	}

      /*
       * Save the head's final shape and x-offset.
       */
      primitive->set_grob_property ("glyph-name",
				    scm_makfrom0str (glyph_name.to_str0 ()));
      primitive->set_grob_property ("x-offset",
				    gh_double2scm (x_offset));

      /*
       * If the head is the 2nd head of a pes or flexa (but not a
       * flexa shape), mark this head to be joined with the left-side
       * neighbour head (i.e. the previous head) by a vertical beam.
       */
      if ((context_info & PES_UPPER) ||
	  ((context_info & FLEXA_RIGHT) &&
	   !(context_info & PES_LOWER)))
	{
	  primitive->set_grob_property ("join-left",
					gh_int2scm (pitch_delta));

	  /*
	   * Create a small overlap of adjacent heads so that the join
	   * can be drawn perfectly between them.
	   */
	  next_distance -= join_thickness;
	}
      else if (!String::compare (glyph_name, ""))
	{
	  /*
	   * 2nd (virtual) head of flexa shape: join tightly with 1st
	   * head, i.e. do *not* add additional space, such that next
	   * head will not be off from the flexa shape.
	   */
	}
      else if (context_info & AFTER_VIRGA)
	{
	  /*
	   * Make a small space after a virga.
	   */
	  next_distance += 2 * join_thickness;
	}
      else if (pitch_delta == 0)
	{
	  /*
	   * Make a small space between two adjacent notes with the
	   * same pitch.
	   */
	  next_distance += 2 * join_thickness;
	}

      /*
       * Horizontally line-up this head to form a ligature.
       */
      get_set_column (primitive, first_primitive->get_column ());
      primitive->translate_axis (next_distance, X_AXIS);
      next_distance += head_width;

    }

  return next_distance;
}

void
Vaticana_ligature_engraver::transform_heads (Spanner *ligature,
					     Array<Grob_info> primitives)
{
  Real flexa_width;
  SCM flexa_width_scm = ligature->get_grob_property ("flexa-width");
  if (flexa_width_scm != SCM_EOL)
    {
      flexa_width = gh_scm2double (flexa_width_scm);
    }
  else
    {
      programming_error ("Vaticana_ligature_engraver:"
			 "flexa-width undefined; assuming 2.0 staff space");
      flexa_width =
	2.0 * Staff_symbol_referencer::staff_space (ligature);
    }

  Real join_thickness;
  SCM join_thickness_scm = ligature->get_grob_property ("thickness");
  if (join_thickness_scm != SCM_EOL)
    {
      join_thickness = gh_scm2double (join_thickness_scm);
    }
  else
    {
      programming_error ("Vaticana_ligature_engraver:"
			 "thickness undefined; assuming 1.4 linethickness");
      join_thickness = 1.4;
    }
  join_thickness *= ligature->get_paper ()->get_var ("linethickness");

  Item *first_primitive = 0;
  Item *prev_primitive = 0;
  int prev_prefix_set = 0;
  int prev_context_info = 0;
  int prev_pitch = 0;
  int prev_pitch_delta = 0;
  String prev_glyph_name = "";
  Real prev_distance = 0.0;
  for (int i = 0; i < primitives.size(); i++) {
    Item *primitive = dynamic_cast<Item*> (primitives[i].grob_);
    Music *music_cause = primitives[i].music_cause ();
    int context_info = gh_scm2int (primitive->get_grob_property ("context-info"));
    int pitch = unsmob_pitch (music_cause->get_mus_property ("pitch"))->steps ();
    String glyph_name;
    if (!first_primitive)
      first_primitive = primitive;
    int prefix_set = gh_scm2int (primitive->get_grob_property ("prefix-set"));

    /*
     * Now determine which head to typeset (this is context sensitive
     * information, since it depends on neighbouring heads; therefore,
     * this decision must be made here in the engraver rather than in
     * the backend).
     */
    if (prefix_set & VIRGA)
      glyph_name = "vaticana_virga";
    else if (prefix_set & QUILISMA)
      glyph_name = "vaticana_quilisma";
    else if (prefix_set & ORISCUS)
      glyph_name = "solesmes_oriscus";
    else if (prefix_set & STROPHA)
      if (prefix_set & AUCTUM)
	glyph_name = "solesmes_stropha_aucta";
      else glyph_name = "solesmes_stropha";
    else if (prefix_set & INCLINATUM)
      if (prefix_set & AUCTUM)
	glyph_name = "solesmes_incl_auctum";
      else if (prefix_set & DEMINUTUM)
	glyph_name = "solesmes_incl_parvum";
      else
	glyph_name = "vaticana_inclinatum";
    else if (prefix_set & DEMINUTUM)
      if (primitive == first_primitive)
	{
	  // initio debilis
	  glyph_name = "vaticana_reverse_plica";
	}
      else if (pitch > prev_pitch)
	{
	  // epiphonus
	  if (!(prev_context_info & FLEXA_RIGHT))
	    {
	      prev_glyph_name = "vaticana_epiphonus";
	    }
	  glyph_name = "vaticana_plica";
	}
      else // (pitch <= prev_pitch)
	{
	  // cephalicus
	  if (!(prev_context_info & FLEXA_RIGHT))
	    {
	      if (prev_primitive == first_primitive)
		{
		  prev_glyph_name = "vaticana_cephalicus";
		}
	      else
		{
		  prev_glyph_name = "vaticana_inner_cephalicus";
		}
	    }
	  glyph_name = "vaticana_reverse_plica";
	}
    else if (prefix_set & (CAVUM | LINEA))
      if ((prefix_set & CAVUM) && (prefix_set & LINEA))
	glyph_name = "vaticana_linea_punctum_cavum";
      else if (prefix_set & CAVUM)
	glyph_name = "vaticana_punctum_cavum";
      else
	glyph_name = "vaticana_linea_punctum";
    else if (prefix_set & AUCTUM)
      if (prefix_set & ASCENDENS)
	glyph_name = "solesmes_auct_asc";
      else
	glyph_name = "solesmes_auct_desc";
    else if ((prefix_set & PES_OR_FLEXA) &&
	     (context_info & PES_LOWER) &&
	     (context_info & FLEXA_RIGHT))
      glyph_name = ""; // second head of flexa shape
    else if (context_info & PES_UPPER)
      if (pitch - prev_pitch > 1)
	glyph_name = "vaticana_upes";
      else
	glyph_name = "vaticana_vupes";
    else
      glyph_name = "vaticana_punctum";

    /*
     * May need updating previous head, depending on the current head.
     */
    if (prefix_set & PES_OR_FLEXA)
      if ((context_info & PES_LOWER) &&
	  (context_info & FLEXA_RIGHT)) // flexa shape
	{
	  prev_glyph_name = "flexa";
	  prev_primitive->set_grob_property ("flexa-height",
					     gh_int2scm (pitch - prev_pitch));
	  prev_primitive->set_grob_property ("flexa-width",
					     gh_double2scm (flexa_width));
	  bool add_stem =
	    !(prev_context_info & PES_UPPER) &&
	    !(prev_context_info & FLEXA_RIGHT);
	  prev_primitive->set_grob_property ("add-stem",
					     gh_bool2scm (add_stem));
	}
      else if (context_info & PES_UPPER)
	{
	  if (!String::compare (prev_glyph_name, "vaticana_punctum"))
	    prev_glyph_name = "vaticana_lpes";
	}
      else // flexa
	{
	  if (!String::compare (prev_glyph_name, "vaticana_punctum"))
	    prev_glyph_name = "vaticana_rvirga";
	}

    /*
     * In the backend, flexa shapes and joins need to know about
     * thickness.  Hence, for simplicity, let's distribute the
     * ligature grob's value for thickness to each ligature head (even
     * if not all of them need to know).
     */
    primitive->set_grob_property ("thickness", gh_double2scm (join_thickness));

    /*
     * The head of the current iteration still may change during the
     * next iteration due to the context sensitiveness of the
     * transformation.  However, the head of the previous iteration is
     * now fully attributed and can be prepared for the backend.
     */

    /*
     * Finish head of previous iteration for backend.
     */
    prev_distance =
      finish_primitive (first_primitive, prev_primitive, prev_prefix_set,
			prev_context_info, prev_glyph_name, prev_pitch_delta,
			flexa_width, join_thickness, prev_distance);

    prev_primitive = primitive;
    prev_prefix_set = prefix_set;
    prev_context_info = context_info;
    prev_pitch_delta = pitch - prev_pitch;
    prev_pitch = pitch;
    prev_glyph_name = glyph_name;
  }

  /*
   * Finish head of last iteration for backend.
   */
  prev_distance =
    finish_primitive (first_primitive, prev_primitive, prev_prefix_set,
		      prev_context_info, prev_glyph_name, prev_pitch_delta,
		      flexa_width, join_thickness, prev_distance);

  /* TODO: make this cfg'able via SCM */
  Real padding = join_thickness;

  /* horizontal padding space after ligature */
  prev_distance += padding;

#if 0 // experimental code to collapse spacing after ligature
  /* TODO: set to max(old/new spacing-increment), since other
     voices/staves also may want to set this property. */
  Paper_column *paper_column = first_primitive->get_column();
  paper_column->warning (_f ("Vaticana_ligature_engraver: "
			     "setting `spacing-increment = %f': ptr=%ul",
			     prev_distance, paper_column));
  paper_column->
    set_grob_property("forced-spacing", gh_double2scm (prev_distance));
#endif
}


ENTER_DESCRIPTION (Vaticana_ligature_engraver,
/* descr */       "Handles ligatures by glueing special ligature heads together.",
/* creats*/       "VaticanaLigature",
/* accepts */     "ligature-event abort-event",
/* acks  */      "note-head-interface rest-interface",
/* reads */       "",
/* write */       "");
