public SolverState solve(ProblemDefinition problem) {
        OccurrenceSelection occurrenceSelection;
        AllocationSelection allocationSelection;
        try {
            occurrenceSelection = (OccurrenceSelection)Class.forName(
                    properties.getProperty("solver.occurrenceSelectionClassName", "net.personaltt.solver.heuristics.RouletteOccurrenceSelection")
                    ).getConstructor(DataProperties.class).newInstance(properties);
            allocationSelection = (AllocationSelection)Class.forName(
                    properties.getProperty("solver.allocationSelectionClassName", "net.personaltt.solver.heuristics.MainAllocationSelection")
                    ).getConstructor(DataProperties.class).newInstance(properties);
        } catch(Exception e) {
            System.out.println("Cannot instantiate selections objects");
            return null;
        }
        
        // init solver state
        currentSolution = new MainSolverState();
        currentSolution.init(problem.initialSchedule);
        
        System.out.printf("Starting solver. Events %s\n", problem.problemOccurrences.size());
        
        // initial is best found so far
        currentSolution.saveBestSolution();
        
        // Start timer
        long startTime = System.currentTimeMillis();

        long stuckedThreshold = Math.max((long)Math.pow(problem.problemOccurrences.size(), stuckedThresholdCoef), 1000);
        
        // while we have time and is not termination condition met, improve solution 
        while(currentSolution.iterate() && System.currentTimeMillis() - startTime < timeoutLimit) {
            //System.out.printf("\nIteration %s\n Cost\t%s\t%s\n", currentSolution.getItearation(), currentSolution.constraintsCost(), currentSolution.preferenceCost());
            
            //printState();
                        
            if (pause) {
                System.out.println("Paused");
                synchronized(this) {
                    try {
                        this.wait();
                    } catch(Exception e) {
                        
                    }
                }
                startTime = System.currentTimeMillis();
            }
            
            if ((currentSolution.getItearation() - currentSolution.getLastBestIteration()) > stuckedThreshold) {
                System.out.printf(" Detected stuck. Ending solver.\n");
                break;
            }
            
            // select variable - occurrence
            Occurrence toSolve = occurrenceSelection.select(currentSolution);
            //System.out.printf(" Selected occurrence %s at %s:%s\n", toSolve, toSolve.getAllocation(), toSolve.getAllocationCost());
            
            // select and save allocation of occurrence
            OccurrenceAllocation selectedAllocation = allocationSelection.select(currentSolution, toSolve);
            if (selectedAllocation == null ) {
               // System.out.printf("No allocation selected, skipping to next iteration");
                continue;
            }
            //System.out.printf(" Selected allocation: %s:%s \n", selectedAllocation, toSolve.getPreferrenceCost(selectedAllocation));
            
            boolean conflicts = currentSolution.setAllocation(toSolve, selectedAllocation);
            //System.out.printf(" Conflicting %s\n", conflicts);
            
            // store best solution
            if (currentSolution.isBetterThanBest()) {
                currentSolution.saveBestSolution();
                //System.out.printf(">New best solution found: %s:%s\n", currentSolution.constraintsCost(), currentSolution.preferenceCost());
            }
           
        }
        
        /*
        System.out.printf("Best solution found: c%s p%s i%s, End: i%s\n", 
                currentSolution.getBestSolution().constraintsCost(),
                currentSolution.getBestSolution().preferenceCost(),
                currentSolution.getLastBestIteration(),
                currentSolution.getItearation()
                );
        */
        // return best solution ever found
        return currentSolution;
    }