public class OptimizationFailedException extends Exception {

    /**
     * Creates a new instance of <code>OptimizationFailedException</code>
     * without detail message.
     */
    public OptimizationFailedException() {
    }

    /**
     * Constructs an instance of <code>OptimizationFailedException</code> with
     * the specified detail message.
     *
     * @param msg the detail message.
     */
    public OptimizationFailedException(String msg) {
        super(msg);
    }
    
    public OptimizationFailedException(Throwable other) {
        super(other);
    }
}
 public class RandomData extends AbstractTestData {

    private final int seed;

    public RandomData(int seed) {
        this.seed = seed;
    }

    @Override
    public Project getData() {
        final Random rng = new Random();
        rng.setSeed(seed);
        int panelCount = rng.nextInt(20);

        StockSheet sheet = makeSheet(800, 600);

        final boolean canRotate = false;
        List<Panel> inputs = new ArrayList<>();
        for (int i = 0; i < panelCount; i++) {
            double x = rng.nextInt(300);
            double y = rng.nextInt(300);
            inputs.add(new Panel(sheet, new Dimension(x, y), "p" + i, canRotate, 1));
        }
        return makeProject(inputs, 1);
    }

    @Override
    public String getTitle() {
        return "Random_" + seed;
    }

}
 public class Data1 extends AbstractTestData {

    @Override
    public Project getData() {
        StockSheet sheet = makeSheet(800, 600);

        final boolean canRotate = false;
        List<Panel> inputs = new ArrayList<>();
        inputs.add(new Panel(sheet, new Dimension(100, 100), "p1", canRotate, 1));
        inputs.add(new Panel(sheet, new Dimension(200, 250), "p2", canRotate, 1));
        inputs.add(new Panel(sheet, new Dimension(200, 250), "p3", canRotate, 1));
        inputs.add(new Panel(sheet, new Dimension(300, 250), "p4", canRotate, 1));
        inputs.add(new Panel(sheet, new Dimension(50, 250), "p4", canRotate, 1));
        inputs.add(new Panel(sheet, new Dimension(15, 13), "p5", canRotate, 1));
        inputs.add(new Panel(sheet, new Dimension(15, 13), "p6", canRotate, 1));

        return makeProject(inputs, 1);
    }

    @Override
    public String getTitle() {
        return "Data1";
    }

}
 public interface TestData {
    public Project getData();
    public String getTitle();
}
 public class DataRegal extends AbstractTestData {

    @Override
    public Project getData() {

        final StockSheet sheet = makeSheet(2500, 1250);
        int i;

        final double w = 600;
        final boolean canRotate = false;
        List<Panel> inputs = new ArrayList<>();
        inputs.add(new Panel(sheet, new Dimension(500, w), "side short", canRotate, 8));
        inputs.add(new Panel(sheet, new Dimension(560, w), "side long", canRotate, 4));
        inputs.add(new Panel(sheet, new Dimension(450, w), "top_bottom", canRotate, 12));
        inputs.add(new Panel(sheet, new Dimension(25, w), "halter", canRotate, 16));

        return makeProject(inputs, 4);
    }

    @Override
    public String getTitle() {
        return "DataRegal";
    }

}
 public class Project implements Serializable {

    private List<Panel> panels = new ArrayList<>();
    private double bladeWidth = 3;
    private Optional<OptimizationResult> optimizationResult;

    public List<Panel> getPanels() {
        return panels;
    }

    public void setPanels(@NonNull final List<Panel> inputs) {
        this.panels = inputs;
    }

    public double getBladeWidth() {
        return bladeWidth;
    }

    public void setBladeWidth(final double bladeWidth) {
        checkArgument(bladeWidth >= 0, "bladeWidth can't be negative");
        this.bladeWidth = bladeWidth;
    }

    public Optional<OptimizationResult> getOptimizationResult() {
        return optimizationResult;
    }

    public void setOptimizationResult(@NonNull final Optional<OptimizationResult> optimizationResult) {
        this.optimizationResult = optimizationResult;
    }

    public void setOptimizationResult(java.util.Optional<OptimizationResult> newValue) {
        if (newValue.isPresent()) {
            this.optimizationResult = Optional.of(newValue.get());

        } else {
            this.optimizationResult = Optional.absent();

        }
    }

    public List<PanelInstance> getPanelInstances() {
        List<PanelInstance> pis = new ArrayList<>();
        for (Panel p : panels) {
            for (int i = 0; i < p.getCount(); i++) {
                pis.add(new PanelInstance(p.getSheet(), p.getDimension(), p.getTitle(), i, p.canRotate()));
            }
        }
        return pis;
    }
}
 @ToString
public class Layout implements Serializable {

    private final StockSheet sheet;
    private final int id;
    private final CutTree cutTree;

    public Layout(@NonNull final StockSheet sheet, final int id) {
        this.sheet = sheet;
        this.id = id;
        this.cutTree = new CutTree(sheet);
    }

    public StockSheet getSheet() {
        return sheet;
    }

    public int getId() {
        return id;
    }

    public int getNumberOfPlacedPanels() {
        int num = 0;
        for (CutTreeNode node : getCutTree()) {
            if (node instanceof PanelNode) {
                num++;
            }
        }
        return num;
    }

    public int getNumberOfSubSheets() {
        int num = 0;
        for (CutTreeNode n : getCutTree()) {
            num++;
        }
        return num;
    }

    public CutTree getCutTree() {
        return cutTree;
    }
}
 public class GAOptimizationStrategy extends AbstractOptimizationStrategy {

    private static final Logger log = Logger.getLogger(GAOptimizationStrategy.class.getName());
    public static java.util.Random random = new java.util.Random();

    @Override
    public OptimizationResult optimize(@NonNull final Project project, 
            @NonNull final FitnessFunction fitness) {

        final ISeq<PanelInstance> panels = ISeq.of(project.getPanelInstances());
        final Codec<ISeq<PanelInstance>, EnumGene<PanelInstance>> codec = codecs.ofPermutation(panels);

        final Engine<EnumGene<PanelInstance>, Double> engine
                = Engine.builder((seq) -> fitness.fitness(optimizeAux(project, seq.asList()).getStats()), codec)
                        .optimize(Optimize.MINIMUM)
                        .build();

        // 4.) Start the execution (evolution) and
        //     collect the result.
        ISeq<PanelInstance> result = codec.decode(
                engine.stream()
                        .limit(100)
                        .collect(EvolutionResult.toBestGenotype()));

        return optimizeAux(project, result.asList());
    }

    public OptimizationResult optimizeAux(@NonNull final Project project, @NonNull final List<PanelInstance> panels) {

        final OptimizationResult optimizationResult = new OptimizationResult();

        for (PanelInstance p : panels) {
            FreeNode candidate = findSheet(optimizationResult, p);
            if (candidate == null) {
                optimizationResult.createNewLayout(p);
                candidate = findSheet(optimizationResult, p);
            }

            candidate = cutToFit(candidate, project, p);
            candidate.setPanel(p);
        }

        return optimizationResult;
    }
}
 public class NaiiveOptimizationStrategy extends AbstractOptimizationStrategy {

    private final static Logger log = Logger.getLogger(NaiiveOptimizationStrategy.class.getName());

    @Override
    public OptimizationResult optimize(@NonNull final Project project, @NonNull final FitnessFunction fitness) {

        OptimizationResult optimizationResult = new OptimizationResult();

        List<PanelInstance> panels = project.getPanelInstances().stream()
                .sorted((b, a) -> Double.compare(a.getDimension().getLength(), b.getDimension().getLength()))
                .collect(Collectors.toList());

        for (PanelInstance p : panels) {
            FreeNode candidate = findSheet(optimizationResult, p);
            if (candidate == null) {
                optimizationResult.createNewLayout(p);
                candidate = findSheet(optimizationResult, p);
            }

            candidate = cutToFit(candidate, project, p);
            candidate.setPanel(p);
        }

        return optimizationResult;
    }
}
 @ToString
public class OptimizationResult implements Serializable {

    private final List<Layout> layouts = new ArrayList<>();
    private double runtime;

    public void createNewLayout(@NonNull PanelInstance p) {
        addLayout(new Layout(p.getSheet(), layouts.size()));
    }

    private void addLayout(Layout l) {
        layouts.add(l);
    }

    public OptimizationResultStats getStats() {
        int numberOfCuts = 0;
        int numberOfLayouts = layouts.size();
        double totalCutLength = 0;
        double sheetArea = 0;
        double usedArea = 0;
        double boundingBoxArea = 0;
        for (Layout layout : layouts) {
            CutTreeStats substats = layout.getCutTree().getStats();
            numberOfCuts += substats.getNumberOfCuts();
            totalCutLength += substats.getTotalCutLength();
            sheetArea += substats.getSheetArea();
            usedArea += substats.getUsedArea();
            boundingBoxArea += substats.getBoundingBoxArea();
        }
        OptimizationResultStats stats = new OptimizationResultStats(numberOfLayouts,
                numberOfCuts,
                totalCutLength,
                sheetArea,
                usedArea,
                boundingBoxArea);
        return stats;
    }

    public List<Layout> getLayouts() {
        return layouts;
    }

    public double getRuntime() {
        return runtime;
    }

    public void setRuntime(double runtime) {
        this.runtime = runtime;
    }

}
 @ToString
public class OptimizationResultStats {

    private final int numberOfLayouts;
    private final int numberOfCuts;
    private final double totalCutLength;
    private final double sheetArea;
    private final double usedArea;
    private final double wastagePercent;
    private final double boundingBoxArea;

    public OptimizationResultStats(int numberOfLayouts, 
            int numberOfCuts, 
            double totalCutLength, 
            double sheetArea, 
            double usedArea,
            double boundingBoxArea) {
        this.numberOfLayouts = numberOfLayouts;
        this.numberOfCuts = numberOfCuts;
        this.totalCutLength = totalCutLength;
        this.sheetArea = sheetArea;
        this.usedArea = usedArea;
        this.wastagePercent = 100 * (1 - (usedArea / sheetArea));
        this.boundingBoxArea = boundingBoxArea;
    }

    public int getNumberOfCuts() {
        return numberOfCuts;
    }

    public double getTotalCutLength() {
        return totalCutLength;
    }

    public double getSheetArea() {
        return sheetArea;
    }

    public double getUsedArea() {
        return usedArea;
    }

    public double getWastagePercent() {
        return wastagePercent;
    }

    public int getNumberOfLayouts() {
        return numberOfLayouts;
    }

    public double getBoundingBoxArea() {
        return boundingBoxArea;
    }
}
 public class SmartOptimizationStrategy extends AbstractOptimizationStrategy {

    private static final Logger log = Logger.getLogger(SmartOptimizationStrategy.class.getName());

    @Override
    public OptimizationResult optimize(@NonNull Project project, @NonNull FitnessFunction fitness) {

        List<PanelInstance> panels = project.getPanelInstances().stream()
                .sorted((b, a) -> Double.compare(a.getDimension().getArea(), b.getDimension().getArea()))
                .collect(Collectors.toList());

        OptimizationResult optimizationResult = new OptimizationResult();
        
        if (panels.isEmpty()) {
            return optimizationResult;
        }

        Deque<PanelInstance> unassignedPanels = new LinkedList<>();

        for (PanelInstance p : panels) {
            unassignedPanels.add(p);
        }

        final PanelInstance firstPanel = unassignedPanels.peekFirst();

        optimizationResult.createNewLayout(firstPanel);

        while (!unassignedPanels.isEmpty()) {
            PanelInstance panel = unassignedPanels.removeFirst();

            List<FreeNode> candidates = getFreeNodes(optimizationResult).stream()
                    .filter(t -> t.canHold(panel))
                    .collect(Collectors.toList());

            FreeNode candidate = candidates.stream()
                    .filter((t) -> almostSameSize(t.getDimension().getWidth(), panel.getDimension().getWidth()) && almostSameSize(t.getDimension().getLength(), panel.getDimension().getLength()))
                    .findFirst()
                    .orElse(null);

            if (candidate == null) {
                candidate = candidates.stream()
                        .filter((t) -> almostSameSize(t.getDimension().getWidth(),panel.getDimension().getWidth()))
                        .findFirst()
                        .orElse(null);
            }

            if (candidate == null) {
                candidate = candidates.stream()
                        .filter((t) -> almostSameSize(t.getDimension().getLength(), panel.getDimension().getLength()))
                        .findFirst()
                        .orElse(null);
            }

            if (candidate == null) {
                candidate = candidates.stream()
                        .findFirst()
                        .orElse(null);
            }

            if (candidate == null) {

                assert(optimizationResult.getLayouts().size() < 100);
                log.info("failed to find free space on sheet, creating a new layout");
                optimizationResult.createNewLayout(panel);
                // push back p, process it again!
                unassignedPanels.addFirst(panel);
                continue;
            }

            candidate = cutToFit(candidate, project, panel);
            final PanelNode pn = candidate.setPanel(panel);

            log.info(String.format("placed panel %s on layout", pn));
        }

        return optimizationResult;
    }
}
 public interface OptimizationStrategy {
    public OptimizationResult optimize(@NonNull Project project, @NonNull FitnessFunction fitness) throws OptimizationFailedException;
}
 public class Optimizer {
    
    private static final Logger log = Logger.getLogger("Optimizer");
    
    public OptimizationResult optimize(@NonNull Project project, @NonNull OptimizationStrategy strategy) throws OptimizationFailedException {
        
        if (project.getPanels().isEmpty()) {
            throw new EmptyProjectException("Project doesn't have any panels configured; nothing to optimize");
        }
        
        log.info("starting optimization");
        
        FitnessFunction fitness = new SimpleFitnessFunction();
        
        Stopwatch stopwatch = Stopwatch.createStarted();
        OptimizationResult result = strategy.optimize(project, fitness);
        result.setRuntime(stopwatch.elapsed(TimeUnit.MILLISECONDS));
        
        log.info(String.format("finished optimization. #sheets=%d, result=%s", result.getLayouts().size(), result));
        return result;
    }
    
}
 public class PermutationOptimizationStrategy extends AbstractOptimizationStrategy {

    private final Logger log = Logger.getLogger("PermutationOptimizationStrategy");

    @Override
    public OptimizationResult optimize(Project project, FitnessFunction fitness) throws OptimizationFailedException {

        if (project.getPanels().size() > 10) {
            throw new OptimizationFailedException("Permutation calculation for n > 10");
        }
        Permutations<PanelInstance> perm = new Permutations<>(project.getPanels().toArray(new PanelInstance[]{}));

        OptimizationResult bestResult = null;
        double bestFitness = Double.MAX_VALUE;

        int count = 0;
        while (perm.hasNext()) {

            OptimizationResult res = optimizeAux(project, Arrays.asList(perm.next()));
            double curFitness = fitness.fitness(res.getStats());

            if (curFitness < bestFitness) {
                bestFitness = curFitness;
                bestResult = res;
            }
            count++;
        }
        System.out.println("total: " + count);

        return bestResult;
    }

    public OptimizationResult optimizeAux(Project project, List<PanelInstance> panels) {

        OptimizationResult optimizationResult = new OptimizationResult();

//        optimizationResult.createNewLayout(p);
        for (PanelInstance p : panels) {
            FreeNode candidate = findSheet(optimizationResult, p);
            if (candidate == null) {
                log.info("failed to find free space on sheet, creating a new layout");

                optimizationResult.createNewLayout(p);
                candidate = findSheet(optimizationResult, p);
            }

            candidate = cutToFit(candidate, project, p);
            candidate.setPanel(p);
        }

        return optimizationResult;
    }

}
 public class SAOptimizationStrategy extends AbstractOptimizationStrategy {

    private final Logger log = Logger.getLogger(SAOptimizationStrategy.class.getName());
    public static java.util.Random random = new java.util.Random();

    @Override
    public OptimizationResult optimize(@NonNull Project project, @NonNull FitnessFunction fitness) {

        final int ITERATIONS = 10000;

        // Uncomment the desired problem below:
        OptState state = new OptState(project, fitness);
        //State state = new BinPackingProblem();
        state.panels = project.getPanelInstances();

        // Uncomment the desired stochastic local search below:
        //OptState minState = (OptState) new HillDescender(state).search(ITERATIONS);
        OptState minState = (OptState) new SimulatedAnnealer(state, 1, .99999).search(ITERATIONS);

        return minState.result;
    }

    public OptimizationResult optimizeAux(@NonNull Project project, @NonNull List<PanelInstance> panels) {

        OptimizationResult optimizationResult = new OptimizationResult();

        for (PanelInstance p : panels) {
            FreeNode candidate = findSheet(optimizationResult, p);
            if (candidate == null) {
                optimizationResult.createNewLayout(p);
                candidate = findSheet(optimizationResult, p);
            }

            candidate = cutToFit(candidate, project, p);
            candidate.setPanel(p);
        }

        return optimizationResult;
    }

    class OptState implements State {

        Project project;
        FitnessFunction fitness;
        List<PanelInstance> panels, panelsPrev;
        OptimizationResult result;

        public OptState(@NonNull Project project, @NonNull FitnessFunction fitness) {
            this.project = project;
            this.fitness = fitness;
            this.panelsPrev = this.panels;
        }

        @Override
        public void step() {

            panelsPrev = new ArrayList<>(panels);

            final int posA = random.nextInt(panels.size());
            int posB = random.nextInt(panels.size());
            while (posA == posB) {
                posB = random.nextInt(panels.size());
            }

            panels.set(posA, panelsPrev.get(posB));
            panels.set(posB, panelsPrev.get(posA));
        }

        @Override
        public void undo() {
            panels = panelsPrev;
        }

        @Override
        public double energy() {
            result = optimizeAux(project, panels);
            return fitness.fitness(result.getStats());
        }

        public Object clone() {
            OptState copy = new OptState(project, fitness);
            copy.panels = new ArrayList<>(panels);
            if (panelsPrev != null ) {
                copy.panelsPrev = new ArrayList<>(panelsPrev);
            }
            copy.result = result;
            return copy;
        }

    }

}
 public class SAOptimizationStrategy2 extends AbstractOptimizationStrategy {

    private static final Logger log = Logger.getLogger(SAOptimizationStrategy2.class.getName());
    public static java.util.Random random = new java.util.Random();

    @Override
    public OptimizationResult optimize(@NonNull Project project, @NonNull FitnessFunction fitness) throws OptimizationFailedException {
        try {
            // higher numbers will break travis CI unit tests...
            int NUMBER_OF_STEPS = 10000;
            double INITIAL_TEMPERATURE = 10;

            Scheduler scheduler = new LinearDecayScheduler(INITIAL_TEMPERATURE, NUMBER_OF_STEPS);
            Problem<OptState> problem = new OptProblem(project, new SimpleFitnessFunction());
            Solver<OptState> solver = new Solver(problem, scheduler);
            OptState solution = solver.solve();

            return solution.result;
        } catch (InfeasibleProblemException ex) {
            throw new OptimizationFailedException(ex);
        }
    }

    public OptimizationResult optimizeAux(@NonNull Project project, @NonNull List<PanelInstance> panels) {

        OptimizationResult optimizationResult = new OptimizationResult();

        for (PanelInstance p : panels) {
            FreeNode candidate = findSheet(optimizationResult, p);
            if (candidate == null) {
                optimizationResult.createNewLayout(p);
                candidate = findSheet(optimizationResult, p);
            }

            candidate = cutToFit(candidate, project, p);
            candidate.setPanel(p);
        }

        return optimizationResult;
    }

    class OptProblem implements Problem<OptState> {

        Project project;
        FitnessFunction fitness;

        public OptProblem(@NonNull Project project, @NonNull FitnessFunction fitness) {
            this.project = project;
            this.fitness = fitness;
        }

        @Override
        public double energy(@NonNull OptState t) {
            t.result = optimizeAux(project, t.panels);
            return fitness.fitness(t.result.getStats());
        }

        @Override
        public OptState initialState() throws InfeasibleProblemException {
            OptState initial = new OptState();
            initial.panels = project.getPanelInstances();
            return initial;
        }
    }

    class OptState implements SearchState<OptState> {

        OptimizationResult result = null;
        List<PanelInstance> panels;

        @Override
        public OptState step() {
            final int posA = random.nextInt(panels.size());
            int posB = random.nextInt(panels.size());
            while (posA == posB) {
                posB = random.nextInt(panels.size());
            }

            OptState other = new OptState();
            other.result = result;
            other.panels = new ArrayList<>(panels);
            other.panels.set(posA, panels.get(posB));
            other.panels.set(posB, panels.get(posA));
            return other;
        }

    }

}
 public class SimpleFitnessFunction implements FitnessFunction {

    @Override
    public double fitness(@NonNull OptimizationResultStats stats) {
        return /*stats.getBoundingBoxArea() * */ Math.sqrt(stats.getSheetArea()) * stats.getTotalCutLength() / 1e4;
    }
}
 public class DataNoPanels extends AbstractTestData {

    @Override
    public Project getData() {
        StockSheet sheet = makeSheet(800, 600);

        List<Panel> inputs = new ArrayList<>();

        return makeProject(inputs, 1);
    }

    @Override
    public String getTitle() {
        return "NoPanels";
    }

}
 public class DataTable extends AbstractTestData {

    @Override
    public Project getData() {

        final StockSheet sheet = makeSheet(2500, 600);
        int i;

        /*      
        Input Group	gestell			1															1	1
Input PanelInstance	bein	900 mm	60 mm	4	18.0mm	SameAsSheet	buche leimholz					False							2	1													
Input PanelInstance	einlegebrett	732 mm	564 mm	2	18.0mm	SameAsSheet	buche leimholz					False							3	1													
Input PanelInstance	flansch	750 mm	30 mm	0	18.0mm	Yes	buche leimholz					False							4	1													
Input PanelInstance	seitenstrebe	630 mm	60 mm	4	18.0mm	SameAsSheet	buche leimholz					False							5	1													
Input PanelInstance	stuetze hinten	564 mm	60 mm	2	18.0mm	SameAsSheet	buche leimholz					False							6	1													
Input PanelInstance	bein	900 mm	60 mm	4	18.0mm	SameAsSheet	buche leimholz					False							7	7													
Input PanelInstance	einlegebrett	732 mm	564 mm	2	18.0mm	SameAsSheet	buche leimholz					False							8	8													
Input PanelInstance	flansch	750 mm	30 mm	0	18.0mm	Yes	buche leimholz					False							9	9													
Input PanelInstance	seitenstrebe	630 mm	60 mm	4	18.0mm	SameAsSheet	buche leimholz					False							10	10													
Input PanelInstance	stuetze hinten	564 mm	60 mm	2	18.0mm	SameAsSheet	buche leimholz					False							11	11													
Input Group	top			1															12	12
Input PanelInstance	sims seite	768 mm	136 mm	2	18.0mm	SameAsSheet	buche leimholz					False							13	12													
Input PanelInstance	sims front	600 mm	76 mm	1	18.0mm	SameAsSheet	buche leimholz					False							14	12													
Input PanelInstance	rueckteil	636 mm	136 mm	1	18.0mm	SameAsSheet	buche leimholz					False							15	12													
Input PanelInstance	liegeflaeche	750 mm	600 mm	1	18.0mm	SameAsSheet	buche leimholz					False							16	12													
Input PanelInstance	sims seite	768 mm	136 mm	2	18.0mm	SameAsSheet	buche leimholz					False							17	17													
Input PanelInstance	sims front	600 mm	76 mm	1	18.0mm	SameAsSheet	buche leimholz					False							18	18													
Input PanelInstance	rueckteil	636 mm	136 mm	1	18.0mm	SameAsSheet	buche leimholz					False							19	19													
Input PanelInstance	liegeflaeche	750 mm	600 mm	1	18.0mm	SameAsSheet	buche leimholz					False							20	20													

         */
        final boolean canRotate = true;
        List<Panel> inputs = new ArrayList<>();
        inputs.add(new Panel(sheet, new Dimension(900, 60), "bein", canRotate, 4));
        inputs.add(new Panel(sheet, new Dimension(732, 564), "einlegebrett", canRotate, 2));
        inputs.add(new Panel(sheet, new Dimension(630, 60), "seitenstrebe", canRotate, 4));
        inputs.add(new Panel(sheet, new Dimension(564, 60), "stuetze hinten", canRotate, 2));
        inputs.add(new Panel(sheet, new Dimension(768, 136), "sims seite", canRotate, 2));
        inputs.add(new Panel(sheet, new Dimension(600, 76), "sims front", canRotate, 1));
        inputs.add(new Panel(sheet, new Dimension(636, 136), "sims back", canRotate, 1));
        inputs.add(new Panel(sheet, new Dimension(750, 600), "lf", canRotate, 1));

        return makeProject(inputs, 4);
    }

    @Override
    public String getTitle() {
        return "Tisch";
    }

}

import com.cutlet.lib.errors.OptimizationFailedException;
import com.cutlet.lib.testing.RandomData;
import com.cutlet.lib.testing.Data1;
import com.cutlet.lib.testing.TestData;
import com.cutlet.lib.testing.DataRegal;
import com.cutlet.lib.model.Project;
import com.cutlet.lib.model.Layout;
import com.cutlet.lib.optimizer.GAOptimizationStrategy;
import com.cutlet.lib.optimizer.NaiiveOptimizationStrategy;
import com.cutlet.lib.optimizer.OptimizationResult;
import com.cutlet.lib.optimizer.OptimizationResultStats;
import com.cutlet.lib.optimizer.SmartOptimizationStrategy;
import java.io.PrintStream;
import java.util.logging.Logger;
import com.cutlet.lib.optimizer.OptimizationStrategy;
import com.cutlet.lib.optimizer.Optimizer;
import com.cutlet.lib.optimizer.PermutationOptimizationStrategy;
import com.cutlet.lib.optimizer.SAOptimizationStrategy;
import com.cutlet.lib.optimizer.SAOptimizationStrategy2;
import com.cutlet.lib.optimizer.SimpleFitnessFunction;
import com.cutlet.lib.testing.DataNoPanels;
import com.cutlet.lib.testing.DataTable;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;

/*
 * Copyright (C) 2017 rudolf.muehlbauer@gmail.com
 */
package com.cutlet.benchmark;


/**
 *
 * @author rmuehlba
 */
public class OptimizerBenchmark {

    private final Logger log = Logger.getLogger("benchmark");

    public static void main(String... args) {

        setDebugLevel(Level.SEVERE);

        System.err.println("benchtmark");

        (new OptimizerBenchmark()).runBenchmarks();
    }

    public void runBenchmarks() {
        log.info("running benchmarks");

        List<TestData> benchmarks = Arrays.asList(
                new Data1(),
                new RandomData(100),
                new RandomData(201),
                new DataRegal(),
                new DataTable(),
                new DataNoPanels()
        );

        List<String> optimizers = Arrays.asList("Smart", "Naiive", "SA", "SA2", "GA" /*, "Perm"*/);

        final PrintStream output = System.err;

        output.println(String.format("%-10s %-13s %10s %10s %10s %10s %10s %10s %10s",
                "Method",
                "dataset",
                "#panels",
                "#sheets",
                "waste%",
                "#cuts",
                "cutlength",
                "fitness",
                "runtime"));

        for (String opt : optimizers) {
            for (TestData b : benchmarks) {
                try {
                    final OptimizationStrategy strategy = createStrategy(opt);
                    final Optimizer optimizer = new Optimizer();
                    final Project project = b.getData();
                    saveProject(project);
                    final OptimizationResult result = optimizer.optimize(project, strategy);

                    final int nPanelsAct = sumNumberOfPanels(result);
                    final int nPanelsExp = project.getPanels().stream().mapToInt(p-> p.getCount()).sum();

                    OptimizationResultStats stats = result.getStats();

                    if (nPanelsAct != nPanelsExp) {
                        throw new OptimizationFailedException(String.format(
                                    "number of panelsActual[%d] != number of panelsExpected[%d] in %s[%s]", 
                                    nPanelsAct, 
                                    nPanelsExp, 
                                    b.getTitle(), 
                                    opt));
                    }

                    output.println(String.format("%-10s %-13s %10d %10d %10.1f %10d %10.0f %10.1f %10.0f",
                                opt,
                                b.getTitle(),
                                nPanelsExp,
                                stats.getNumberOfLayouts(),
                                stats.getWastagePercent(),
                                stats.getNumberOfCuts(),
                                stats.getTotalCutLength(),
                                (new SimpleFitnessFunction()).fitness(stats),
                                result.getRuntime()));

                    //ConsoleOutput.dumpResult(result);
                } catch (OptimizationFailedException ex) {
                    //Logger.getLogger(OptimizerBenchmark.class.getName()).log(Level.SEVERE, null, ex);
                    output.println(String.format("%-10s %-13s failed: %s",
                            opt,
                            b.getTitle(),
                            ex.getMessage()));
                }
            }
        }
    }

    private int sumNumberOfPanels(OptimizationResult r) {
        int i = 0;
        for (Layout l : r.getLayouts()) {
            i += l.getNumberOfPlacedPanels();
        }
        return i;
    }

    private void saveProject(Project project) {
        // just for testing, save and load the project
        try (ObjectOutputStream oos
                = new ObjectOutputStream(new FileOutputStream("project.cutlet"))) {

            oos.writeObject(project);

        } catch (Exception ex) {
            ex.printStackTrace();
        }

        try (ObjectInput input = new ObjectInputStream(new BufferedInputStream(new FileInputStream("project.cutlet")));) {
            //deserialize the List
            input.readObject();
        } catch (ClassNotFoundException ex) {
            log.log(Level.SEVERE, "Cannot perform input. Class not found.", ex);
        } catch (IOException ex) {
            log.log(Level.SEVERE, "Cannot perform input.", ex);
        }
    }

    public OptimizationStrategy createStrategy(String which) {
        switch (which) {
            case "Smart":
                return new SmartOptimizationStrategy();
            case "Naiive":
                return new NaiiveOptimizationStrategy();
            case "Perm":
                return new PermutationOptimizationStrategy();
            case "SA":

                return new SAOptimizationStrategy();
