///////////////////////////////////////////////////////////////////////////////
//                             Lisaac Library                                //
//                                                                           //
//                   LSIIT - ULP - CNRS - INRIA - FRANCE                     //
//                                                                           //
//   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 3 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, see <http://www.gnu.org/licenses/>.   //
//                                                                           //
//                     http://isaacproject.u-strasbg.fr/                     //
///////////////////////////////////////////////////////////////////////////////
Section Header
  
  + name    := LINKED2_LIST[V];


  - copyright   := "2003-2005 Jérome Boutet, 2003-2007 Benoit Sonntag";
  
  - comment := "Two way linked list with internal automatic memorization \
               \of the last access .";
	       
Section Inherit
  
  + parent_linked_collection:Expanded LINKED_COLLECTION[V];
  
Section LINKED2_LIST
  
  + first_link:LINK2[V];
  // NULL when empty or gives access to the first element.
    
  + last_link:LINK2[V];
  // NULL when empty or gives access to the last element.
  
  + mem_idx:INTEGER; 
  
  + mem_lnk:LINK2[V];
  // To speed up accessing, `mem_idx' and `mem_lnk' is the
  // memory of the last access done. For example, after
  // item(1), `mem_idx' is 1 and `mem_lnk' is `first_link'.
  // When list is empty, `first_link' is NULL as well as
  // `mem_lnk' and `mem_idx' is 0;
  
Section Public
  
  - create:SELF <-
  // Make an empty list;
  (     
    SELF.clone
  );
  
  - is_empty:BOOLEAN <- first_link = NULL;
  
  - add_first element:V <-
  ( 
    (first_link = NULL).if {
      first_link := LINK2[V].create element previous NULL next NULL;
      last_link := first_link;
      upper := 1;
      mem_idx := 1;
      mem_lnk := first_link;
    } else {
      first_link := LINK2[V].create element previous NULL next first_link;
      first_link.next.set_previous first_link;
      upper := upper + 1;
      mem_idx := mem_idx + 1;
    };
  )
  [ ...
    +? { upper = 1 + Old upper };
  ];
    
  - add_last element:V <-
  ( 
    (first_link = NULL).if {
      first_link := LINK2[V].create element previous NULL next NULL;
      last_link := first_link;
      upper := 1;
      mem_idx := 1;
      mem_lnk := first_link;
    } else {
      last_link := LINK2[V].create element previous last_link next NULL;
      last_link.previous.set_next last_link;
      upper := upper + 1;
    };
  );
    
  - add element:V to index:INTEGER <-
  ( + link:LINK2[V];
    
    (index = 1).if {
      add_first element;
    }.elseif {index = upper + 1} then {
      add_last element;
    } else {
      (index - 1 != mem_idx).if {
	go_item (index - 1);
      };
      link := LINK2[V].create element previous mem_lnk next (mem_lnk.next);
      link.next.set_previous link;
      mem_lnk.set_next link;
      upper := upper + 1;
    };    
  );
    
  - remove_first <-
  ( 
    (upper = 1).if {
      first_link := NULL;			
      last_link  := NULL;
      mem_lnk    := NULL;
      mem_idx := 0;
      upper   := 0;
    } else {      
      first_link := first_link.next;
      first_link.set_previous NULL;
      upper := upper - 1;
      (mem_idx > 1).if {
	mem_idx := mem_idx - 1;
      } else {
	mem_lnk := first_link;
	mem_idx := 1;
      };      
    };
  );
    
  - remove index:INTEGER <-
  ( + link:LINK2[V];
    
    (index = 1).if {
      remove_first;
    }.elseif { index = upper} then {
      remove_last;
    } else {
      ((index - 1) != mem_idx).if {
	go_item (index - 1);
      };
      link := mem_lnk.next;
      mem_lnk.set_next (link.next);
      link.next.set_previous mem_lnk;
      upper := upper - 1;
    };
  );
    
  - first:V <- first_link.item;
  
  - last:V <- last_link.item;
  
  - item index:INTEGER :V <-
  (
    (index != mem_idx).if {
      go_item index;
    };
    mem_lnk.item
  );
    
  - put element:V to index:INTEGER <-
  ( 
    (index != mem_idx).if {
      go_item index;
    };
    mem_lnk.set_item element;    
  );
    
  - count:INTEGER <- upper;
  
  - set_all_with v:V <-
  ( 
    (first_link != NULL).if {
      first_link.set_all_with v;
    };    
  );
    
  - copy other:SELF <-
  (
    from_collection other;
  );
    
  - '==' Right 60 other:SELF :BOOLEAN <-
  ( + result:BOOLEAN;
    + lnk1, lnk2:LINK2[V];
    
    (Self = other).if {
      result := TRUE;
    }.elseif {upper = other.upper} then {
      result := TRUE;
      lnk1 := first_link;
      lnk2 := other.first_link;
      {(lnk1 = NULL) || {! result}}.until_do {
	result := lnk1.item = lnk2.item;
	lnk1 := lnk1.next;
	lnk2 := lnk2.next;
      };
    };    
    result
  );
    
  - is_equal_map other:SELF :BOOLEAN <-
  ( + result:BOOLEAN;
    + lnk1, lnk2:LINK2[V];
    + safe_equal:SAFE_EQUAL[V];
    
    ( Self = other ).if {
      result := TRUE;
    }.elseif {upper = other.upper} then {
      result := TRUE;
      lnk1 := first_link;
      lnk2 := other.first_link;
      {(lnk1 = NULL) || {! result}}.until_do {
	result := safe_equal.test (lnk1.item) with (lnk2.item);
	lnk1 := lnk1.next;
	lnk2 := lnk2.next;
      };
    };    
    result
  );
    
  - index_of x:V start start_index:INTEGER :INTEGER <-
  ( + result:INTEGER;
    + safe_equal:SAFE_EQUAL[V];
    
    result := start_index;
    {(result > upper) || {safe_equal.test x with (item result)}}.until_do {
      result := result + 1;
    };    
    result
  );
  
  - reverse_index_of element:V start start_index:INTEGER :INTEGER <-
  ( + safe_equal:SAFE_EQUAL[V]; 
    + temporary_idx:INTEGER;
    + temporary_lnk:LINK2[V];
    + result:INTEGER;
    
    (start_index != mem_idx).if {
      go_item start_index;
    };
    temporary_idx := mem_idx;
    temporary_lnk := mem_lnk;
    {(temporary_idx < lower) || {safe_equal.test element with (temporary_lnk.item)}}.until_do {
      temporary_idx := temporary_idx - 1;
      temporary_lnk := temporary_lnk.previous;
    };
    result := temporary_idx;
    (temporary_idx >= lower).if {
      mem_idx := temporary_idx;
      mem_lnk := temporary_lnk;
    };
    result
  );

  - fast_index_of x:V start start_index:INTEGER :INTEGER <-
  ( + result:INTEGER;
    + u:INTEGER;
    
    result := lower;
    u := upper;
    {(result > u) || {x = item result}}.until_do {
      result := result + 1;
    };
    result
  );	

  - fast_reverse_index_of element:V start start_index:INTEGER :INTEGER <-
  ( + temporary_idx:INTEGER;
    + temporary_lnk:LINK2[V];
    + result:INTEGER;
		
    (start_index != mem_idx).if {
      go_item start_index;
    };
    temporary_idx := mem_idx;
    temporary_lnk := mem_lnk;			
    {(temporary_idx < lower) || {element = temporary_lnk.item}}.until_do {
      temporary_idx := temporary_idx - 1;
      temporary_lnk := temporary_lnk.previous;
    };
    result := temporary_idx;
    (temporary_idx >= lower).if {
      mem_idx := temporary_idx;
      mem_lnk := temporary_lnk;
    };
  );
    
  - clear <-
  (
    (first_link != NULL).if {
      first_link := NULL;
      mem_idx := 0;
      mem_lnk := NULL;
      upper := 0;
      last_link := NULL;
    };
  )
  [ ...
    +? {upper = 0};
  ];
    
  - from_collection model:COLLECTION[V] <-
  ( + lnk:LINK2[V];
    
    (first_link = NULL).if {
      (model.lower).to (model.upper) do { i:INTEGER;
	add_last (model.item i);
      };
    } else {
      lnk := first_link;
      (model.lower).to (model.upper) do { i:INTEGER;
	(lnk = NULL).if {
	  add_last (model.item i);
	} else {
	  lnk.set_item (model.item i);
	  lnk := lnk.next;
	};
      };
      (lnk = first_link).if {
	? {model.count = 0};
	clear;
      }.elseif {lnk != NULL} then {
	+ i:INTEGER;
	i := model.count;
	(mem_idx != i).if {
	  go_item i;
	};
	? {lnk = mem_lnk.next};
	mem_lnk.set_next NULL;
	upper := i;
	last_link := mem_lnk;
      };
    };   
  );
    
  - slice low:INTEGER to up:INTEGER :SELF <-
  ( + lnk:LINK2[V];
    + result:SELF;

    result := SELF.create;    
    (mem_idx != low).if {
      go_item low;
    };
    lnk := mem_lnk;
    (up - low + 1).downto 1 do { i:INTEGER;
      result.add_last (lnk.item);
      lnk := lnk.next;
    };
    result
  );
    
  - occurrences element:V :INTEGER <-
  ( + lnk:LINK2[V];
    + safe_equal:SAFE_EQUAL[V];
    + result:INTEGER;
    
    lnk := first_link;
    {lnk = NULL}.until_do {
      (safe_equal.test element with (lnk.item)).if {
	result := result + 1;
      };
      lnk := lnk.next;
    };    
    result
  );
    
  - fast_occurrences element:V :INTEGER <-
  ( + lnk:LINK2[V];
    + result:INTEGER;
    
    lnk := first_link;
    {lnk = NULL}.until_do {
      (element = lnk.item).if {
	result := result + 1;
      };
      lnk := lnk.next;
    };
    result
  );
    
  - force element:V to index:INTEGER <-
  ( + v:V;
    
    {index <= upper}.until_do {
      add_last v;
    };
    put element to index;
  );
    
  - all_default:BOOLEAN <-
  ( + l:LINK2[V];
    + d:V;
    + result:BOOLEAN;
    
    result := TRUE;
    l := first_link;
    {(! result) || {l = NULL}}.until_do {
      d := l.item;
      (d != NULL).if {
	result := d.is_default;
      };
      l := l.next;
    };    
    result
  );
    
  - remove_last <-
  ( 
    (upper = 1).if {
      first_link := NULL;
      last_link  := NULL;
      mem_lnk := NULL;
      mem_idx := 0;
      upper := 0;
    } else {
      link := last_link;
      last_link := link.previous;
      last_link.set_next NULL;
      (mem_idx = upper).if {
	mem_idx := 1;
	mem_lnk := first_link;
      };
      upper := upper - 1;
    };    
  );
    
  - replace_all old_value:V with new_value:V <-
  ( + safe_equal:SAFE_EQUAL[V];
    
    lower.to upper do { i:INTEGER;
      (safe_equal.test (item i) with old_value).if {
	put new_value to i;
      };
    };   
  );
    
  - fast_replace_all old_value:V with new_value:V <-
  ( 
    lower.to upper do { i:INTEGER;
      (item i = old_value).if {
	put new_value to i;
      };
    };    
  );
  
  - reverse <-
  ( + temp:V; 
    + low:LINK2[V]; 
    + high:LINK2[V];
    + i:INTEGER;
    
    low  := first_link;
    high := last_link;
    
    i := count / 2;
    ? {(i > 0) -> ((low != NULL) & (high != NULL))};
    ? {(i > 0) -> ((low != high) & (low.previous != high))};
    {i = 0}.until_do {
      temp := low.item;
      low.set_item (high.item);
      high.set_item temp;
      low  := low.next;
      high := high.previous;
      i    := i - 1;
    };	
  );
			
Section Private
  
  - go_item index:INTEGER <-
  [ ...
    -? { valid_index index };
    -? { mem_idx != index };
    -? { mem_idx > 0 };
    -? { mem_lnk != NULL };
  ]
  (
    (index > mem_idx).if {
      ((upper - index) < (index - mem_idx)).if {
	mem_idx := upper;
	mem_lnk := last_link;
	{index = mem_idx}.until_do {
	  mem_lnk := mem_lnk.previous;
	  mem_idx := mem_idx - 1;
	};
      } else {
	{index = mem_idx}.until_do {
	  mem_lnk := mem_lnk.next;
	  mem_idx := mem_idx + 1;
	};
      };
    }.elseif {(mem_idx - index) < (index - 1)} then {
      {index = mem_idx}.until_do {
	mem_lnk := mem_lnk.previous;
	mem_idx := mem_idx - 1;
      };
    } else {
      mem_idx := 1;
      mem_lnk := first_link;
      {index = mem_idx}.until_do {
	mem_lnk := mem_lnk.next;
	mem_idx := mem_idx + 1;
      };
    };
  )
  [ ...
    +? { mem_idx = index };
    +? { mem_lnk != NULL };
  ];
  
  //
  // Invariant.
  //
  
//  [ ...
//    "Empty status." -? {(first_link = NULL) ->
//    ((last_link = NULL) & (upper = 0) & (mem_idx = 0) & (mem_lnk = NULL))};
    
//    "Not empty status." -? {(first_link != NULL) ->
//    ((last_link != NULL) & (upper > 0) & (mem_idx > 0) & (mem_lnk != NULL))};
//  ];
