/*
 * Solver.java
 *
 * Created on 17. prosinec 2000, 14:47
 * Documented 18. 7. 2001
 */

package timetable.solver;

import timetable.data.*;
import timetable.util.*;
import timetable.solver.strategy.*;
import timetable.solver.strategy.price.*;
import timetable.solver.strategy.value.*;
import timetable.solver.strategy.variable.*;
/** Řešič problému interaktivního rozvrhování.
 *
 * @author Tomáš Müller
 * @version 1.0
 */
public class Solver extends Object {
    /** konfigurace */
    Config config;
    /** Problém - rozvrhovací data */
    Problem problem;
    /** tabu-list - pro výběr umístění */
    Tabu tabu = null;
    /** heuristika výběru nenaplánované aktivity*/
    VariableSelectionInterface variableSelection = null;
    /**  heuristika výběru umístění*/
    ValueSelectionInterface valueSelection = null;
    
    /** počet iterací */
    public int nrIters = 0;
    
    /** doba řešení problému*/
    public long solveTime = 0;
    /** doba generování možných umístění */
    public long generateTime = 0;
    /** doba výběru umístění */
    public long selectValueTime = 0;
    /** doba výběru aktivity */
    public long selectVariableTime = 0;
    /** maximální počet naplánovaných aktivit, kterého se podařilo dosáhnout */
    public long maxScheduledActivities = 0;

    /** Konstruktor.
     * @param config konfigurace
     * @param problem problém rozvrhu
     */
    public Solver(Config config, Problem problem) throws TimetableException {
        this.problem=problem;
        this.config=config;
        nrIters=0;
        solveTime=generateTime=selectValueTime=selectVariableTime=0;
        maxScheduledActivities=0;
        tabu = new Tabu(config);
    }
    
    /** Vrátí problém - data.
     * @return problém rozvrhu
     */
    public Problem getProblem() {return problem; }
    
    /** Reset řešiče i problému - odebrání všech naplánovaných aktivit z rozvrhu.
     */
    public void reset() throws TimetableException {
        nrIters=0;
        solveTime=generateTime=selectValueTime=selectVariableTime=0;
        maxScheduledActivities=0;
        problem.reset();
        tabu = new Tabu(config);
    }
    
    /** Nastavení heuristiky výběru nenaplánované aktivity
     * @param variableSelection heuristika výběru nenaplánované aktivity
     */
    public void setVariableSelection(VariableSelectionInterface variableSelection) {
        this.variableSelection=variableSelection;
    }
    
    /** Nastavení heuristiky výběru umístění 
     * @param variableSelection heuristika výběru umístění nenaplánované aktivity
     */
    public void setValueSelection(ValueSelectionInterface valueSelection) {
        this.valueSelection=valueSelection;
    }
    
    /** Řešení problému.
     */
    public void solve() throws TimetableException {
        solveTime=generateTime=selectValueTime=selectVariableTime=0;
        maxScheduledActivities=0;
        nrIters = 0;
        problem.saveBestState();
        
        int canNotSchedule =0;

        long solveStart= System.currentTimeMillis();
        
        ActivityGroup unscheduled = problem.unscheduledActivities();
        
        while (unscheduled.size()>0 && nrIters<config.getInt(Config.NR_TRIES)) {
            config.log("Try: "+nrIters+", Unscheduled: "+unscheduled.size());
            if (maxScheduledActivities<problem.activities.size()-unscheduled.size()) {
              maxScheduledActivities=problem.activities.size()-unscheduled.size();
              problem.saveBestState();
            }
            
            long selectVariableStart=System.currentTimeMillis();;
              Activity activity=variableSelection.select(unscheduled,problem.dependences);
            selectVariableTime+=(System.currentTimeMillis()-selectVariableStart);
            
            long generateStart=System.currentTimeMillis();
              int slot = schedule(activity);
            generateTime+=(System.currentTimeMillis()-generateStart);
            
            if (slot<0) {
              canNotSchedule++;            
            } else {
              config.log("Activity: "+activity.name+" -> "+slot+" (resources: "+activity.selectedResources.toString()+" )");
              if (config.existKey("DEBUG_LOG") && problem.resources!=null) {
                  try { problem.print(config.getLogWriter(),problem.resources[0],0,problem.resources.length); } catch (Exception e) {}
              };
            };
            
            unscheduled = problem.unscheduledActivities();
            nrIters++;
        }
        
        if (maxScheduledActivities<problem.activities.size()-unscheduled.size())
            maxScheduledActivities=problem.activities.size()-unscheduled.size();
        
        solveTime=System.currentTimeMillis()-solveStart;
        generateTime-=selectValueTime;
    }
   
    /** Rozvrhne danou aktivitu.
     * @param activity nenaplánovaná aktivita
     * @return počáteční slot (-1 pokud se nepodařilo aktivitu naplánovat)
     */
    public int schedule(Activity activity) throws TimetableException {
        valueSelection.reset(activity);
        
        config.log("SCHEDULE: "+activity.name);

        for (int slot=0;slot<config.getInt(Config.NR_SLOTS);slot++) 
            if (activity.canScheduleAt(slot,null)) {
                ActivityGroup conflictActivities = problem.dependences.getConflictActivities(activity,slot);
                boolean can = true; 
                for (int i=0;i<conflictActivities.size();i++)
                    if (!conflictActivities.get(i).canReschedule) can=false;
                config.log("  -> "+slot+" (conflict: "+conflictActivities.toString()+")");
                if (can) generate(activity, valueSelection, slot, new ActivityResources(), conflictActivities);
            }
        
        long selectValueStart=System.currentTimeMillis();
        valueSelection.select();
        int selectedSlot=valueSelection.selectedSlot();
        selectValueTime+=(System.currentTimeMillis()-selectValueStart);
        
        if (selectedSlot<0) return selectedSlot;
        
        ActivityGroup conflictActivities = valueSelection.selectedConflictActivities();
        for (int i=0;i<conflictActivities.size();i++) 
            conflictActivities.get(i).remove(activity);
        activity.schedule(selectedSlot, valueSelection.selectedResources());
                
        return selectedSlot;
    }
    
    /** Rozvrhne danou aktivitu na dané místo (určeno pouze počátčením slotem a jedním zdrojem - takových míst může být více).
     * @param activity nenaplánovaná aktivita
     * @param slot počáteční slot
     * @param useResource zdroj, který musí být použit (null, pokud nená zvolen)
     * @return true, pokud se podařilo aktivitu naplánovat
     */
    public boolean schedule(Activity activity, int slot, Resource useResource) throws TimetableException {
        valueSelection.reset(activity);
        
        config.log("SCHEDULE: "+activity.name);

        if (activity.canScheduleAt(slot,null)) {
            ActivityGroup conflictActivities = problem.dependences.getConflictActivities(activity,slot);
            //System.out.println("Activity:"+activity.shortCut+"\nSlot:"+slot+"\nConflicts:"+conflictActivities.toString());
            boolean can = true; 
            for (int i=0;i<conflictActivities.size();i++)
                if (!conflictActivities.get(i).canReschedule) can=false;
            config.log("  -> "+slot+" (conflict: "+conflictActivities.toString()+")");
            if (can) generate(activity, valueSelection, slot, new ActivityResources(), conflictActivities,useResource);
        }
        
        long selectValueStart=System.currentTimeMillis();
        valueSelection.select();
        int selectedSlot=valueSelection.selectedSlot();
        selectValueTime+=(System.currentTimeMillis()-selectValueStart);
        
        if (selectedSlot<0) return false;
        
        ActivityGroup conflictActivities = valueSelection.selectedConflictActivities();
        for (int i=0;i<conflictActivities.size();i++) 
            conflictActivities.get(i).remove(activity);
        activity.schedule(selectedSlot, valueSelection.selectedResources());
                
        return true;
    }
    
    /** Rekuzivní metoda, která generuje možná umístění aktivity a přidává je do heuristiky výběru umístění.
     * @param activity nenaplánovaná aktivita, pro kterou se umístění hledá
     * @param valueSelection heuristika výběru umístění
     * @param slot počáteční slot, pro který se umístění generují
     * @param resources již vybrané zdroje (pro rekurzi, voláno s new ActivityResources())
     * @param conflict množina již nalezených konfliktních aktivit
     */    
    private void generate(Activity activity, ValueSelectionInterface valueSelection, int slot, ActivityResources resources, ActivityGroup conflict) throws TimetableException {
        int number= resources.size();
        if (number<activity.resources.size()) {
            if (activity.resources.isGroup(number)) {
                if (activity.resources.getResources(number).conjunctive) {
                    resources.add(activity.resources.getResources(number));
                    generate(activity,valueSelection,slot,resources,conflict);
                    resources.remove(activity.resources.getResources(number));
                } else {
                    for (int i=0;i<activity.resources.getResources(number).size();i++) 
                        if (activity.resources.getResources(number).get(i).canBeFreeAt(slot,activity.length)) {
                            resources.add(activity.resources.getResources(number).get(i));
                            generate(activity,valueSelection,slot,resources,conflict);
                            resources.remove(activity.resources.getResources(number).get(i));
                        }
                }
            } else {
                resources.add(activity.resources.getResource(number));
                generate(activity,valueSelection,slot,resources,conflict);
                resources.remove(activity.resources.getResource(number));
            }
        } else {
            ActivityGroup conflictActivities = (ActivityGroup) conflict.clone();
            for (int i=0;i<resources.size();i++) 
                if (resources.isGroup(i)) 
                    for (int k=0;k<resources.getResources(i).size();k++)
                        conflictActivities.addIfIsNot(resources.getResources(i).get(k).getActivitiesAt(slot,activity.length));
                else
                    conflictActivities.addIfIsNot(resources.getResource(i).getActivitiesAt(slot,activity.length));
            config.log("    GENER: resources="+resources.toString()+" (conflict: "+conflictActivities+")");

            long start=System.currentTimeMillis();
              valueSelection.addValue(slot,resources,problem.dependences,conflictActivities);
            selectValueTime+=(System.currentTimeMillis()-start);
        }
    }

    /** Rekuzivní metoda, která generuje možná umístění aktivity a přidává je do heuristiky výběru umístění.
     * @param activity nenaplánovaná aktivita, pro kterou se umístění hledá
     * @param valueSelection heuristika výběru umístění
     * @param slot počáteční slot, pro který se umístění generují
     * @param resources již vybrané zdroje (pro rekurzi, voláno s new ActivityResources())
     * @param conflict množina již nalezených konfliktních aktivit
     * @param useResource zdroj, který musí být použit (pokud je na výběr)
     */    
    private void generate(Activity activity, ValueSelectionInterface valueSelection, int slot, ActivityResources resources, ActivityGroup conflict, Resource useResource) throws TimetableException {
        if (useResource==null) {
            generate(activity, valueSelection, slot, resources, conflict);
            return;
        }
        int number= resources.size();
        if (number<activity.resources.size()) {
            if (activity.resources.isGroup(number)) {
                if (activity.resources.getResources(number).conjunctive) {
                    resources.add(activity.resources.getResources(number));
                    generate(activity,valueSelection,slot,resources,conflict,useResource);
                    resources.remove(activity.resources.getResources(number));
                } else {
                    if (activity.resources.getResources(number).indexOf(useResource)>=0) {
                        //System.out.println("  Using resource "+useResource.shortCut);
                        resources.add(useResource);
                        generate(activity,valueSelection,slot,resources,conflict,useResource);
                        resources.remove(useResource);
                    } else {
                        for (int i=0;i<activity.resources.getResources(number).size();i++) 
                            if (activity.resources.getResources(number).get(i).canBeFreeAt(slot,activity.length)) {
                                resources.add(activity.resources.getResources(number).get(i));
                                generate(activity,valueSelection,slot,resources,conflict,useResource);
                                resources.remove(activity.resources.getResources(number).get(i));
                            }
                    }
                }
            } else {
                resources.add(activity.resources.getResource(number));
                generate(activity,valueSelection,slot,resources,conflict,useResource);
                resources.remove(activity.resources.getResource(number));
            }
        } else {
            ActivityGroup conflictActivities = (ActivityGroup) conflict.clone();
            for (int i=0;i<resources.size();i++) 
                if (resources.isGroup(i)) 
                    for (int k=0;k<resources.getResources(i).size();k++)
                        conflictActivities.addIfIsNot(resources.getResources(i).get(k).getActivitiesAt(slot,activity.length));
                else
                    conflictActivities.addIfIsNot(resources.getResource(i).getActivitiesAt(slot,activity.length));
            config.log("    GENER: resources="+resources.toString()+" (conflict: "+conflictActivities+")");

            long start=System.currentTimeMillis();
              valueSelection.addValue(slot,resources,problem.dependences,conflictActivities);
            selectValueTime+=(System.currentTimeMillis()-start);
        }
    }
}