/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

/* $Id$ */

package org.apache.fop.layoutmgr.inline;

import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Line layout possibilities.
 */
public class LineLayoutPossibilities {

    /** logger instance */
    private static final Log LOG = LogFactory.getLog(LineLayoutPossibilities.class);

    private final class Possibility {
        private int lineCount;
        private double demerits;
        private List<LineLayoutManager.LineBreakPosition> breakPositions;

        private Possibility(int lc, double dem) {
            lineCount = lc;
            demerits = dem;
            breakPositions = new java.util.ArrayList<LineLayoutManager.LineBreakPosition>(lc);
        }

        private int getLineCount() {
            return lineCount;
        }

        private double getDemerits() {
            return demerits;
        }

        private void addBreakPosition(LineLayoutManager.LineBreakPosition pos) {
            // Positions are always added with index 0 because
            // they are created backward, from the last one to
            // the first one
            breakPositions.add(0, pos);
        }

        private LineLayoutManager.LineBreakPosition getBreakPosition(int i) {
            return breakPositions.get(i);
        }
    }

    private List possibilitiesList;
    private List savedPossibilities;
    private int minimumIndex;
    private int optimumIndex;
    private int maximumIndex;
    private int chosenIndex;
    private int savedOptLineCount;

    /** default constructor */
    public LineLayoutPossibilities() {
        possibilitiesList = new java.util.ArrayList<>();
        savedPossibilities = new java.util.ArrayList<>();
        optimumIndex = -1;
    }

    /**
     * Add possibility.
     * @param ln line number
     * @param dem demerits
     */
    public void addPossibility(int ln, double dem) {
        possibilitiesList.add(new Possibility(ln, dem));
        if (possibilitiesList.size() == 1) {
            // first Possibility added
            minimumIndex = 0;
            optimumIndex = 0;
            maximumIndex = 0;
            chosenIndex = 0;
        } else {
            if (dem < ((Possibility)possibilitiesList.get(optimumIndex)).getDemerits()) {
                optimumIndex = possibilitiesList.size() - 1;
                chosenIndex = optimumIndex;
            }
            if (ln < ((Possibility)possibilitiesList.get(minimumIndex)).getLineCount()) {
                minimumIndex = possibilitiesList.size() - 1;
            }
            if (ln > ((Possibility)possibilitiesList.get(maximumIndex)).getLineCount()) {
                maximumIndex = possibilitiesList.size() - 1;
            }
        }
    }

    /**
     * Save in a different array the computed Possibilities,
     * so possibilitiesList is ready to store different Possibilities.
     * @param bSaveOptLineCount true if should save optimum line count
     */
    public void savePossibilities(boolean bSaveOptLineCount) {
        if (bSaveOptLineCount) {
            savedOptLineCount = getOptLineCount();
        } else {
            savedOptLineCount = 0;
        }
        savedPossibilities = possibilitiesList;
        possibilitiesList = new java.util.ArrayList<>();
    }

    /**
     * Replace the Possibilities stored in possibilitiesList with
     * the ones stored in savedPossibilities and having the same line number.
     */
    public void restorePossibilities() {
        int index = 0;
        while (savedPossibilities.size() > 0) {
            Possibility restoredPossibility = (Possibility) savedPossibilities.remove(0);
            if (restoredPossibility.getLineCount() < getMinLineCount()) {
                // if the line number of restoredPossibility is less than the minimum one,
                // add restoredPossibility at the beginning of the list
                possibilitiesList.add(0, restoredPossibility);
                // update minimumIndex
                minimumIndex = 0;
                // shift the other indexes;
                optimumIndex++;
                maximumIndex++;
                chosenIndex++;
            } else if (restoredPossibility.getLineCount() > getMaxLineCount()) {
                // if the line number of restoredPossibility is greater than the maximum one,
                // add restoredPossibility at the end of the list
                possibilitiesList.add(possibilitiesList.size(), restoredPossibility);
                // update maximumIndex
                maximumIndex = possibilitiesList.size() - 1;
                index = maximumIndex;
            } else {
                // find the index of the Possibility that will be replaced
                while (index < maximumIndex
                       && getLineCount(index) < restoredPossibility.getLineCount()) {
                    index++;
                }
                if (getLineCount(index) == restoredPossibility.getLineCount()) {
                    possibilitiesList.set(index, restoredPossibility);
                } else {
                    // this should not happen
                    LOG.error("LineLayoutPossibilities restorePossibilities(),"
                        + " min= " + getMinLineCount()
                        + " max= " + getMaxLineCount()
                        + " restored= " + restoredPossibility.getLineCount());
                    return;
                }
            }
            // update optimumIndex and chosenIndex
            if (savedOptLineCount == 0
                && getDemerits(optimumIndex) > restoredPossibility.getDemerits()
                || savedOptLineCount != 0
                && restoredPossibility.getLineCount() == savedOptLineCount) {
                optimumIndex = index;
                chosenIndex = optimumIndex;
            }
        }
        //log.debug(">> minLineCount = " + getMinLineCount()
        //  + " optLineCount = " + getOptLineCount() + " maxLineCount() = " + getMaxLineCount());
    }

    /**
     * @param pos a position
     * @param i an index into posibilities list
     */
    public void addBreakPosition(LineLayoutManager.LineBreakPosition pos, int i) {
        ((Possibility)possibilitiesList.get(i)).addBreakPosition(pos);
    }

    /** @return true if can use more lines */
    public boolean canUseMoreLines() {
        return (getOptLineCount() < getMaxLineCount());
    }

    /** @return true if can use fewer lines */
    public boolean canUseLessLines() {
        return (getMinLineCount() < getOptLineCount());
    }

    /** @return the line count of the minimum index */
    public int getMinLineCount() {
        return getLineCount(minimumIndex);
    }

    /** @return the line count of the optimum index */
    public int getOptLineCount() {
        return getLineCount(optimumIndex);
    }

    /** @return the line count of the maximum index */
    public int getMaxLineCount() {
        return getLineCount(maximumIndex);
    }

    /** @return the line count of the chosen index */
    public int getChosenLineCount() {
        return getLineCount(chosenIndex);
    }

    /**
     * @param i the posibilities list index
     * @return the line count
     */
    public int getLineCount(int i) {
        return ((Possibility)possibilitiesList.get(i)).getLineCount();
    }

    /** @return the demerits of the chosen index */
    public double getChosenDemerits() {
        return getDemerits(chosenIndex);
    }

    /**
     * @param i the posibilities list index
     * @return the demerits
     */
    public double getDemerits(int i) {
        return ((Possibility)possibilitiesList.get(i)).getDemerits();
    }

    /** @return the possibilities count */
    public int getPossibilitiesNumber() {
        return possibilitiesList.size();
    }

    /**
     * @param i the break position index
     * @return the chosen position
     */
    public LineLayoutManager.LineBreakPosition getChosenPosition(int i) {
        return ((Possibility)possibilitiesList.get(chosenIndex)).getBreakPosition(i);
    }

    /**
     * @param adj the adjustment
     * @return the adjustment or zero
     */
    public int applyLineCountAdjustment(int adj) {
        if (adj >= (getMinLineCount() - getChosenLineCount())
            && adj <= (getMaxLineCount() - getChosenLineCount())
            && getLineCount(chosenIndex + adj) == getChosenLineCount() + adj) {
            chosenIndex += adj;
            LOG.debug("chosenLineCount= " + (getChosenLineCount() - adj) + " adjustment= " + adj
                               + " => chosenLineCount= " + getLineCount(chosenIndex));
            return adj;
        } else {
            // this should not happen!
            LOG.warn("Cannot apply the desired line count adjustment.");
            return 0;
        }
    }

    /** print all */
    public void printAll() {
        System.out.println("++++++++++");
        System.out.println(" " + possibilitiesList.size() + " possibility':");
        for (int i = 0; i < possibilitiesList.size(); i++) {
            System.out.println("   " + ((Possibility)possibilitiesList.get(i)).getLineCount()
                               + (i == optimumIndex ? " *" : "")
                               + (i == minimumIndex ? " -" : "")
                               + (i == maximumIndex ? " +" : ""));
        }
        System.out.println("++++++++++");
    }
}
