with Integer_Linear_System_Solvers;      use Integer_Linear_System_Solvers;
with Transformations;                    use Transformations;
with Integer_Vectors_Utilities;          use Integer_Vectors_Utilities;
with Lists_of_Vectors_Utilities;         use Lists_of_Vectors_Utilities;
with Transforming_Integer_Vector_Lists;  use Transforming_Integer_Vector_Lists;
with Vertices;                           use Vertices;

--with text_io,integer_io;               use text_io,integer_io;
--with Integer_Vectors_io;               use Integer_Vectors_io;
--with Integer_Matrices_io;              use Integer_Matrices_io;
--with Transformations_io;               use Transformations_io;
--with Lists_of_Integer_Vectors_io;      use Lists_of_Integer_Vectors_io;

package body Inner_Normal_Cones is

-- NOTE ON THE IMPLEMENTATION :
--   There is a safety mode in which after each computation of the generators,
--   it is automatically tested whether the generators satisfy the 
--   inequalities of the normal cone.  If not, then the bug is reported and
--   a program_error is raised.

-- AUXILIAIRIES :

  function Lower ( a1,b1,a2,b2 : integer ) return boolean is

  -- DESCRIPTION :
  --   Returns true if a1/b1 < a2/b2, false otherwise. 

  -- REQUIRED : b1 /= 0 and b2 /= 0.

  begin
    if b1*b2 > 0
     then return (a1*b2 - a2*b1 < 0);
     else return (a1*b2 - a2*b1 > 0);
    end if;
  end Lower;

  function Higher ( a1,b1,a2,b2 : integer ) return boolean is

  -- DESCRIPTION :
  --   Returns true if a1/b1 > a2/b2, false otherwise. 

  -- REQUIRED : b1 /= 0 and b2 /= 0.

  begin
    if b1*b2 > 0
     then return (a1*b2 - a2*b1 > 0);
     else return (a1*b2 - a2*b1 < 0);
    end if;
  end Higher;

  function Compute_Facets ( l : List; x : Vector ) return Faces is

  -- DESCRIPTION :
  --   Returns all facets of conv(l) that all contain x.
  --   Checks whether x belongs to the list or not.

    res : Faces;
    n : constant natural := x'length;

  begin
    if Is_In(l,x)
     then res := Create(n-1,n,l,x);
     else declare
            wrk : List := l;
            lx : Link_to_Vector;
          begin
            lx := new Vector'(x);
            Construct(lx,wrk);
            res := Create(n-1,n,wrk,x);
          end;
    end if;
    return res;
  end Compute_Facets;

  procedure Shifted_Points ( l : in List; x : in Vector;
                             res,res_last : in out List ) is

  -- DESCRIPTION :
  --   Appends the list of shifted points w.r.t. x to the list res.

    tmp : List := l;

  begin
    tmp := l;
    while not Is_Null(tmp) loop
      Append_Diff(res,res_last,Head_Of(tmp).all-x);
      tmp := Tail_Of(tmp);
    end loop;
  end Shifted_Points;

  function In_Hull ( l : List; x : Vector ) return boolean is

  -- DESCRIPTION :
  --   Returns true if the vector x belongs the convex hull spanned by
  --   the points in l minus the point x itself.

    res : boolean;
    lx : List;

  begin
    Copy(l,lx);
    Remove(lx,x);
    res := Is_In_Hull(x,lx);
    Clear(lx);
    return res;
  end In_Hull;

-- AUXILIAIRIES TO CONSTRUCT THE NORMALS :

  procedure Normal ( m : in out Matrix; v : out Vector ) is

  -- DESCRIPTION :
  --   Computes the normal to the vectors stored in the rows of the matrix m.

  -- REQUIRED : the matrix m is square!

    res : Vector(m'range(2));

  begin
    Upper_Triangulate(m); Scale(m);
    res := (res'range => 0);
    Solve0(m,res);
    v := res;
  end Normal;

  procedure Orientate_Normal ( l : in List; f : in Face; 
                               normal : in out Vector ) is

  -- DESCRIPTION :
  --   Orientates the normal of the face such that it becomes an inner normal
  --   w.r.t. the points in the list l.

    tmp : List := l;
    ip : constant integer := f(f'first).all*normal;
    pt : Vector(Head_Of(l)'range);
    done : boolean := false;
    ptip : integer;

  begin
    while not Is_Null(tmp) loop
      pt := Head_Of(tmp).all;
      if not Is_In(f,pt)
       then ptip := pt*normal;
            if ptip /= ip
             then if ptip < ip
                   then normal := -normal;
                  end if;
                  done := true;
            end if;
      end if;
      exit when done;
      tmp := Tail_Of(tmp);
    end loop;
  end Orientate_Normal;

  function Inner_Normal ( l : List; facet : Face ) return Vector is

  -- DESCRIPTION :
  --   Returns the inner normal to the facet.

    res,fst : Vector(facet(facet'first)'range);
    m : Matrix(facet'first+1..facet'last,facet(facet'first)'range);

  begin
    fst := facet(facet'first).all;
    for i in m'first(1)..m'last(1) loop
      for j in m'range(2) loop
        m(i,j) := facet(i)(j) - fst(j);
      end loop;
    end loop;
    Normal(m,res);
    Orientate_Normal(l,facet,res);
    return res;
  end Inner_Normal;

  procedure Inner_Normals ( l : in List; facets : in Faces;
                            iv,iv_last : in out List ) is

  -- DESCRIPTION :
  --   Returns the list of all inner normals to the facets of conv(l).
  --   If there is only one facet, then both `inner' and `outer' normal
  --   will be returned, because there is nothing to orientate with.
  --   This means that also the negation of the normal will be in the list iv.

    tmp : Faces := facets;

  begin
    while not Is_Null(tmp) loop
      declare
        f : Face := Head_Of(tmp);
        v : constant Vector := Inner_Normal(l,Head_Of(tmp));
      begin
        Append(iv,iv_last,v);
        if Length_Of(facets) = 1
         then Append(iv,iv_last,-v);
        end if;
      end;
      tmp := Tail_Of(tmp);
    end loop;
  end Inner_Normals;

  function Number_of_Rows ( l : List ) return natural is

  -- DESCRIPTION :
  --   Returns Maximum(dimension,Length_Of(l)).

  -- REQUIRED : not Is_Null(l).

    n : constant natural := Head_Of(l)'length;
    m : constant natural := Length_Of(l);

  begin
    if n > m
     then return n;
     else return m;
    end if;
  end Number_of_Rows;

  function Normal ( l : List ) return Vector is

  -- DESCRIPTION :
  --   Return a normal to the vectors in the list.

  -- REQUIRED : Length_Of(l) <= n = Head_Of(l)'length
  --   or the points in l all lie on the same facet.

    fst : Link_to_Vector := Head_Of(l);
    m : Matrix(1..Number_of_Rows(l),fst'range);
    cnt : natural := 0;
    res : Vector(fst'range);
    tmp : List := Tail_Of(l);

  begin
    while not Is_Null(tmp) loop
      res := Head_Of(tmp).all;
      cnt := cnt+1;
      for i in res'range loop
        m(cnt,i) := res(i) - fst(i);
      end loop;
      tmp := Tail_Of(tmp);
    end loop;
    for i in cnt+1..m'last(1) loop
      for j in m'range(2) loop
        m(i,j) := 0;
      end loop;
    end loop;
    Normal(m,res);
    return res;
  end Normal;

  procedure Normals ( l : in List; x : in Vector; iv,iv_last : in out List ) is

  -- DESCRIPTION :
  --   Computes a list of all normals to the vectors in the list.

  -- REQUIRED : Length_Of(l) <= n = x'length.
  --   or all points in l lie on the same facet.

    nor : constant Vector := Normal(l);
    piv : natural := Pivot(nor);
    pivnor : Vector(nor'range);   -- normal with pivnor(piv) > 0

  begin
    if piv <= nor'last
     then if x'length > 2
           then if nor(piv) < 0
                 then pivnor := -nor;
                 else pivnor := nor;
                end if;
                declare
                  t : Transfo := Build_Transfo(pivnor,piv);
                  tx : constant Vector := Reduce(t*x,piv);
                  tt : Transfo := Transpose(t);
                  tl : List := Transform_and_Reduce(t,piv,l);
                begin
                  if Length_Of(tl) <= nor'length-1
                   then Normals(tl,tx,iv,iv_last);
                   else declare
                          facets : Faces := Compute_Facets(tl,tx);
                        begin
                          if Length_Of(facets) > 1
                           then Inner_Normals(tl,facets,iv,iv_last);
                           else Normals(tl,tx,iv,iv_last);
                          end if;
                        end;
                  end if;
                  Insert_and_Transform(iv,piv,0,tt);
                  iv_last := Pointer_to_Last(iv);
                  Clear(t); Deep_Clear(tl); Clear(tt);
                end;
          end if;
          Append(iv,iv_last,nor); Append(iv,iv_last,-nor);
    end if;
  end Normals;

-- CONSTRUCTORS FOR PRIMAL REPRESENTATION :

  function Generators ( l : List; facets : Faces; x : Vector ) return List is

    res,res_last : List;

  begin
    if Length_Of(facets) > 1
     then Inner_Normals(l,facets,res,res_last);     -- compute inner normals
     elsif In_Hull(l,x)
         then return res;                           -- empty normal cone
         else Normals(l,x,res,res_last);            -- compute normals
              if Length_Of(l) = 2
               then Shifted_Points(l,x,res,res_last);
              end if;
    end if;
   -- SAFETY MODE :
   -- if not Contained_in_Cone(l,x,res)
   --  then put_line("Bug raised for computing generators for list"); put(l);
   --       put(" and the point : "); put(x); new_line;
   --       raise PROGRAM_ERROR;
   -- end if;
    return res;
  end Generators;

  function Generators ( l : List; x : Vector ) return List is

    res,res_last : List;
    n : constant natural := x'length;
    facets : Faces;

  begin
    if In_Hull(l,x)
     then return res;                                -- empty normal cone
     else if Length_Of(l) > n
           then facets := Compute_Facets(l,x);
          end if;
          if Length_Of(facets) > 1
           then res := Generators(l,facets,x);
           else Normals(l,x,res,res_last);
                if Length_Of(l) = 2
                 then Shifted_Points(l,x,res,res_last);
                end if;
          end if;
         -- SAFETY MODE :
         -- if not Contained_in_Cone(l,x,res)
         --  then put_line("Bug raised for computing generators for list");
         --       put(l); put(" and the point : "); put(x); new_line;
         --       raise PROGRAM_ERROR;
         -- end if;
          return res;
    end if;
  end Generators;

-- CONSTRUCTOR FOR DUAL REPRESENTATION :

  function Inner_Normal_Cone ( l : List; x : Vector ) return Matrix is

    res : Matrix(x'range,1..Length_Of(l)-1);
       -- CAUTION : Is_In(l,x) must hold !!
    y : Vector(x'range);
    cnt : natural := 0;
    tmp : List := l;

  begin
    while not Is_Null(tmp) loop
      y := Head_Of(tmp).all;
      if not Equal(x,y)                  -- avoid trivial inequality
       then cnt := cnt+1;
            for i in x'range loop
              res(i,cnt) := y(i) - x(i);
            end loop;
      end if;
      tmp := Tail_Of(tmp);
    end loop;
    return res;
  end Inner_Normal_Cone;

  function Included_Normal_Cone ( gx : List; x : Vector ) return Matrix is

    res : Matrix(x'first-1..x'last,1..Length_Of(gx));
    tmp : List := gx;
    v : Vector(x'range);

  begin
    for j in res'range(2) loop
      v := Head_Of(tmp).all;
      res(res'first(1),j) := x*v;
      for i in res'first(1)+1..res'last(1) loop
        res(i,j) := v(i);
      end loop;
      tmp := Tail_Of(tmp);
    end loop;
    return res;
  end Included_Normal_Cone;

-- PRIMITIVE SELECTORS :

  function Evaluate ( m : Matrix; i : integer; v : Vector ) return integer is

    sum : integer := 0;

  begin
    for j in v'range loop
      sum := sum + m(j,i)*v(j);
    end loop;
    return sum;
  end Evaluate;

  function Satisfies ( m : Matrix; i : integer; v : Vector ) return boolean is
  begin
    if m'first(1) = v'first-1
     then return (Evaluate(m,i,v) >= m(0,i));
     else return (Evaluate(m,i,v) >= 0);
    end if;
  end Satisfies;

  function Strictly_Satisfies ( m : Matrix; i : integer; v : Vector )
                              return boolean is
  begin
    if m'first(1) = v'first-1
     then return (Evaluate(m,i,v) > m(0,i));
     else return (Evaluate(m,i,v) > 0);
    end if;
  end Strictly_Satisfies;

  function Satisfies ( m : Matrix; v : Vector ) return boolean is
  begin
    for i in m'range(2) loop
      if not Satisfies(m,i,v)
       then return false;
      end if;
    end loop;
    return true;
  end Satisfies;

  function Strictly_Satisfies ( m : Matrix; v : Vector ) return boolean is
  begin
    for i in m'range(2) loop
      if not Strictly_Satisfies(m,i,v)
       then return false;
      end if;
    end loop;
    return true;
  end Strictly_Satisfies;

  function Satisfies ( m : Matrix; v : List ) return boolean is

    tmp : List := v;

  begin
    while not Is_Null(tmp) loop
      if not Satisfies(m,Head_Of(tmp).all)
       then return false;
       else tmp := Tail_Of(tmp);
      end if;
    end loop;
    return true;
  end Satisfies;

  function Strictly_Satisfies ( m : Matrix; v : List ) return boolean is

    tmp : List := v;

  begin
    while not Is_Null(tmp) loop
      if not Strictly_Satisfies(m,Head_Of(tmp).all)
       then return false;
       else tmp := Tail_Of(tmp);
      end if;
    end loop;
    return true;
  end Strictly_Satisfies;

-- SECONDARY SELECTORS :

  function Satisfy ( m : Matrix; l : List ) return List is

    res,res_last,tmp : List;

  begin
    tmp := l;
    while not Is_Null(tmp) loop
      if Satisfies(m,Head_Of(tmp).all)
       then Append(res,res_last,Head_Of(tmp).all);
      end if;
      tmp := Tail_Of(tmp);
    end loop;
    return res;
  end Satisfy;

  function Strictly_Satisfy ( m : Matrix; l : List ) return List is

    res,res_last,tmp : List;

  begin
    tmp := l;
    while not Is_Null(tmp) loop
      if Strictly_Satisfies(m,Head_Of(tmp).all)
       then Append(res,res_last,Head_Of(tmp).all);
      end if;
      tmp := Tail_Of(tmp);
    end loop;
    return res;
  end Strictly_Satisfy;

  function Contained_in_Cone ( l : List; x : Vector; v : List )
                             return boolean is

    ineq : Matrix(x'range,1..Length_Of(l)-1) := Inner_Normal_Cone(l,x);

  begin
    return Satisfies(ineq,v);
  end Contained_in_Cone;

  function Strictly_Contained_in_Cone ( l : List; x : Vector; v : List )
                                      return boolean is

    ineq : Matrix(x'range,1..Length_Of(l)-1) := Inner_Normal_Cone(l,x);

  begin
    return Strictly_Satisfies(ineq,v);
  end Strictly_Contained_in_Cone;

  function Contained_in_Cone ( l,v : List ) return boolean is

    tmp : List := l;

  begin
    while not Is_Null(tmp) loop
      if Contained_in_Cone(l,Head_Of(tmp).all,v)
       then -- put("true for vector : "); put(Head_Of(tmp).all); new_line;
            return true;
       else tmp := Tail_Of(tmp);
      end if;
    end loop;
    return false;
  end Contained_in_Cone;

  function Strictly_Contained_in_Cone ( l,v : List ) return boolean is

    tmp : List := l;

  begin
    while not Is_Null(tmp) loop
      if Strictly_Contained_in_Cone(l,Head_Of(tmp).all,v)
       then return true;
       else tmp := Tail_Of(tmp);
      end if;
    end loop;
    return false;
  end Strictly_Contained_in_Cone;

-- CONCERNING THE UNION OF CONES :

  function In_Union ( v1,v2 : Vector; k1,k2 : Matrix ) return boolean is

  -- ALGORITHM : consider x(t) = (1-t)*v1 + t*v2, for t in [0,1]
  --   and compute t1: smallest t such that Satisfies(k1,x(t))
  --   and t2: largest t such that Satisfies(k2,x(t)).
  --   Then t1 >= t2 makes the test returning true, false otherwise.

    diff : constant Vector := v1-v2;
    t1a,t1b,t2a,t2b,a,b : integer;          -- t1 = t1a/t1b and t2 = t2a/t2b

  begin
    t1a := 1; t1b := 1;                            -- initially t1 = 1 = 1/1
    for i in k1'range(2) loop
      b := Evaluate(k1,i,diff);
      if b /= 0
       then a := Evaluate(k1,i,v1);
            if Lower(a,b,t1a,t1b)
             then t1a := a; t1b := b;
            end if;
      end if;
    end loop;
    t2a := 0; t2b := 1;                            -- initially t2 = 0 = 0/1
    for i in k2'range(2) loop
      b := Evaluate(k2,i,diff);
      if b /= 0
       then a := Evaluate(k2,i,v1);
            if Higher(a,b,t2a,t2b)
             then t2a := a; t2b := b;
            end if;
      end if;
    end loop;
   -- put("In_Union for "); put(v1); put(" and "); put(v2);
   -- put(" : t1 = "); put(t1a,1); put("/"); put(t1b,1);
   -- put(" : t2 = "); put(t2a,1); put("/"); put(t2b,1); new_line;
   -- put_line("k1 : "); put(k1); put_line("k2 : "); put(k2);
    return not Lower(t1a,t1b,t2a,t2b);
  end In_Union;

  function In_Union ( v1,v2 : List; k1,k2 : Matrix ) return boolean is

    tmpv1,tmpv2 : List;

  begin
    tmpv1 := v1;
    while not Is_Null(tmpv1) loop
      tmpv2 := v2;
      while not Is_Null(tmpv2) loop
        if not In_Union(Head_Of(tmpv1).all,Head_Of(tmpv2).all,k1,k2)
         then return false;
         else tmpv2 := Tail_Of(tmpv2);
        end if;
      end loop;
      tmpv1 := Tail_Of(tmpv1);
    end loop;
    return true;
  end In_Union;

end Inner_Normal_Cones;
