! This module implements a linked list with the following operations to
! manipulate it:
!
! - subroutine addFirst(list, element)
! Insert the given element at the beginning of the specified list.
!
! - subroutine addLast(list, element)
! Append the specified element to the end of the list.
!
! - subroutine add(list, element)
! Add (according to the type of the list) the specified element to the list.
!
! - function createList()
! Create and return an empty list.
!
! - function empty(list)
! Return true if the specified list contains no elements.
!
! - function removeFirst(list)
! Remove and return the first element from the specified list.
!
! - function removeLast(list)
! Remove and return the last element from the specified list.
!
! - function remove(list)
! Remove (according to the type of the list) and return the element 
! from the specified list.

module LinkedList

  use Container

! Different types of a list.
  integer, parameter :: QUEUE    = 0
  integer, parameter :: STACK    = 1
  integer, parameter :: PRIORITY = 2
  integer, parameter :: UNSPEC   = 3

! ************************************************************************
! ************************************************************************

  type list

     private

!    Datatype to represent a list.

!    Pointer to the first element of the list.
     type(node), pointer :: first

!    Pointer to the last element of the list.
     type(node), pointer :: last

!    The type of this list. If this variable is set, the subroutine add()
!    and the function remove() will execute a specific action, according
!    to the type of the list.
     integer :: type

!    Size of the list.
     integer :: size

  end type list

! ************************************************************************
! ************************************************************************

  type, private :: node

!    Datatype to represent a node in the list. It stores a data
!    and a pointer to the next element of the list.

!    The data stored in this node.
     type(data) :: data

!    Pointer to the next node.
     type(node), pointer :: next

!    Pointer to the previous node.
     type(node), pointer :: previous

  end type node

! ************************************************************************
! ************************************************************************

contains

  function createList(type) result(list_)

    implicit none

!   Create and return an empty list.

!   OPTIONAL ARGUMENT
    integer, optional, intent(in) :: type

!   RETURN VARIABLE
    type(list) :: list_

    if(present(type)) then

       list_%type = type

    else

       list_%type = UNSPEC

    end if

    list_%size = 0
    nullify(list_%first)
    nullify(list_%last)

  end function createList

! ************************************************************************
! ************************************************************************

  subroutine changeListType(list_,type)

    implicit none

!   Change the type of the list.

!   ARGUMENTS
    integer,    intent(in)     :: type
    type(list), intent(in out) :: list_

    list_%type = type

  end subroutine changeListType

! ************************************************************************
! ************************************************************************

  integer function getListType(list_)

    implicit none

!   Return the type of the list.

!   ARGUMENT
    type(list), intent(in) :: list_

    getListType = list_%type

  end function getListType

! ************************************************************************
! ************************************************************************

  subroutine addFirst(list_, element)

    implicit none

!   Insert the given element at the beginning of the specified list.
!
!   Parameters of the function:
!   ===========================
!
!   LIST_ list: List where the element will be inserted.
!   ----------
!
!   ELEMENT data: Element to be inserted at the beginning of the list.
!   ------------

!   ARGUMENTS
    type(list), intent(in out) :: list_
    type(data), intent(in)     :: element

!   LOCAL VARIABLES
    type(node), pointer :: current

!   Create a new node.
    allocate(current)
    current%data = element

    if(empty(list_)) then

       nullify(current%next)
       nullify(current%previous)

!      Update the first element of the list.
       list_%first => current

!      Update the pointer to the last element of the list.
       list_%last => current

    else

       nullify(current%previous)

!      Point to next node.
       current%next => list_%first

!      First node points to the new node.
       list_%first%previous => current

!      Update the first element of the list.
       list_%first => current

    endif

    list_%size = list_%size + 1

  end subroutine addFirst

! ************************************************************************
! ************************************************************************

  subroutine addLast(list_, element)

    implicit none

!   Append the specified element to the end of the list.
!
!   Parameters of the function:
!   ===========================
!
!   LIST_ list: List where the element will be appended.
!   ----------
!
!   ELEMENT data: Element to be appended to the list.
!   ------------

!   ARGUMENTS
    type(list), intent(in out) :: list_
    type(data), intent(in)     :: element

!   LOCAL VARIABLES
    type(node), pointer :: current

!   Create a new node.
    allocate(current)
    current%data = element

    if(empty(list_)) then

       nullify(current%next)
       nullify(current%previous)

!      Update the first element of the list.
       list_%first => current

!      Update the pointer to the last element of the list.
       list_%last => current

    else

       nullify(current%next)

!      Point to previous node.
       current%previous => list_%last

!      Last node points to the new node.
       list_%last%next => current

!      Update the pointer to the last element of the list.
       list_%last => current

    endif

    list_%size = list_%size + 1

  end subroutine addLast

! ************************************************************************
! ************************************************************************

  function removeFirst(list_) result(element)

    implicit none

!   Remove and returns the first element from the specified list.
!
!   Parameters of the function:
!   ===========================
!
!   LIST_ list: List where the first element will be removed.
!   ----------

!   ARGUMENTS
    type(list), intent(in out) :: list_

!   RETURN VARIABLE
    type(data) :: element

!   LOCAL VARIABLES
    type(node), pointer :: old

    if(.not. empty(list_)) then

       element = list_%first%data
       old => list_%first
       list_%first => list_%first%next

       if(.not. empty(list_)) then
          nullify(list_%first%previous)
       end if

       deallocate(old)

       list_%size = list_%size - 1

    end if

  end function removeFirst

! ************************************************************************
! ************************************************************************

  function removeLast(list_) result(element)

    implicit none

!   Remove and return the last element from the specified list.
!
!   Parameters of the function:
!   ===========================
!
!   LIST_ list: List where the last element will be removed.
!   ----------

!   ARGUMENTS
    type(list), intent(in out) :: list_

!   RETURN VARIABLE
    type(data) :: element

!   LOCAL VARIABLES
    type(node), pointer :: old

    if(.not. empty(list_)) then

       element = list_%last%data
       old => list_%last
       list_%last => list_%last%previous

       if(.not. empty(list_)) then
          nullify(list_%last%next)
       end if

       deallocate(old)

       list_%size = list_%size - 1

    end if
    
  end function removeLast

! ************************************************************************
! ************************************************************************

  function removeBestBound(list_) result(element)

    implicit none

!   Remove and return the first element from the specified list.
!
!   Parameters of the function:
!   ===========================
!
!   LIST_ list: List where the first element will be removed.
!   ----------

!   ARGUMENTS
    type(list), intent(in out) :: list_

!   RETURN VARIABLE
    type(data) :: element

!   LOCAL VARIABLES
    real(kind=8) :: lowerBound, bestLowerBound
    type(node), pointer :: current, bestNode

    if(.not. empty(list_)) then

       current  => list_%first
       bestNode => current
       bestLowerBound = current%data%problem%lowerBound

       do while(associated(current))

          lowerBound = current%data%problem%lowerBound

          if(lowerBound .lt. bestLowerBound) then

             bestLowerBound = lowerBound
             bestNode => current

          end if

          current => current%next

       end do

       element = bestNode%data

       if(associated(bestNode%previous) .and. associated(bestNode%next)) then

          ! *----------*        *-----------*        *------*
          ! | Previous |  <-->  | Best node |  <-->  | Next |
          ! *----------*        *-----------*        *------*

          bestNode%previous%next => bestNode%next          
          bestNode%next%previous => bestNode%previous

       else if(associated(bestNode%previous)) then

          ! *----------*        *-----------*
          ! | Previous |  <-->  | Best node |  <-->  NULL
          ! *----------*        *-----------*

          nullify(bestNode%previous%next)
          list_%last => bestNode%previous

       else if(associated(bestNode%next)) then

          !                     *-----------*        *------*
          !         NULL  <-->  | Best node |  <-->  | Next |
          !                     *-----------*        *------*

          nullify(bestNode%next%previous)
          list_%first => bestNode%next

       else

          !                     *-----------*
          !         NULL  <-->  | Best node |  <-->  NULL
          !                     *-----------*

          nullify(list_%first)
          nullify(list_%last)

       end if

       deallocate(bestNode)

       list_%size = list_%size - 1

    end if

  end function removeBestBound

! ************************************************************************
! ************************************************************************

  logical function empty(list_)

    implicit none

!   Return true if the specified list contains no elements.

!   ARGUMENTS
    type(list), intent(in) :: list_

    if(.not. associated (list_%first) .or. .not. associated (list_%last)) then
       empty = .true.
    else
       empty = .false.
    end if

  end function empty

! ************************************************************************
! ************************************************************************

  function remove(list_) result(element)

    implicit none

!   Remove and return an element from the specified list, according
!   to its type.
!
!   Parameters of the function:
!   ===========================
!
!   LIST_ list: List where the element will be removed.
!   ----------

!   ARGUMENTS
    type(list), intent(in out) :: list_

!   RETURN VARIABLE
    type(data) :: element

    if(list_%type == PRIORITY) then

       element = removeBestBound(list_)

    else 
   
       element = removeFirst(list_)

    end if

  end function remove

! ************************************************************************
! ************************************************************************

  subroutine add(list_, element)

    implicit none

!   Add the specified element to the list, according to its type.
!
!   Parameters of the function:
!   ===========================
!
!   LIST_ list: List where the element will be added.
!   ----------
!
!   ELEMENT data: Element to be added to the list.
!   ------------

!   ARGUMENTS
    type(list), intent(in out) :: list_
    type(data), intent(in)     :: element

    if(list_%type == QUEUE) then

       call addLast(list_, element)

    else
       ! Stack.
       call addFirst(list_, element)

    end if

  end subroutine add

! ************************************************************************
! ************************************************************************

  subroutine printList(list_)

    implicit none

!   Print the specified list.

    type(list), intent(in) :: list_
    type(node), pointer    :: current

    current => list_%first

    do while(associated(current))
       print *, current%data%problem%initialPoint(1)
       current => current%next
    end do

  end subroutine printList

! ************************************************************************
! ************************************************************************

  function getSize(list_) result(size)

    type(list), intent(in) :: list_

    integer :: size

    size = list_%size

  end function getSize

! ************************************************************************
! ************************************************************************

  subroutine destroyList(list_)

    use Problems
    use Container

    implicit none

    type(list), intent(in out) :: list_

    type(data) :: d

    do while(.not. empty(list_))
       d = removeLast(list_)
       call deallocateProblem(d%problem)
    end do

  end subroutine destroyList

! ************************************************************************
! ************************************************************************

end module LinkedList
