/*
 * Copyright 2011 JBoss Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.drools.planner.examples.tsp.solver.solution.initializer;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.drools.FactHandle;
import org.drools.WorkingMemory;
import org.drools.planner.core.phase.custom.CustomSolverPhaseCommand;
import org.drools.planner.core.score.DefaultSimpleScore;
import org.drools.planner.core.score.Score;
import org.drools.planner.core.solution.director.SolutionDirector;
import org.drools.planner.examples.common.domain.PersistableIdComparator;
import org.drools.planner.examples.tsp.domain.City;
import org.drools.planner.examples.tsp.domain.CityAssignment;
import org.drools.planner.examples.tsp.domain.TravelingSalesmanTour;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TspStartingSolutionInitializer implements CustomSolverPhaseCommand {

    protected final transient Logger logger = LoggerFactory.getLogger(getClass());

    public void changeWorkingSolution(SolutionDirector solutionDirector) {
        TravelingSalesmanTour travelingSalesmanTour = (TravelingSalesmanTour) solutionDirector.getWorkingSolution();
        initializeCityAssignmentList(solutionDirector, travelingSalesmanTour);
    }

    private void initializeCityAssignmentList(SolutionDirector solutionDirector,
            TravelingSalesmanTour travelingSalesmanTour) {
        City startCity = travelingSalesmanTour.getStartCity();
        WorkingMemory workingMemory = solutionDirector.getWorkingMemory();

        // TODO the planning entity list from the solution should be used and might already contain initialized entities
        List<CityAssignment> cityAssignmentList = createCityAssignmentList(travelingSalesmanTour);
        List<CityAssignment> assignedCityAssignmentList = null;
        for (CityAssignment cityAssignment : cityAssignmentList) {
            FactHandle cityAssignmentHandle = null;
            if (assignedCityAssignmentList == null) {
                assignedCityAssignmentList = new ArrayList<CityAssignment>(cityAssignmentList.size());
                cityAssignment.setNextCityAssignment(cityAssignment);
                cityAssignment.setPreviousCityAssignment(cityAssignment);
                cityAssignmentHandle = workingMemory.insert(cityAssignment);
            } else {
                Score bestScore = DefaultSimpleScore.valueOf(Integer.MIN_VALUE);
                CityAssignment bestAfterCityAssignment = null;
                FactHandle bestAfterCityAssignmentFactHandle = null;
                CityAssignment bestBeforeCityAssignment = null;
                FactHandle bestBeforeCityAssignmentFactHandle = null;
                for (CityAssignment afterCityAssignment : assignedCityAssignmentList) {
                    CityAssignment beforeCityAssignment = afterCityAssignment.getNextCityAssignment();
                    FactHandle afterCityAssignmentFactHandle = workingMemory.getFactHandle(afterCityAssignment);
                    FactHandle beforeCityAssignmentFactHandle = workingMemory.getFactHandle(beforeCityAssignment);
                    // Do changes
                    afterCityAssignment.setNextCityAssignment(cityAssignment);
                    cityAssignment.setPreviousCityAssignment(afterCityAssignment);
                    cityAssignment.setNextCityAssignment(beforeCityAssignment);
                    beforeCityAssignment.setPreviousCityAssignment(cityAssignment);
                    if (cityAssignmentHandle == null) {
                        cityAssignmentHandle = workingMemory.insert(cityAssignment);
                    } else {
                        workingMemory.update(cityAssignmentHandle, cityAssignment);
                    }
                    workingMemory.update(afterCityAssignmentFactHandle, afterCityAssignment);
                    workingMemory.update(beforeCityAssignmentFactHandle, beforeCityAssignment);
                    // Calculate score
                    Score score = solutionDirector.calculateScoreFromWorkingMemory();
                    if (score.compareTo(bestScore) > 0) {
                        bestScore = score;
                        bestAfterCityAssignment = afterCityAssignment;
                        bestAfterCityAssignmentFactHandle = afterCityAssignmentFactHandle;
                        bestBeforeCityAssignment = beforeCityAssignment;
                        bestBeforeCityAssignmentFactHandle = beforeCityAssignmentFactHandle;
                    }
                    // Undo changes
                    afterCityAssignment.setNextCityAssignment(beforeCityAssignment);
                    beforeCityAssignment.setPreviousCityAssignment(afterCityAssignment);
                    workingMemory.update(afterCityAssignmentFactHandle, afterCityAssignment);
                    workingMemory.update(beforeCityAssignmentFactHandle, beforeCityAssignment);
                }
                if (bestAfterCityAssignment == null) {
                    throw new IllegalStateException("The bestAfterCityAssignment (" + bestAfterCityAssignment
                            + ") cannot be null.");
                }
                bestAfterCityAssignment.setNextCityAssignment(cityAssignment);
                cityAssignment.setPreviousCityAssignment(bestAfterCityAssignment);
                cityAssignment.setNextCityAssignment(bestBeforeCityAssignment);
                bestBeforeCityAssignment.setPreviousCityAssignment(cityAssignment);
                workingMemory.update(cityAssignmentHandle, cityAssignment);
                workingMemory.update(bestAfterCityAssignmentFactHandle, bestAfterCityAssignment);
                workingMemory.update(bestBeforeCityAssignmentFactHandle, bestBeforeCityAssignment);
            }
            assignedCityAssignmentList.add(cityAssignment);
            if (cityAssignment.getCity() == startCity) {
                travelingSalesmanTour.setStartCityAssignment(cityAssignment);
            }
            logger.debug("    CityAssignment ({}) initialized.", cityAssignment);
        }
        Collections.sort(cityAssignmentList, new PersistableIdComparator());
        travelingSalesmanTour.setCityAssignmentList(cityAssignmentList);
    }

    public List<CityAssignment> createCityAssignmentList(TravelingSalesmanTour travelingSalesmanTour) {
        List<City> cityList = travelingSalesmanTour.getCityList();
        // TODO weight: create by city on distance from the center ascending
        List<CityAssignment> cityAssignmentList = new ArrayList<CityAssignment>(cityList.size());
        int cityAssignmentId = 0;
        for (City city : cityList) {
            CityAssignment cityAssignment = new CityAssignment();
            cityAssignment.setId((long) cityAssignmentId);
            cityAssignment.setCity(city);
            cityAssignmentList.add(cityAssignment);
            cityAssignmentId++;
        }
        return cityAssignmentList;
    }

}
