/*
	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 should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor,
	Boston, MA  02110-1301, USA.

	---
	Copyright (C) 2011 - 2015, Simon Hampe <simon.hampe@googlemail.com>

	Computes all lines (and families thereof) in a tropical cubic surface in R^3.
	*/

#include "polymake/client.h"
#include "polymake/Rational.h"
#include "polymake/TropicalNumber.h"
#include "polymake/Polynomial.h"
#include "polymake/Matrix.h"
#include "polymake/Vector.h"
#include "polymake/linalg.h"
#include "polymake/IncidenceMatrix.h"
#include "polymake/tropical/divisor.h"
#include "polymake/tropical/convex_hull_tools.h"
#include "polymake/tropical/solver_def.h"
#include "polymake/tropical/lines_in_cubic_data.h"
#include "polymake/tropical/lines_in_cubic_helper.h"
#include "polymake/tropical/lines_in_cubic_reachable.h"
#include "polymake/tropical/specialcycles.h"
#include "polymake/tropical/thomog.h"

namespace polymake { namespace tropical {

namespace {
	/**
	  @brief This takes a result of computeFacets and finds all the facets visible from "direction", i.e. all facets whose outer normal has strict positive scalar product with direction (it doesn't matter which normal we take: The direction must lie in the span of the two-dimensional cone, so the equation g of the cone is zero on direction. Any two representatives of a facet normal only differ by a multiple of g).
	  */
        void appendVisibleFaces(RestrictedIncidenceMatrix<>& faces, const FacetData& fd, const Vector<Rational>& direction)
        {
          for (int f = 0; f < fd.ineqs.rows(); f++) {
            if (fd.ineqs.row(f) * direction < 0) { //<0, since ineqs has the inner normals
              faces /= fd.facets.row(f);
            }
          }
	}
}
// ------------------------------------------------------------------------------------------------  

	//Takes a max(!)-tropical polynomial of degree 3 in affine coordinates and computes all
	//lines in the corresponding cubic.
	perl::Object linesInCubic(const Polynomial<TropicalNumber<Max>>& f) {
		//First, we compute the divisor of f
		perl::Object r3 = projective_torus<Max>(3,1);
		perl::Object ratfct = call_function("rational_fct_from_affine_numerator", f);	
		perl::Object X = call_function("divisor", r3, ratfct);
		perl::Object lindom = ratfct.give("DOMAIN");
		Matrix<Rational> lindom_rays = lindom.give("VERTICES");
			lindom_rays = tdehomog(lindom_rays);
		IncidenceMatrix<> lindom_cones = lindom.give("MAXIMAL_POLYTOPES");
		Matrix<Rational> funmat(f.monomials_as_matrix());
		Vector<Rational> funcoeffs(f.coefficients_as_vector());
		//Rearrange so that values of terms on points can be computed as product with matrix
		funmat = funcoeffs | funmat;

		Matrix<Rational> degree = -unit_matrix<Rational>(3);
		degree = ones_vector<Rational>(3) / degree;
		degree = zero_vector<Rational>(4) | degree;

		//Then we compute all reachable points for each direction
                std::vector<ReachableResult> reachable_points;
                reachable_points.reserve(4);
		for (int i = 0; i < 4; i++) {
			cout << "Computing reachable points from direction " << i << endl;
			reachable_points.push_back(reachablePoints(f,X,i));
		}
		
		solver<Rational> sv;

		//This maps pairs of leafs to increasing numbers and vice versa
		Map<int, Map<int,int> > edge_pair_map;
		Map<int,Set<int> > index_to_pair_map;
		int count = 0;
		for(int a = 0; a <= 3; a++) {
			edge_pair_map[a] = Map<int,int>();
			for(int b = a+1; b <= 3; b++) {
				(edge_pair_map[a])[b] = count;
				index_to_pair_map[count] = (scalar2set(a) + scalar2set(b));
				count++;
			}
		}

		//Result variables

		//These variables contain the lines in the cubic
		Vector<VertexLine> vertex_line;
		Vector<VertexFamily> vertex_family;
		Vector<EdgeLine> edge_line;
		Vector<EdgeFamily> edge_family;


		//Then we compute the six intersection sets of the reachable loci, paired in complements
		//In each step we go through all pairs of maximal cones of these two complexes. We add to each
		// cone the direction of the potential bounded edge of the line and then intersect the two
		// resulting polyhedra. Afterwards we check if the result lies in X.

		Matrix<Rational> dummy_lineality(0,4);
		for(int i = 1; i <= 3; i++) {
			Vector<int> remaining(sequence(1,3) - i);
			cout << "Intersecting reachable loci from direction 0 and " << i << " and from direction " << remaining[0] << " and " << remaining[1] << endl;
			//All points reachable from direction 0 and i
			fan_intersection_result z_inter = cdd_fan_intersection(
					reachable_points[0].rays, dummy_lineality, reachable_points[0].cells / reachable_points[0].edges,
					reachable_points[i].rays, dummy_lineality, reachable_points[i].cells / reachable_points[i].edges
					);
			//All points reachable from the other two directions
			fan_intersection_result c_inter = cdd_fan_intersection(
					reachable_points[remaining[0]].rays, dummy_lineality, reachable_points[remaining[0]].cells / reachable_points[remaining[0]].edges,
					reachable_points[remaining[1]].rays, dummy_lineality, reachable_points[remaining[1]].cells / reachable_points[remaining[1]].edges
					);
			//Clean up the intersection
			DirectionIntersection z_inter_clean = cleanUpIntersection(z_inter);
			DirectionIntersection c_inter_clean = cleanUpIntersection(c_inter);

			IncidenceMatrix<> all_z_cones = z_inter_clean.cells / z_inter_clean.edges / z_inter_clean.points;
			IncidenceMatrix<> all_c_cones = c_inter_clean.cells / c_inter_clean.edges / c_inter_clean.points;

			// 	perl::ListReturn test;
			// 	  perl::Object t("WeightedComplex");
			// 	  t.take("RAYS") << z_inter_clean.rays;
			// 	  t.take("MAXIMAL_CONES") << all_z_cones;
			// 	  t.take("USES_HOMOGENEOUS_C") << true;
			// 	  test << t; return test;

			cout << "Computing potential families in between..." << endl;
			//Go through all pairs of cells, add the appropriate direction vector and intersect the two polyhedra
			Vector<Rational> dir_z = - degree.row(0) - degree.row(i);
			Vector<Rational> dir_c = - dir_z;
			for(int zc = 0; zc < all_z_cones.rows(); zc++) {
				//We compute the number of relevant cells in zc. If the dimension of zc is <= 1, this is just zc
				//If the dimension is 2, we take all the codimension one faces of zc that are visible
				//"from the direction of dir_z", i.e. the scalar product of the outer facet normal with dir_z is
				//strictly positive. We will then do computations separately for each such codim one face
				RestrictedIncidenceMatrix<> z_cones;
				int z_dim = (all_z_cones.row(zc).size() <= 2? all_z_cones.row(zc).size()-1 : 2);
				if (z_dim < 2) {
                                  z_cones /= all_z_cones.row(zc);
				} else {
                                  const FacetData z_facets = computeFacets(z_inter_clean.rays, all_z_cones.row(zc), sv);
                                  appendVisibleFaces(z_cones, z_facets, dir_z);
				}

				//Now we go through all cells of c and apply the same procedure
				for (int cc = 0; cc < all_c_cones.rows(); cc++) {
					RestrictedIncidenceMatrix<> c_cones;
					int c_dim = (all_c_cones.row(cc).size() <= 2? all_c_cones.row(cc).size()-1 : 2);
					if (c_dim < 2) {
                                          c_cones /= all_c_cones.row(cc);
					} else {
                                          const FacetData c_facets = computeFacets(c_inter_clean.rays, all_c_cones.row(cc), sv);
                                          appendVisibleFaces(c_cones, c_facets, dir_c);
					}

					//Now we go trough all pairs of elements of z_cones and c_cones and find families in between them
					for (int zlist = 0; zlist < z_cones.rows(); zlist++) {
						for (int clist = 0; clist < c_cones.rows(); clist++) {
							//First we compute the intersection of the two polyhedra we obtain if we add to the
							//cells in zlist/clist a ray in edge direction. The result cannot have lineality space,
							//as neither of the two factors has one (not even implicit).
							Matrix<Rational> center = cdd_cone_intersection(
                                                                        z_inter_clean.rays.minor(z_cones.row(zlist), All) / dir_z, dummy_lineality,
									c_inter_clean.rays.minor(c_cones.row(clist), All) / dir_c, dummy_lineality).first ;
							if (center.rows() == 0) continue;
							if (is_zero(center.col(0))) continue;

							//If the intersection is 0-dimensional, we just check if the maximum is attained twice,
							//then add it as solution
							if (center.rows() == 1) {
								if (maximumAttainedTwice(funmat * center.row(0))) {
									VertexLine vl;
									vl.vertex = center.row(0);
									Set<int> spans;
									if(z_dim == 2) spans += (edge_pair_map[0])[i];
									if(c_dim == 2) spans += (edge_pair_map[remaining[0]])[remaining[1]];
									vl.cells = spans;
									vertex_line |= vl;
								}
								continue;
							}//END if 0-dimensional

							//First we compute the "border" of center, i.e. the intersection with the two cones
							Matrix<Rational> z_border = cdd_cone_intersection(
                                                                        z_inter_clean.rays.minor(z_cones.row(zlist), All), dummy_lineality,
									center, dummy_lineality).first;
							Matrix<Rational> c_border = cdd_cone_intersection(
                                                                        c_inter_clean.rays.minor(c_cones.row(clist), All), dummy_lineality,
									center, dummy_lineality).first;

							//Then we refine this polyhedron along the domains of f
							const IncidenceMatrix<> center_cone(rowwise(), sequence(0,center.rows()));
							DirectionIntersection center_ref = cleanUpIntersection(cdd_fan_intersection(
                                                           center, dummy_lineality, center_cone, lindom_rays, dummy_lineality, lindom_cones));

							//If the intersection is 1-dimensional , we have two possibilities:
							// (1) The center cone is equal to the intersection of the two border cones. Then the
							//     whole cone is a one-dimensional family of lines without bounded edge.
							// (2) The center cone intersects the border cones each in a vertex. Then we check if all
							//     cells of center_ref are contained in X. If so, we have an isolated line.
							if(center.rows() == 2 ) {
								if(z_border.rows() == 2) {
									VertexFamily vf;
									vf.edge = z_border;
									vertex_family |= vf;
								}//END case (1)
								else {
									bool found_bad_cell = false;
									for(int cencone = 0; cencone < center_ref.edges.rows(); cencone++) {
										Matrix<Rational> cencone_rays = center_ref.rays.minor(center_ref.edges.row(cencone),All);
										Vector<Rational> interior_point =
											accumulate(rows(cencone_rays),
													operations::add()) / accumulate(cencone_rays.col(0),operations::add());
										if(!maximumAttainedTwice(funmat*interior_point)) {
											found_bad_cell = true; break;
										}
									}//END iterate edges of refined center cell
									//If all edges lie in X, we add the line as isolated line
									if(!found_bad_cell) {
										EdgeLine el;
										el.vertexAtZero = z_border.row(0);
										el.vertexAwayZero = c_border.row(0);
										el.leafAtZero = i;
										el.spanAtZero = (z_dim == 2);
										el.spanAwayZero = (c_dim == 2);
										el.maxDistAtZero =
											maximalDistanceVector(el.vertexAtZero,
													degree.row(0) + degree.row(el.leafAtZero),
													lindom_rays, lindom_cones, funmat);
										el.maxDistAwayZero =
											maximalDistanceVector(el.vertexAwayZero,
													degree.row(remaining[0]) + degree.row(remaining[1]),
													lindom_rays, lindom_cones, funmat);
										edge_line |= el;
									}
								}//END case (2)
								continue;
							}//END if 1-dimensional

							//If the cone is 2-dimensional, we have to refine, project and refine again,
							// similarly to the procedure used when computing the reachable points
							if(center.rows() > 2) {
								LinesInCellResult lines_in_cell =
									computeEdgeFamilies(center_ref, z_border, c_border,i, funmat);
								edge_family |= lines_in_cell.edge_families;
								//Add spans and max-dist-vectors before adding edge_line and vertex_line
								for(int el = 0; el < lines_in_cell.edge_lines.dim(); el++) {
									lines_in_cell.edge_lines[el].spanAtZero = (z_dim == 2);
									lines_in_cell.edge_lines[el].spanAwayZero = (c_dim == 2);
									lines_in_cell.edge_lines[el].maxDistAtZero =
										maximalDistanceVector(lines_in_cell.edge_lines[el].vertexAtZero,
												degree.row(0) + degree.row(i),
												lindom_rays, lindom_cones, funmat);
									lines_in_cell.edge_lines[el].maxDistAwayZero =
										maximalDistanceVector(lines_in_cell.edge_lines[el].vertexAwayZero,
												degree.row(remaining[0]) + degree.row(remaining[1]),
												lindom_rays, lindom_cones, funmat);
								}//END fine-tune edge_lines
								for(int vl = 0; vl < lines_in_cell.vertex_lines.dim(); vl++) {
									Set<int> spans;
									if(z_dim == 2) spans += (edge_pair_map[0])[i];
									if(c_dim == 2) spans += (edge_pair_map[remaining[0]])[remaining[1]];
									lines_in_cell.vertex_lines[vl].cells = spans;
								}//END fine-tune vertex_lines

								edge_line |= lines_in_cell.edge_lines;
								vertex_line |= lines_in_cell.vertex_lines;
							}//END if 2-dimensional



						}//END iterate relevant cones from c
					}//END iterate relevant cones from z


				}//END iterate j-k-reachable cones
			}//END iterate 0-i-reachable cones
		}//END compute intersections of reachable loci

		// .............................................................................................
		cout << "Cleaning up result" << endl;

		// Step I: Clean up each type individually by checking if one contains the other or has to be glued
		// together somehow

		//Vertex line: Only check if one is contained in the other
		Set<int> double_vertices;
		for (int vl = 0; vl < vertex_line.dim(); ++vl) {
			if (!double_vertices.contains(vl)) {
				for (int ovl = 0; ovl < vertex_line.dim(); ++ovl) {
					if (vl != ovl) {
						if (vertex_line[vl].vertex == vertex_line[ovl].vertex &&
								(vertex_line[vl].cells * vertex_line[ovl].cells).size() == vertex_line[ovl].cells.size() ) {
							double_vertices += ovl;
						}
					}
				}
			}
		}//END clean up vertex_line
		vertex_line = vertex_line.slice(~double_vertices);

		//For vertex families we have to check if two families can be glued together
		std::list<VertexFamily> vf_queue;
		for(int vf = 0; vf < vertex_family.dim(); vf++) {
			vf_queue.push_back(vertex_family[vf]);
		}
		Vector<VertexFamily> approved_vfamilies;
		while(vf_queue.size() > 0) {
			VertexFamily fam = vf_queue.front(); vf_queue.pop_front();
			bool is_approved = true;
			for(int vf = 0; vf < approved_vfamilies.dim(); vf++) {
				//Case I: Both are identical: Just ignore the new one and add nothing new
				if( (approved_vfamilies[vf].edge.row(0) == fam.edge.row(0) &&
							approved_vfamilies[vf].edge.row(1) == fam.edge.row(1)) ||
						(approved_vfamilies[vf].edge.row(0) == fam.edge.row(1) &&
						 approved_vfamilies[vf].edge.row(1) == fam.edge.row(0))) {
					is_approved = false;
					break;
				}

				//Case II: At least one consists of two vertices, one of which is equal to
				// a vertex of the other. Then we glue both together, add the new element to the vf_queue and
				// go to the next vf_queue element
				if( (approved_vfamilies[vf].edge(0,0) + approved_vfamilies[vf].edge(1,0) == 2) ||
						(fam.edge(0,0) + fam.edge(1,0) == 2) ) {
					int avf_equal = -1;
					int fam_equal = -1;
					if(approved_vfamilies[vf].edge.row(0) == fam.edge.row(0)) { avf_equal = 0; fam_equal = 0; }
					if(approved_vfamilies[vf].edge.row(0) == fam.edge.row(1)) { avf_equal = 0; fam_equal = 1; }
					if(approved_vfamilies[vf].edge.row(1) == fam.edge.row(0)) { avf_equal = 1; fam_equal = 0; }
					if(approved_vfamilies[vf].edge.row(1) == fam.edge.row(1)) { avf_equal = 1; fam_equal = 1; }

					if(avf_equal != -1) {
						int other_avf = avf_equal == 0? 1 : 0;
						int other_fam = fam_equal == 0? 1 : 0;
						VertexFamily replacement;
						replacement.edge = approved_vfamilies[vf].edge.row(other_avf) / fam.edge.row(other_fam);
						vf_queue.push_back(replacement);
						approved_vfamilies = approved_vfamilies.slice(~scalar2set(vf));
						is_approved = false;
						break;
					} //END create new vertex family
				}//END Case II	
			}//END iterate approved families
			if(is_approved) approved_vfamilies |= fam;
		}//END clean up vertex families
		vertex_family = approved_vfamilies;

		//For edge lines, we check if two are equal or can be glued together
		std::list<EdgeLine> el_queue;
		for(int el = 0; el < edge_line.dim(); el++) {
			el_queue.push_back(edge_line[el]);
		}
		Vector<EdgeLine> approved_elfamilies;
		while(el_queue.size() > 0) {
			EdgeLine fam = el_queue.front(); el_queue.pop_front();
			bool is_approved = true;
			for(int el = 0; el < approved_elfamilies.dim(); el++) {
				//Interesting things only happen if both have the same direction
				if(approved_elfamilies[el].leafAtZero == fam.leafAtZero) {
					//Compatibility = Both families can be glued, i.e. in each direction either:
					// - the vertices at that end agree -> keep vertex with largest span
					// - one vertex has unbounded span and the other vertex differs by a positive multiple of
					//   the edge direction -> keep vertex with unbounded span
					// - one vertex has bounded span, which contains the other vertex. -> keep vertex with bounded span
					bool isCompatibleAtZero = false;
					Vector<Rational> vertexAtZero;
					Vector<Rational> mdistAtZero;
					bool spanAtZero = false;
					if(approved_elfamilies[el].vertexAtZero == fam.vertexAtZero) {
						isCompatibleAtZero = true;
						vertexAtZero = fam.vertexAtZero;
						spanAtZero = approved_elfamilies[el].spanAtZero || fam.spanAtZero;
					}
					else {
						//Check if difference of vertices is multiple of edge direction
						Vector<Rational> edir = degree.row(0) + degree.row(fam.leafAtZero);
						Rational vdiff = vertexDistance(approved_elfamilies[el].vertexAtZero,
								fam.vertexAtZero, edir);
						//If so, check if span is compatible
						if(vdiff != 0) {
							if(vdiff > 0 && approved_elfamilies[el].spanAtZero) {
								Rational sdiff = vertexDistance(approved_elfamilies[el].vertexAtZero,
										approved_elfamilies[el].maxDistAtZero, edir);
								isCompatibleAtZero = sdiff == 0 || sdiff >= vdiff;
								vertexAtZero = approved_elfamilies[el].vertexAtZero;
								mdistAtZero = approved_elfamilies[el].maxDistAtZero;
								spanAtZero = true;
							}		
							if(vdiff < 0 && fam.spanAtZero) {
								Rational sdiff = vertexDistance(fam.vertexAtZero, fam.maxDistAtZero,edir);
								isCompatibleAtZero = sdiff == 0 || sdiff >= -vdiff;
								vertexAtZero = fam.vertexAtZero;
								mdistAtZero = fam.maxDistAtZero;
								spanAtZero = true;
							}
						}//END if is multiple of edge direction
					}//END check compat at zero
					if(!isCompatibleAtZero) continue;

					bool isCompatibleAwayZero = false;
					Vector<Rational> vertexAwayZero;
					Vector<Rational> mdistAwayZero;
					bool spanAwayZero = false;
					if(approved_elfamilies[el].vertexAwayZero == fam.vertexAwayZero) {
						isCompatibleAwayZero = true;
						vertexAwayZero = fam.vertexAwayZero;
						spanAwayZero = approved_elfamilies[el].spanAwayZero || fam.spanAwayZero;
					}
					else {
						//Check if difference of vertices is multiple of edge direction
						Vector<int> rem(sequence(1,3) - fam.leafAtZero);
						Vector<Rational> edir = degree.row(rem[0]) + degree.row(rem[1]);
						Rational vdiff = vertexDistance(approved_elfamilies[el].vertexAwayZero,
								fam.vertexAwayZero, edir);
						//If so, check if span is compatible
						if(vdiff != 0) {
							if(vdiff > 0 && approved_elfamilies[el].spanAwayZero) {
								Rational sdiff = vertexDistance(approved_elfamilies[el].vertexAwayZero,
										approved_elfamilies[el].maxDistAwayZero, edir);
								isCompatibleAwayZero = sdiff == 0 || sdiff >= vdiff;
								vertexAwayZero = approved_elfamilies[el].vertexAwayZero;
								mdistAwayZero = approved_elfamilies[el].maxDistAwayZero;
								spanAwayZero = true;
							}		
							if(vdiff < 0 && fam.spanAwayZero) {
								Rational sdiff = vertexDistance(fam.vertexAwayZero, fam.maxDistAwayZero,edir);
								isCompatibleAwayZero = sdiff == 0 || sdiff >= -vdiff;
								vertexAwayZero = fam.vertexAwayZero;
								mdistAwayZero = fam.maxDistAwayZero;
								spanAwayZero = true;
							}
						}//END if is multiple of edge direction
					}//END check compat away zero
					if(!isCompatibleAwayZero) continue;

					//If we arrive here, we have to glue
					EdgeLine newel;
					newel.vertexAtZero = vertexAtZero;
					newel.vertexAwayZero = vertexAwayZero;
					newel.leafAtZero = fam.leafAtZero;
					newel.spanAtZero = spanAtZero;
					newel.spanAwayZero = spanAwayZero;
					newel.maxDistAtZero = mdistAtZero;
					newel.maxDistAwayZero = mdistAwayZero;
					approved_elfamilies = approved_elfamilies.slice(~scalar2set(el));
					el_queue.push_back(newel);
					is_approved = false;
					break;
				}//END if same direction
			}//END iterate approved families
			if(is_approved) approved_elfamilies |= fam;
		}//END clean up edge lines
		edge_line = approved_elfamilies;

		//Clean up edge_families: Check if two can be glued together
		std::list<EdgeFamily> ef_queue;
		for(int ef = 0; ef < edge_family.dim(); ef++) {
			ef_queue.push_back(edge_family[ef]);
		}
		Vector<EdgeFamily> approvedFam;
		while(ef_queue.size() > 0) {
			EdgeFamily fam = ef_queue.front(); ef_queue.pop_front();
			bool is_approved = true;
			for(int ef = 0; ef < approvedFam.dim(); ef++) {
				EdgeFamily compFam = approvedFam[ef];
				//We can only glue, if both have the same direction
				if(fam.leafAtZero == compFam.leafAtZero) {
					//We can glue if at least one family has 2-vertex-borders that intersect the other edges
					// in a vertex
					if(fam.borderAtZero(0,0) + fam.borderAtZero(1,0) == 2 || compFam.borderAtZero(0,0) + compFam.borderAtZero(1,0) == 2) {
						int fam_equal = -1;
						int comp_equal = -1;
						//Check if two vertices agree
						if(fam.borderAtZero.row(0) == compFam.borderAtZero.row(0)) { fam_equal = 0; comp_equal = 0;}
						if(fam.borderAtZero.row(0) == compFam.borderAtZero.row(1)) { fam_equal = 0; comp_equal = 1;}
						if(fam.borderAtZero.row(1) == compFam.borderAtZero.row(0)) { fam_equal = 1; comp_equal = 0;}
						if(fam.borderAtZero.row(1) == compFam.borderAtZero.row(1)) { fam_equal = 1; comp_equal = 1;}

						if(fam_equal != -1) {
							int fam_other = 1 - fam_equal;
							int comp_other = 1 - comp_equal;
							//Check if on the other side also two vertices agree AND that this is not the same
							// vertex as on this side
							int side_fam_equal = -1;
							int side_comp_equal = -1;
							if (fam.borderAwayZero.row(0) == compFam.borderAwayZero.row(0) &&
									fam.borderAwayZero.row(0) != fam.borderAtZero.row(fam_equal))
							{side_fam_equal = 0; side_comp_equal = 0;}
							if (fam.borderAwayZero.row(0) == compFam.borderAwayZero.row(1) &&
									fam.borderAwayZero.row(0) != fam.borderAtZero.row(fam_equal))
							{side_fam_equal = 0; side_comp_equal = 1;}
							if (fam.borderAwayZero.row(1) == compFam.borderAwayZero.row(0) &&
									fam.borderAwayZero.row(1) != fam.borderAtZero.row(fam_equal))
							{side_fam_equal = 1; side_comp_equal = 0;}
							if (fam.borderAwayZero.row(1) == compFam.borderAwayZero.row(1) &&
									fam.borderAwayZero.row(1) != fam.borderAtZero.row(fam_equal))
							{side_fam_equal = 1; side_comp_equal = 1;}
							if (side_fam_equal != -1) {
								int side_fam_other = 1 - side_fam_equal;
								int side_comp_other = 1 - side_comp_equal;

								Matrix<Rational> nzero_border =
									fam.borderAtZero.row(fam_other) / compFam.borderAtZero.row(comp_other);
								Matrix<Rational> naway_border =
									fam.borderAwayZero.row(side_fam_other) / compFam.borderAwayZero.row(side_comp_other);
								EdgeFamily replacement;
								replacement.leafAtZero = fam.leafAtZero;
								replacement.edgesAtZero = fam.edgesAtZero | compFam.edgesAtZero;
								replacement.edgesAwayZero = fam.edgesAwayZero | compFam.edgesAwayZero;
								replacement.borderAtZero = nzero_border;
								replacement.borderAwayZero = naway_border;
								ef_queue.push_back(replacement);
								approvedFam = approvedFam.slice(~scalar2set(ef));
								is_approved = false;
								break;
							}//END if two vertices agree on the other side
						}//END if two vertices agree
					}//END if can be glued
				}//END if direction agrees	
			}//END iterate approved families
			if(is_approved) approvedFam |= fam;
		}//END clean up edge families
		edge_family = approvedFam;

		// STEP II: A family of one type may be contained in a family of a different type

		//Check if any edge line is contained in a spanning vertex line
		Set<int> redundant_el;
		for(int el = 0; el < edge_line.dim(); el++) {
			for(int vl = 0; vl < vertex_line.dim(); vl++) {
				//One vertex has to be equal and the other vertex has to be in the span of the vertex line
				if(edge_line[el].vertexAtZero == vertex_line[vl].vertex ||
						edge_line[el].vertexAwayZero == vertex_line[vl].vertex) {
					Vector<Rational> other_vertex;
					Set<int> dir_indices;
					if(edge_line[el].vertexAtZero == vertex_line[vl].vertex) {
						other_vertex = edge_line[el].vertexAwayZero;
						dir_indices = sequence(1,3) - scalar2set(edge_line[el].leafAtZero);
					}
					else {
						other_vertex = edge_line[el].vertexAtZero;
						dir_indices = scalar2set(0) + scalar2set(edge_line[el].leafAtZero);
					}
					bool span_has_dir = false;
					for(Entire<Set<int> >::iterator c = entire(vertex_line[vl].cells); !c.at_end(); c++) {
						if(  (dir_indices * index_to_pair_map[*c]).size() == 2) {
							span_has_dir = true; break;
						}
					}
					bool is_compatible = false;
					if(span_has_dir) {
						Vector<Rational> edir = accumulate(rows(degree.minor(dir_indices,All)), operations::add());
						Rational vdiff = vertexDistance(vertex_line[vl].vertex, other_vertex, edir);
						if(vdiff > 0) {
							Vector<Rational> mdist = maximalDistanceVector(vertex_line[vl].vertex, edir, lindom_rays, lindom_cones, funmat);
							Rational mdiff = vertexDistance(vertex_line[vl].vertex, mdist, edir);
							is_compatible = mdiff == 0 || mdiff >= vdiff;
						}
					}
					if(is_compatible) redundant_el += el;
				}
			}
		}
		edge_line = edge_line.slice(~redundant_el);

		//Check if any vertex_line is contained in a vertex_family
		Set<int> redundant_vl;
		for(int vl = 0; vl < vertex_line.dim(); vl++) {
			for(int vf = 0; vf < vertex_family.dim(); vf++) {
				//Check if the vertex is a vertex of the family and the spans contain the direction
				int fam_dir = vertexFamilyDirection(vertex_family[vf]);
				if( vertex_line[vl].vertex == vertex_family[vf].edge.row(0) ||
						vertex_line[vl].vertex == vertex_family[vf].edge.row(1) ){
					Set<int> cells = vertex_line[vl].cells;
					bool is_compatible = true;
					for(Entire<Set<int> >::iterator c = entire(cells); !c.at_end(); c++) {
						if(!index_to_pair_map[*c].contains(fam_dir)) {
							is_compatible = false; break;
						}
					}
					if(is_compatible) redundant_vl += vl;
				}
			}//END iterate vertex_family
		}//END compare vertex_line to vertex_family
		vertex_line = vertex_line.slice(~redundant_vl);



		//Create corresponding line objects ...............................................................
		cout << "Creating lines as complexes..." << endl;

		perl::Object result(perl::ObjectType::construct<Max>("LinesInCubic"));

		result.take("CUBIC") << X;
		result.take("POLYNOMIAL") << ratfct.give("NUMERATOR");

		//Create vertex_line objects:
		// If two rays in such an object span a 2-dim-cell, this is computed as follows:
		// We check, how far the line in direction of the sum of the two corr. rays lies in X
		// If all of it lies in X, the 2-dim cell is just the vertex + the two rays. If not, let
		// w be the end vertex of the line. Then we have two 2-dim. cells: conv(vertex,w) + each of the rays
		for(int ivert = 0; ivert < vertex_line.dim(); ++ivert) {
			perl::Object var("Cycle<Max>");
			Matrix<Rational> var_rays = degree / vertex_line[ivert].vertex ;
                        RestrictedIncidenceMatrix<> var_cones;
			//Find all rays that are NOT involved in a 2-dim cell
			Set<int> rays_in_cells;
			for (const int vcell : vertex_line[ivert].cells) {
				rays_in_cells += index_to_pair_map[vcell];
			}
			Set<int> rays_not_in_cells = sequence(0,4) - rays_in_cells;
			//Add those rays not in 2-dim. cells
			for (const int i : rays_not_in_cells) {
				var_cones /= scalar2set(4) + scalar2set(i);
			}
			//Now we compute the 2-dimensional cells as described above
			for (const auto& span : vertex_line[ivert].cells) {
				Vector<Rational> md_vector = maximalDistanceVector(
						vertex_line[ivert].vertex,
						accumulate(rows(degree.minor(index_to_pair_map[span],All)),operations::add()),
						lindom_rays, lindom_cones, funmat);
				if (md_vector.dim() == 0) {
					var_cones /= scalar2set(4) + index_to_pair_map[span];
				}
				else {
					var_rays /= md_vector;
					Vector<int> dirs(index_to_pair_map[span]);
					var_cones /= scalar2set(4) + scalar2set(var_rays.rows()-1) + dirs[0];
					var_cones /= scalar2set(4) + scalar2set(var_rays.rows()-1) + dirs[1];
				}
			}
			var.take("VERTICES") << thomog(var_rays);
                        const IncidenceMatrix<> var_cones_final(std::move(var_cones));
			var.take("MAXIMAL_POLYTOPES") << var_cones_final;
			if (vertex_line[ivert].cells.size() == 0) {
				var.take("WEIGHTS") << ones_vector<Integer>(var_cones_final.rows());
				result.add("LIST_ISOLATED_NO_EDGE", var);
			}
			else {
				var.take("PURE") << false;
				result.add("LIST_FAMILY_FIXED_VERTEX", var);
			}
		}

		//Create vertex_family objects: Find the direction spanned by the family and only add the remaining three
		//as rays
		for (int fvert = 0; fvert < vertex_family.dim(); ++fvert) {
			int missing_dir = vertexFamilyDirection(vertex_family[fvert]);
			Matrix<Rational> var_rays = vertex_family[fvert].edge / degree.minor(~scalar2set(missing_dir),All);
			const IncidenceMatrix<> var_cones{ {0,1,2}, {0,1,3}, {0,1,4} };
			perl::Object var("Cycle<Max>");
			var.take("VERTICES") << thomog(var_rays);
			var.take("MAXIMAL_POLYTOPES") << var_cones;
			var.take("PURE") << false;
			result.add("LIST_FAMILY_MOVING_VERTEX", var);

		}



		//Create edge_lines
		// Two-dimensional cells at each end are computed as for vertex_line
		for (int el = 0; el < edge_line.dim(); ++el) {
			perl::Object var("Cycle<Max>");
			Matrix<Rational> var_rays = edge_line[el].vertexAtZero / edge_line[el].vertexAwayZero / degree;
			RestrictedIncidenceMatrix<> var_cones;
			var_cones /= sequence(0,2);
			if (edge_line[el].spanAtZero) {
				Vector<Rational> z_mdvector = edge_line[el].maxDistAtZero;
				if (z_mdvector.dim() == 0) {
					var_cones /= (scalar2set(0) + scalar2set(2) + scalar2set(edge_line[el].leafAtZero+2));
				}
				else {
					var_rays /= z_mdvector;
					var_cones /= (scalar2set(0) + scalar2set(var_rays.rows()-1) + scalar2set(2));
					var_cones /=
						(scalar2set(0) + scalar2set(var_rays.rows()-1) + scalar2set(edge_line[el].leafAtZero+2));
				}
			}
			else {
				var_cones /= (scalar2set(0) + scalar2set(2));
				var_cones /= (scalar2set(0) + scalar2set(edge_line[el].leafAtZero+2));
			}

			Vector<int> rem(sequence(1,3) - edge_line[el].leafAtZero);
			if(edge_line[el].spanAwayZero) {
				Vector<Rational> c_mdvector = edge_line[el].maxDistAwayZero;
				if(c_mdvector.dim() == 0) {
					var_cones /= (scalar2set(1) + scalar2set(rem[0]+2) + scalar2set(rem[1]+2));
				}
				else {
					var_rays /= c_mdvector;
					var_cones /= (scalar2set(1) + scalar2set(var_rays.rows()-1) + scalar2set(rem[0]+2));
					var_cones /= (scalar2set(1) + scalar2set(var_rays.rows()-1) + scalar2set(rem[1]+2));
				}
			}
			else {
				var_cones /= (scalar2set(1) + (rem[0]+2));
				var_cones /= (scalar2set(1) + (rem[1]+2));
			}



			var.take("VERTICES") << thomog(var_rays);
                        const IncidenceMatrix<> var_cones_final(std::move(var_cones));
			var.take("MAXIMAL_POLYTOPES") << var_cones_final;
			if (edge_line[el].spanAtZero || edge_line[el].spanAwayZero) {
				var.take("PURE") << false;
				result.add("LIST_FAMILY_FIXED_EDGE",var);
			}
			else {
				var.take("WEIGHTS") << ones_vector<Integer>(var_cones_final.rows());
				result.add("LIST_ISOLATED_EDGE",var);
			}

		}

		//Created edge families
		for (int ef = 0; ef < edge_family.dim(); ef++) {
			perl::Object var("Cycle<Max>");
			Matrix<Rational> var_rays = degree;
			RestrictedIncidenceMatrix<> var_cones;
			for(int eg = 0; eg < edge_family[ef].edgesAtZero.dim(); eg++) {
				int start_index = var_rays.rows();
				var_rays /= edge_family[ef].edgesAtZero[eg];
				var_rays /= edge_family[ef].edgesAwayZero[eg];
				var_cones /= sequence(start_index, 4);

				//We have to canonicalize each of the direction cones, since they might be one-dimensional
				Vector<int> z_cone(sequence(start_index,2));
				z_cone |= 0;
				Vector<int> i_cone(sequence(start_index,2));
				i_cone |= (edge_family[ef].leafAtZero);

				Vector<int> rem(sequence(1,3) - edge_family[ef].leafAtZero);

				Vector<int> j_cone(sequence(start_index+2,2));
				j_cone |= rem[0];
				Vector<int> k_cone(sequence(start_index+2,2));
				k_cone |= rem[1];

				var_cones /= Set<int>(z_cone.slice(sv.canonicalize(var_rays.minor(Set<int>(z_cone),All), dummy_lineality).first));
				var_cones /= Set<int>(i_cone.slice(sv.canonicalize(var_rays.minor(Set<int>(i_cone),All), dummy_lineality).first));
				var_cones /= Set<int>(j_cone.slice(sv.canonicalize(var_rays.minor(Set<int>(j_cone),All), dummy_lineality).first));
				var_cones /= Set<int>(k_cone.slice(sv.canonicalize(var_rays.minor(Set<int>(k_cone),All), dummy_lineality).first));

			}

			var.take("VERTICES") << thomog(var_rays);
                        const IncidenceMatrix<> var_cones_final(std::move(var_cones));
			var.take("MAXIMAL_POLYTOPES") << var_cones_final;
			var.take("PURE") << false;
			result.add("LIST_FAMILY_MOVING_EDGE", var);
		}

		cout << "Done." << endl;

		return result;
	}//END linesInCubic


	Function4perl(&linesInCubic,"linesInCubic(Polynomial<TropicalNumber<Max>>)");
}}

