module OptionsReader

  use BranchAndBound_Options

  implicit none

  integer, parameter, private :: MAX_NUMBER_OPTIONS = 20

  ! Name of the options file.
  character(len=15), parameter, private :: OPTIONS_FILE_NAME = 'algencan-bb.opt'

  ! Characters used to comment lines in the options file.
  character(len=15), parameter, private :: COMMENT_CHARACTERS = '#*'

  ! Valid options.
  character(len=30), parameter, private ::      &
       NODE_SELECTION_TOKEN = 'NODE_SELECTION', &
       BRANCHING_RULE_TOKEN = 'BRANCHING_RULE', &
       MULTI_START_TOKEN    = 'MULTI_START',    &
       NUM_ROOT_RESOLUTIONS_TOKEN = 'NUM_ROOT_RESOLUTIONS', &
       NUM_NODE_RESOLUTIONS_TOKEN = 'NUM_NODE_RESOLUTIONS', &
       IT_SWITCH_BEST_BOUND_TOKEN = 'IT_SWITCH_BEST_BOUND', &
       INCREASE_NODE_RESOLUTIONS_TOKEN = 'INCREASE_NODE_RESOLUTIONS', &
       UPDATES_BEFORE_TRUST_TOKEN = 'UPDATES_BEFORE_TRUST', &
!       VAR_SELECTION_PARAMETERS_TOKEN = 'VAR_SELECTION_PARAMETERS', &
       CUTOFF_TOKEN = 'CUTOFF', &
       BOUND_TOLERANCE_TOKEN = 'BOUND_TOLERANCE', &
       BOX_CONSTRAINED_SOLVER_TOKEN = 'BOX_CONSTRAINED_SOLVER', &
       SOLVE_ROUNDED_SOLUTION_TOKEN = 'SOLVE_ROUNDED_SOLUTION'

  ! Possible values.

  ! NODE SELECTION
  character(len=20), parameter, private :: NODE_SELECTION_STRINGS(4) = &
       (/ 'BEST_BOUND_RULE','BREADTH_FIRST','DEPTH_FIRST','SWITCH_DF_BBR' /)

  integer, parameter, private :: NODE_SELECTION_VALUES(4) = &
       (/ BEST_BOUND_RULE,BREADTH_FIRST,DEPTH_FIRST,SWITCH_DF_BBR /)

  ! BRANCHING VARIABLE SELECTION
  character(len=20), parameter, private :: BRANCHING_RULE_STRINGS(5) = &
       (/ 'LOWEST_INDEX_FIRST','MOST_FRACTIONAL','RANDOM_VARIABLE', &
          'PSEUDO_COSTS', 'STRONG_BRANCHING' /)

  integer, parameter, private :: BRANCHING_RULE_VALUES(4) = &
       (/ LOWEST_INDEX_FIRST,MOST_FRACTIONAL,RANDOM_VARIABLE,PSEUDO_COSTS /)

  ! MULTI-START STRATEGIES
  character(len=20), parameter, private :: MULTI_START_STRINGS(2) = &
       (/ 'BEST_FEASIBLE','FIRST_FEASIBLE' /)

  integer, parameter, private :: MULTI_START_VALUES(2) = &
       (/ BEST_FEASIBLE,FIRST_FEASIBLE /)

  ! BOX-CONSTRAINED SOLVERS
  character(len=20), parameter, private :: BOX_CONSTRAINED_SOLVERS_STRINGS(2) =&
       (/ 'GENCAN','ASA' /)

contains

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

  subroutine readOptions(options)

    implicit none

    type(bb_options), intent(out) :: options

    character(len=80) :: line
    character(len=80), dimension(MAX_NUMBER_OPTIONS) :: option,value
    integer :: opRead

    opRead = 0

    ! Initialize the options with default options.
    call initializeOptions(options)

    open(20,err=100,file=OPTIONS_FILE_NAME,status='OLD')

    do while(.true.)

       ! Read a line.
       read(20,fmt=1000,err=400,end=200) line
       !read(20,err=400,end=200) line

       ! Remove spaces.
       line = trim(adjustl(line))

       if(.not. isComment(line(1:1))) then

          call parseLine(line,option(opRead + 1),value(opRead + 1))

          if(applyOption(options,option(opRead + 1),value(opRead + 1))) then
             
             opRead = opRead + 1

          else

             stop

          end if

       end if

    end do

! NO SPECIFICATION FILE
100 continue
    write(*, 9000) OPTIONS_FILE_NAME

! CLOSING SPECIFICATION FILE
200 continue
    close(20)
    go to 500

! ERROR READING THE SPECIFICATION FILE
400 continue
    write(*, 9010) OPTIONS_FILE_NAME
    go to 500

500 continue
!   call printOptions(option,value,opRead)

1000 format(A80)

9000 format(/,' The options file ', A15, ' was not found.',/,&
              ' The default options for the Branch-And-Bound algorithm',&
              ' will be used.')
9010 format(/,' Error reading specification file ', A15, '.')

  end subroutine readOptions

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

  subroutine printOptions(option,value,opRead)

    implicit none

    ! SCALAR ARGUMENTS
    integer :: opRead

    ! ARRAY ARGUMENTS
    character(len=*), dimension(MAX_NUMBER_OPTIONS) :: option,value

    ! LOCAL SCALARS
    integer :: i

    if(opRead .gt. 0) then

       write(*,100)
       do i = 1, opRead

          write(*,200) option(i),value(i)

       end do

    else

       write(*,300) OPTIONS_FILE_NAME

    end if

    write(*,*)

100 format(/, ' Branch-And-Bound Options:')
200 format(' ', A40, ' ', A40)
300 format(/, ' No option specified in the file ', A15, '.')

  end subroutine printOptions

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

  logical function applyOption(options,option,value)

    implicit none

    type(bb_options), intent(in out) :: options
    character(len=*), intent(in) :: option,value

    ! LOCAL SCALARS
    integer :: i,index,int_value
    real(kind=8) :: real_value

    applyOption = .true.

    select case(option)

    case(NODE_SELECTION_TOKEN)

       if(isNodeSelectionOptionValid(value,index)) then

          call setNodeSelectionStrategy(options,NODE_SELECTION_VALUES(index))
          return

       else

          write(*,200) value,NODE_SELECTION_TOKEN
          do i = 1,size(NODE_SELECTION_STRINGS)
             write(*,300) trim(adjustl(NODE_SELECTION_STRINGS(i)))
          end do

          applyOption = .false.
          return

       end if

    case(BRANCHING_RULE_TOKEN)

       if(isBranchingRuleOptionValid(value,index)) then

          call setBranchingVariableStrategy(options,BRANCHING_RULE_VALUES(index))
          return

       else

          write(*,200) value,BRANCHING_RULE_TOKEN
          do i = 1,size(BRANCHING_RULE_STRINGS)
             write(*,300) trim(adjustl(BRANCHING_RULE_STRINGS(i)))
          end do

          applyOption = .false.
          return

       end if

    case(MULTI_START_TOKEN)

       if(isMultiStartOptionValid(value,index)) then

          call setMultiStartStrategy(options,MULTI_START_VALUES(index))
          return

       else

          write(*,200) value,MULTI_START_TOKEN
          do i = 1,size(MULTI_START_STRINGS)
             write(*,300) trim(adjustl(MULTI_START_STRINGS(i)))
          end do

          applyOption = .true.
          return

       end if

    case(NUM_ROOT_RESOLUTIONS_TOKEN)

       read(value,*) int_value
       call setNumRootResolutions(options,int_value)
       return

    case(NUM_NODE_RESOLUTIONS_TOKEN)

       read(value,*) int_value
       call setNumNodeResolutions(options,int_value)
       return

    case(INCREASE_NODE_RESOLUTIONS_TOKEN)

       read(value,*) int_value
       call setNumNodeResolutionsIncrease(options,int_value)
       return

    case(IT_SWITCH_BEST_BOUND_TOKEN)

       read(value,*) int_value
       call setNumIterationsForBestBoundRule(options,int_value)
       return

    case(UPDATES_BEFORE_TRUST_TOKEN)

       read(value,*) int_value
       call setNumberOfUpdatesBeforeTrust(options,int_value)
       return

    case(CUTOFF_TOKEN)

       read(value,*) real_value
       call setCutOff(options,real_value)
       return

    case(BOUND_TOLERANCE_TOKEN)

       read(value,*) real_value
       call setBoundTolerance(options,real_value)
       return

    case(SOLVE_ROUNDED_SOLUTION_TOKEN)

       call setSolveRoundedSolution(options,.true.)
       return

    case(BOX_CONSTRAINED_SOLVER_TOKEN)

       if(isBoxConstrainedSolverOptionValid(value,index)) then

          call setBoxConstrainedSolver(options,MULTI_START_VALUES(index))
          return

       else

          write(*,200) value,BOX_CONSTRAINED_SOLVER_TOKEN
          do i = 1,size(BOX_CONSTRAINED_SOLVERS_STRINGS)
             write(*,300) trim(adjustl(BOX_CONSTRAINED_SOLVERS_STRINGS(i)))
          end do

          applyOption = .false.
          return

       end if

    case default
       
       write(*,100) option

    end select

    applyOption = .false.

100 format(' Invalid option: ', A40)
200 format(' Invalid value: ', A40, /, &
           ' Possible values for ',A40,' are:')
300 format('  ', A20)

  end function applyOption

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

  subroutine parseLine(line,firstWord,secondWord)

    implicit none

    ! SCALAR ARGUMENTS
    character(len=*), intent(in)  :: line
    character(len=*), intent(out) :: firstWord,secondWord

    integer :: i

    do i = 1, len(firstWord)

       firstWord(i:i)  = ''
       secondWord(i:i) = ''

    end do

    do i = 1, len(line)

       if(line(i:i) .eq. ' ') then

          exit

       end if

       firstWord(i:i) = line(i:i)

    end do

    secondWord(1:len(line) - i) = line(i+1:len(line))

  end subroutine parseLine

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

  logical function isNodeSelectionOptionValid(value,index)

    implicit none

    ! SCALAR ARGUMENTS
    character(len=*), intent(in) :: value
    integer, intent(out) :: index

    ! LOCAL SCALARS
    integer :: i

    do i = 1, size(NODE_SELECTION_STRINGS)

       if(value .eq. trim(NODE_SELECTION_STRINGS(i))) then

          isNodeSelectionOptionValid = .true.
          index = i
          return

       end if

    end do

    index = -1
    isNodeSelectionOptionValid = .false.

  end function isNodeSelectionOptionValid

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

  logical function isBranchingRuleOptionValid(value,index)

    implicit none

    ! SCALAR ARGUMENTS
    character(len=*), intent(in) :: value
    integer, intent(out) :: index

    ! LOCAL SCALARS
    integer :: i

    do i = 1, size(BRANCHING_RULE_STRINGS)

       if(value .eq. trim(BRANCHING_RULE_STRINGS(i))) then

          isBranchingRuleOptionValid = .true.
          index = i
          return

       end if

    end do

    index = -1
    isBranchingRuleOptionValid = .false.

  end function isBranchingRuleOptionValid

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

  logical function isMultiStartOptionValid(value,index)

    implicit none

    ! SCALAR ARGUMENTS
    character(len=*), intent(in) :: value
    integer, intent(out) :: index

    ! LOCAL SCALARS
    integer :: i

    do i = 1, size(MULTI_START_STRINGS)

       if(value .eq. trim(MULTI_START_STRINGS(i))) then

          isMultiStartOptionValid = .true.
          index = i
          return

       end if

    end do

    index = -1
    isMultiStartOptionValid = .false.

  end function isMultiStartOptionValid

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

  logical function isBoxConstrainedSolverOptionValid(value,index)

    implicit none

    ! SCALAR ARGUMENTS
    character(len=*), intent(in) :: value
    integer, intent(out) :: index

    ! LOCAL SCALARS
    integer :: i

    do i = 1, size(BOX_CONSTRAINED_SOLVERS_STRINGS)

       if(value .eq. trim(BOX_CONSTRAINED_SOLVERS_STRINGS(i))) then

          isBoxConstrainedSolverOptionValid = .true.
          index = i
          return

       end if

    end do

    index = -1
    isBoxConstrainedSolverOptionValid = .false.

  end function isBoxConstrainedSolverOptionValid

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

  logical function isComment(c)

    implicit none

    character, intent(in) :: c

    if(index(COMMENT_CHARACTERS,c) .gt. 0) then

       isComment = .true.

    else

       isComment = .false.

    end if

  end function isComment

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

end module OptionsReader
