/*
 * Copyright 2012 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.droolsplannerdelirium.travelingsanta.solver.score;

import org.drools.planner.core.score.buildin.hardmediumsoft.DefaultHardMediumSoftScore;
import org.drools.planner.core.score.buildin.hardmediumsoft.HardMediumSoftScore;
import org.drools.planner.core.score.director.incremental.AbstractIncrementalScoreCalculator;
import org.droolsplannerdelirium.travelingsanta.domain.Appearance;
import org.droolsplannerdelirium.travelingsanta.domain.Domicile;
import org.droolsplannerdelirium.travelingsanta.domain.TravelingSalesmanTour;
import org.droolsplannerdelirium.travelingsanta.domain.Visit;

public class TspIncrementalScoreCalculator extends AbstractIncrementalScoreCalculator<TravelingSalesmanTour> {

    private Domicile domicile;

    private int hardScore;
    private int oddScore;
    private int evenScore;

    public void resetWorkingSolution(TravelingSalesmanTour tour) {
        if (tour.getDomicileList().size() != 1) {
            throw new UnsupportedOperationException(
                    "The domicileList (" + tour.getDomicileList() + ") should be a singleton.");
        }
        domicile = tour.getDomicileList().get(0);
        hardScore = 0;
        oddScore = 0;
        evenScore = 0;
        for (Visit visit : tour.getVisitList()) {
            insert(visit);
        }
    }

    public void beforeEntityAdded(Object entity) {
        // Do nothing
    }

    public void afterEntityAdded(Object entity) {
        insert((Visit) entity);
    }

    public void beforeAllVariablesChanged(Object entity) {
        retract((Visit) entity);
    }

    public void afterAllVariablesChanged(Object entity) {
        insert((Visit) entity);
    }

    public void beforeVariableChanged(Object entity, String variableName) {
        retract((Visit) entity);
    }

    public void afterVariableChanged(Object entity, String variableName) {
        insert((Visit) entity);
    }

    public void beforeEntityRemoved(Object entity) {
        retract((Visit) entity);
    }

    public void afterEntityRemoved(Object entity) {
        // Do nothing
    }

    private void insert(Visit visit) {
        Appearance previousOdd = visit.getPreviousOdd();
        if (previousOdd != null) {
            oddScore -= visit.getDistanceToPreviousOdd();
            // HACK: This counts too much, but the insert/retracts balance each other out
            oddScore += domicile.getCity().getDistance(previousOdd.getCity());
            oddScore -= domicile.getCity().getDistance(visit.getCity());
        }
        Appearance previousEven = visit.getPreviousEven();
        if (previousEven != null) {
            evenScore -= visit.getDistanceToPreviousEven();
            // HACK: This counts too much, but the insert/retracts balance each other out
            evenScore += domicile.getCity().getDistance(previousEven.getCity());
            evenScore -= domicile.getCity().getDistance(visit.getCity());
        }
        if (previousOdd != null) {
            if (previousOdd == previousEven) {
                hardScore--;
            }
            if (previousOdd instanceof Visit && ((Visit) previousOdd).getPreviousEven() == visit) {
                hardScore--;
                // TODO bug: domicilie.getPreviousEven() might be == visit
            }
        }
    }

    private void retract(Visit visit) {
        Appearance previousOdd = visit.getPreviousOdd();
        if (previousOdd != null) {
            oddScore += visit.getDistanceToPreviousOdd();
            // HACK: This counts too much, but the insert/retracts balance each other out
            oddScore -= domicile.getCity().getDistance(previousOdd.getCity());
            oddScore += domicile.getCity().getDistance(visit.getCity());
        }
        Appearance previousEven = visit.getPreviousEven();
        if (previousEven != null) {
            evenScore += visit.getDistanceToPreviousEven();
            // HACK: This counts too much, but the insert/retracts balance each other out
            evenScore -= domicile.getCity().getDistance(previousEven.getCity());
            evenScore += domicile.getCity().getDistance(visit.getCity());
        }
        if (previousOdd != null) {
            if (previousOdd == previousEven) {
                hardScore++;
            }
            if (previousOdd instanceof Visit && ((Visit) previousOdd).getPreviousEven() == visit) {
                hardScore++;
                // TODO bug: domicilie.getPreviousEven() might be == visit
            }
        }
    }

    public HardMediumSoftScore calculateScore() {
        int mediumScore = Math.min(oddScore, evenScore);
        int softScore = Math.max(oddScore, evenScore);
        return DefaultHardMediumSoftScore.valueOf(hardScore, mediumScore, softScore);
    }

}

@@ -982,16 +982,77 @@
 arance;%0A
+import org.droolsplannerdelirium.travelingsanta.domain.City;%0A
 import o
@@ -1108,16 +1108,16 @@
 micile;%0A
-
 import o
@@ -2763,32 +2763,162 @@
 (Visit visit) %7B%0A
+        City domicileCity = domicile.getCity();%0A        int domicileToVisitDistance = domicileCity.getDistance(visit.getCity()); %0A
         Appearan
@@ -3169,34 +3169,28 @@
  += domicile
-.get
 City
-()
 .getDistance
@@ -3250,47 +3250,23 @@
 cile
-.getCity().getDistance(visit.getCity())
+ToVisitDistance
 ;%0A  
@@ -3547,34 +3547,28 @@
  += domicile
-.get
 City
-()
 .getDistance
@@ -3630,47 +3630,23 @@
 cile
-.getCity().getDistance(visit.getCity())
+ToVisitDistance
 ;%0A  
@@ -4045,32 +4045,162 @@
 (Visit visit) %7B%0A
+        City domicileCity = domicile.getCity();%0A        int domicileToVisitDistance = domicileCity.getDistance(visit.getCity()); %0A
         Appearan
@@ -4451,34 +4451,28 @@
  -= domicile
-.get
 City
-()
 .getDistance
@@ -4532,47 +4532,23 @@
 cile
-.getCity().getDistance(visit.getCity())
+ToVisitDistance
 ;%0A  
@@ -4829,34 +4829,28 @@
  -= domicile
-.get
 City
-()
 .getDistance
@@ -4912,47 +4912,23 @@
 cile
-.getCity().getDistance(visit.getCity())
+ToVisitDistance
 ;%0A  

Improve calculator performance by sharing calculated distances.