with unchecked_deallocation;
with Integer_Matrices,Transformations;    use Integer_Matrices,Transformations;
with Integer_Linear_System_Solvers;       use Integer_Linear_System_Solvers;
with Integer_Vectors_Utilities;           use Integer_Vectors_Utilities;

package body Simplices is

-- DATA STRUCTURES :

  type Point is record
    pt : Link_to_Vector;    -- the point
    si : Simplex;           -- by pivoting a new simplex is obtained
  end record;
  type Points is array ( integer range <> ) of Point;

  type Simplex_Rep ( n : natural ) is record
    nor : vector(1..n);     -- normal to the simplex
    tra : Transfo;          -- transformation to triangulate the cell
    pts : points(1..n);     -- vector of points
  end record;

-- AUXILIAIRIES :

  function Diagonalize ( s : simplex ) return matrix is

  -- DESCRIPTION :
  --   Places the vertices of the simplex, shifted w.r.t. the first point,
  --   in a matrix.  Returns the triangulated matrix.

    a : matrix(1..s.n-1,1..s.n-1);
    x,y : Vector(1..s.n-1);

  begin
    for k in 2..s.n loop
      x := s.pts(k).pt(x'range) - s.pts(1).pt(x'range);
      y := s.tra*x;
      for i in a'range(1) loop
        a(i,k-1) := y(i);
      end loop;
    end loop;
    return a;
  end Diagonalize;

  function Diagonalize ( s : simplex; pt : Vector ) return matrix is

  -- DESCRIPTION :
  --   Places the vertices of the simplex, shifted w.r.t. the first point,
  --   in a matrix.  Returns the triangulated matrix.

    a : matrix(1..s.n-1,1..s.n);
    x,y : Vector(1..s.n-1);

  begin
    for k in 2..s.n loop
      x := s.pts(k).pt(x'range) - s.pts(1).pt(x'range); y := s.tra*x;
      for i in a'range(1) loop
        a(i,k-1) := y(i);
      end loop;
    end loop;
    x := pt(x'range) - s.pts(1).pt(x'range); y := s.tra*x;
    for i in a'range(1) loop
      a(i,s.n) := y(i);
    end loop;
    return a;
  end Diagonalize;

  function Create ( pts : Integer_Vectors_of_Vectors.Vector ) return Transfo is

    a,l : matrix(pts'first..pts'last-1,pts'first..pts'last-1);
    x : vector(pts(pts'first)'range);

  begin
    for k in pts'first+1..pts'last loop
      x := pts(k).all - pts(pts'first).all;
      for i in a'range(1) loop
        a(i,k-1) := x(i);
      end loop;
    end loop;
    Upper_Triangulate(l,a);
    return Create(l);
  end Create;

  function Create ( pts : Integer_Vectors_of_Vectors.Vector ) return Vector is

    a : matrix(pts'first..pts'last - 1,pts'range);
    res : Vector(pts'range);

  begin
    for k in a'range(1) loop
      for i in a'range(2) loop
        a(k,i) := pts(k+1)(i) - pts(pts'first)(i);
      end loop;
    end loop;
    Upper_Triangulate(a);
    Scale(a);
    res := (res'range => 0);
    Solve0(a,res);
    Normalize(res);
    if res(res'last) < 0
     then return -res;
     else return res;
    end if;
  end Create;

-- CREATORS :

  function Create ( x : Integer_Vectors_of_Vectors.Vector ) return Simplex is

    n : natural := x'last - x'first + 1;
    res : Simplex := new Simplex_Rep(n);
    cnt : natural := x'first;

  begin
    for k in res.pts'range loop
      res.pts(k).pt := x(cnt);
      cnt := cnt + 1;
      res.pts(k).si := Null_Simplex;
    end loop;
    res.tra := Create(x);
    res.nor := Create(x);
    return res;
  end Create;

  procedure Update ( s : in out Simplex; x : in Link_to_Vector;
                     k : in natural ) is

    pts : Integer_Vectors_of_Vectors.Vector(1..s.n);
    nei : Simplex;

  begin
    if s.pts(k).si = Null_Simplex
     then for i in pts'range loop
            if i = k
             then pts(i) := x;
             else pts(i) := s.pts(i).pt;
            end if;
          end loop;
          nei := Create(pts);
          s.pts(k).si := nei;
          nei.pts(k).si := s;
    end if;
  end Update;

  procedure Update ( s : in out Simplex; x : in Link_to_Vector; 
                     pos : in Vector ) is
  begin
    for k in pos'first..pos'last-1 loop
      if pos(k)*pos(pos'last) > 0 
       then Update(s,x,k+1);
      end if;
    end loop;
  end Update;

  procedure Update_One ( s : in out Simplex; x : in Link_to_Vector;
                         pos : in Vector ) is

    done : boolean := false;
    nei : Simplex;

  begin
    for k in pos'first..pos'last-1 loop
      if pos(k)*pos(pos'last) > 0
       then nei := s.pts(k+1).si;
            if nei /= Null_Simplex
             then Update_One(nei,x,Position(nei,x.all));
             else Update(s,x,k+1);
            end if;
            done := true;
      end if;
      exit when done;
    end loop;
  end Update_One;

  procedure Update_One ( s : in out Simplex; x : in Link_to_Vector;
                         pos : in Vector; news : out Simplex ) is

    done : boolean := false;
    nei,newsnei : Simplex;

  begin
   -- LOOK FIRST FOR NULL SIMPLEX IN THE DIRECTION TO x :
    for k in pos'first..pos'last-1 loop
      if pos(k)*pos(pos'last) > 0
       then nei := s.pts(k+1).si;
            if nei = Null_Simplex
             then Update(s,x,k+1);
                  news := s.pts(k+1).si;
                  done := true;
            end if;
      end if;
      exit when done;
    end loop;
   -- WALK FURTHER IN THE DIRECTION TO x :
    if not done
     then Update_One(nei,x,Position(nei,x.all),newsnei);
          if newsnei /= Null_Simplex
           then news := newsnei;
                s := nei;
          end if;
    end if;
  end Update_One;

  procedure Update_All ( s : in out Simplex; x : in Link_to_Vector;
                         pos : in Vector; ancestor : in Simplex ) is

    nei : Simplex;
    continue : boolean := true;

  begin
    for k in pos'first..pos'last-1 loop
      if pos(k)*pos(pos'last) > 0
       then nei := s.pts(k+1).si;
            if nei /= Null_Simplex 
             then if not Is_Vertex(nei,x.all) and nei /= ancestor
                   then Update_All(nei,x,Position(nei,x.all),s);
                  end if;
             else Update(s,x,k+1);
                  Process(s.pts(k+1).si,continue);
            end if;
      end if;
      exit when not continue;
    end loop;
  end Update_All;

  procedure Connect ( s1,s2 : in out Simplex ) is

    neighb : boolean;
    index1,index2 : natural;

  begin
    neighb := true; -- assume they are neighbors
    index1 := 0; 
   -- SEARCH FOR INDEX OF POINT IN s1 THAT DOES NOT BELONG TO s2 :
    for k in s1.pts'range loop
      if not Is_Vertex(s2,s1.pts(k).pt.all)
       then if (index1 = 0) and (s1.pts(k).si = Null_Simplex)
             then index1 := k;      -- kth point is not common
             else neighb := false;  -- more than one point not common
                                    -- or there is already a neighbor
            end if;
      end if;
      exit when not neighb;
    end loop;  -- either not neighb or index1 -> point in s1, not in s2
   -- SEARCH FOR INDEX OF POINT IN s2 THAT DOES NOT BELONG TO s1 :
    if neighb
     then index2 := 0;
          for k in s2.pts'range loop
            if not Is_Vertex(s1,s2.pts(k).pt.all)
             then if (index2 = 0) and (s2.pts(k).si = Null_Simplex)
                   then index2 := k;      -- kth point is not common
                   else neighb := false;  -- more than one point not common
                                          -- or there is already a neighbor
                  end if;
            end if;
            exit when not neighb;
          end loop;  -- either no neighb or index2 -> point in s2, not in s1
   -- CONNECT THE SIMPLICES WITH EACH OTHER :
          if neighb
           then s1.pts(index1).si := s2;
                s2.pts(index2).si := s1;
          end if;
    end if;
  end Connect;

  procedure Flatten ( s : in out Simplex ) is
  begin
    s.nor := (s.nor'range => 0);
    s.nor(s.n) := 1;
    for k in s.pts'range loop
      s.pts(k).pt(s.n) := 0;
    end loop;
  end Flatten;

-- SELECTORS :

  function Dimension ( s : Simplex ) return natural is
  begin
    return s.n;
  end Dimension;

  function Normal ( s : Simplex ) return Vector is
  begin
    return s.nor;
  end Normal;

  function Is_Flat ( s : Simplex ) return boolean is
  begin
    for i in s.nor'first..(s.nor'last-1) loop
      if s.nor(i) /= 0
       then return false;
      end if;
    end loop;
    return (s.nor(s.nor'last) = 1);
  end Is_Flat;

  function Vertices ( s : Simplex ) return Integer_Vectors_of_Vectors.Vector is

    res : Integer_Vectors_of_Vectors.Vector(s.pts'range);

  begin
    for k in res'range loop
      res(k) := s.pts(k).pt;
    end loop;
    return res;
  end Vertices;

  function Vertex ( s : Simplex; k : natural ) return Vector is
  begin
    return s.pts(k).pt.all;
  end Vertex;

  function Is_Vertex ( s : Simplex; x : Vector ) return boolean is
  begin
    for k in s.pts'range loop
      if s.pts(k).pt.all = x
       then return true;
      end if;
    end loop;
    return false;
  end Is_Vertex;

  function Equal ( s1,s2 : Simplex ) return boolean is

    found : boolean;

  begin
    if s1.nor /= s2.nor                -- CHECK IF NORMALS ARE THE SAME
     then return false;
     else for k in s1.pts'range loop   -- CHECK IF VERTICES ARE THE SAME
            -- CHECK WHETHER s1.pts(k).pt.all OCCURS IN s2.pts
            found := false;
            for l in s2.pts'range loop
              if s1.pts(k).pt.all = s2.pts(l).pt.all
               then found := true;
              end if;
              exit when found;
            end loop;
            if not found
             then return false;
            end if;
          end loop;
          return true;
    end if;
  end Equal;

  function Index ( s : Simplex; x : Vector ) return natural is
  begin
    for k in s.pts'range loop
      if s.pts(k).pt.all = x
       then return k;
      end if;
    end loop;
    return 0;
  end Index;

  function Neighbor ( s : Simplex; k : natural ) return Simplex is
  begin
    return s.pts(k).si;
  end Neighbor;

  function Neighbor ( s : Simplex; k : natural; pos : Vector )
                    return Simplex is
  begin
    if pos(k-1)*pos(pos'last) > 0
     then return s.pts(k).si;
     else return Null_Simplex;
    end if;
  end Neighbor;

  function Position ( s : Simplex; x : Vector ) return Vector is

    m : matrix(x'first..x'last-1,x'range);
    pos : Vector(x'range);
    res : Vector(0..pos'last);
 
  begin 
   -- nbpos := nbpos + 1;
   -- put("# position computations : "); put(nbpos,1); new_line;
   -- transform point and simplex
    m := Diagonalize(s,x);
   -- solve the system
    pos := (pos'range => 0);
    Solve0(m,pos);
    res(pos'first..pos'last) := pos;
    res(0) := 0;
    for k in pos'range loop
      res(0) := res(0) + pos(k);
    end loop;
    res(0) := -res(0);
    return res;
  end Position;

  function Is_In ( s : Simplex; x : Vector ) return boolean is

    pos : Vector(0..x'last) := Position(s,x);

  begin
    return Is_In(s,x,pos);
  end Is_In;
    
  function Is_In ( s : Simplex; x,pos : Vector ) return boolean is
  begin
    for k in pos'first..pos'last-1 loop
      if pos(k)*pos(pos'last) > 0
       then return false;
      end if;
    end loop;
    return true;
  end Is_In;

  function Is_In_All ( s : Simplex; x : Vector ) return boolean is

    pos : Vector(0..x'last) := Position(s,x);

  begin
    return Is_In_All(s,x,pos);
  end Is_In_All;

  function Is_In_All ( s : Simplex; x : Vector ) return Simplex is

    pos : Vector(0..x'last) := Position(s,x);

  begin
    return Is_In_All(s,x,pos);
  end Is_In_All;

  function Is_In_All ( s : Simplex; x,pos : Vector ) return boolean is

    ins : boolean := true;    -- assumes that x belongs to s

  begin
    for k in pos'first..pos'last-1 loop
      if pos(k)*pos(pos'last) > 0
       then if s.pts(k+1).si /= Null_Simplex
             then return Is_In_All(s.pts(k+1).si,x);
             else ins := false;
            end if;
      end if;
    end loop;
    return ins;
  end Is_In_All;

  function Is_In_All ( s : Simplex; x,pos : Vector ) return Simplex is

    ins : boolean := true;    -- assumes that x belongs to s

  begin
    for k in pos'first..pos'last-1 loop
      if pos(k)*pos(pos'last) > 0
       then if s.pts(k+1).si /= Null_Simplex
             then return Is_In_All(s.pts(k+1).si,x);
             else ins := false;
            end if;
      end if;
    end loop;
    if ins
     then return s;
     else return Null_Simplex;
    end if;
  end Is_In_All;

  procedure Neighbors ( s : in out Simplex; x : in Vector ) is

    cont : boolean;

    procedure Neighbors ( s : in out Simplex; x : in Vector;
                          cont : out boolean ) is

      pos : Vector(0..x'last) := Position(s,x);
      continue : boolean := true;

    begin
      for k in pos'first..pos'last-1 loop
        if pos(k)*pos(pos'last) > 0
         then if s.pts(k+1).si /= Null_Simplex
               then Neighbors(s.pts(k+1).si,x,continue);
               else Process_Neighbor(s,k+1,continue);
              end if;
        end if;
        exit when not continue;
      end loop;
      cont := continue;
    end Neighbors;
  
  begin
    Neighbors(s,x,cont);
  end Neighbors;

  function Volume ( s : Simplex ) return natural is

    m : constant matrix := Diagonalize(s);
    vol : integer := 1;

  begin
    for k in m'range(1) loop
      vol := vol*m(k,k);
    end loop;
    if vol >= 0
     then return vol;
     else return -vol;
    end if;
  end Volume;

-- DESTRUCTORS :

  procedure Destroy_Neighbor ( s : in out Simplex; k : in natural ) is
  begin
    s.pts(k).si := Null_Simplex;
  end Destroy_Neighbor;

  procedure Destroy_Neighbors ( s : in out Simplex ) is
  begin
    for k in s.pts'range loop
      Destroy_Neighbor(s,k);
    end loop;
  end Destroy_Neighbors;

  procedure Clear_Neighbor ( s : in out Simplex; k : in natural ) is
  begin
    Clear(s.pts(k).si);
  end Clear_Neighbor;

  procedure Clear_Neighbors ( s : in out Simplex ) is
  begin
    for k in s.pts'range loop
      Clear_Neighbor(s,k);
    end loop;
  end Clear_Neighbors;

  procedure Clear ( s : in out Simplex ) is

    procedure free is new unchecked_deallocation(Simplex_Rep,Simplex);

  begin
    Clear(s.tra);
    free(s);
  end Clear;

end Simplices;
