/*
 * 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.fo.flow.table;

import org.apache.fop.layoutmgr.table.CollapsingBorderModel;

/**
 * A class that holds the three possible values for a border-before/after on a table-cell,
 * in the collapsing model. These three values are (for border-before, similar for
 * border-after):
 * <ul>
 * <li>normal: common case, when a cell follows the cell before on a same page;</li>
 * <li>leading: when the table is broken and the cell appears at the top of a page, in
 * which case its border must be resolved with the header (or the top of the table)
 * instead of with the previous cell;</li>
 * <li>rest: when a cell is broken over several pages; same as leading but with
 * conditionality taken into account.</li>
 * </ul>
 */
public class ConditionalBorder {

    /** normal border */
    public static final int NORMAL = 0;

    /** leading and trailing border */
    public static final int LEADING_TRAILING = 1;

    /** all the rest */
    public static final int REST = 2;

    /** Normal case, no break. */
    BorderSpecification normal;

    /** Special case: the cell is at the top or the bottom of the page. */
    BorderSpecification leadingTrailing;

    /** Special case: break inside the cell. */
    BorderSpecification rest;

    /** The model used to resolve borders. */
    private CollapsingBorderModel collapsingBorderModel;

    private ConditionalBorder(BorderSpecification normal,
            BorderSpecification leadingTrailing, BorderSpecification rest,
            CollapsingBorderModel collapsingBorderModel) {
        assert collapsingBorderModel != null;
        this.normal = normal;
        this.leadingTrailing = leadingTrailing;
        this.rest = rest;
        this.collapsingBorderModel = collapsingBorderModel;
    }

    /**
     * Creates a new conditional border.
     *
     * @param borderSpecification the border specification to take as a basis
     * @param collapsingBorderModel the model that will be used to resolved borders
     */
    ConditionalBorder(BorderSpecification borderSpecification,
            CollapsingBorderModel collapsingBorderModel) {
        this (borderSpecification, borderSpecification,
               borderSpecification.getBorderInfo().getWidth().isDiscard()
                 ? BorderSpecification.getDefaultBorder() : borderSpecification,
               collapsingBorderModel);
    }

    /**
     * Resolves and updates the relevant parts of this border as well as the given one.
     *
     * @param competitor
     * @param withNormal
     * @param withLeadingTrailing
     * @param withRest
     */
    void resolve(ConditionalBorder competitor, boolean withNormal,
            boolean withLeadingTrailing, boolean withRest) {
        if (withNormal) {
            BorderSpecification resolvedBorder = collapsingBorderModel.determineWinner(
                    normal, competitor.normal);
            if (resolvedBorder != null) {
                normal = resolvedBorder;
                competitor.normal = resolvedBorder;
            }
        }
        if (withLeadingTrailing) {
            BorderSpecification resolvedBorder = collapsingBorderModel.determineWinner(
                    leadingTrailing, competitor.leadingTrailing);
            if (resolvedBorder != null) {
                leadingTrailing = resolvedBorder;
                competitor.leadingTrailing = resolvedBorder;
            }
        }
        if (withRest) {
            BorderSpecification resolvedBorder = collapsingBorderModel.determineWinner(rest,
                    competitor.rest);
            if (resolvedBorder != null) {
                rest = resolvedBorder;
                competitor.rest = resolvedBorder;
            }
        }
    }

    /**
     * Integrates the given segment in this border. Unlike for
     * {@link #integrateSegment(ConditionalBorder, boolean, boolean, boolean)}, this
     * method nicely handles the case where the CollapsingBorderModel returns null, by
     * keeping the components to their old values.
     *
     * @param competitor
     * @param withNormal
     * @param withLeadingTrailing
     * @param withRest
     */
    void integrateCompetingSegment(ConditionalBorder competitor, boolean withNormal,
            boolean withLeadingTrailing, boolean withRest) {
        if (withNormal) {
            BorderSpecification resolvedBorder = collapsingBorderModel.determineWinner(
                    normal, competitor.normal);
            if (resolvedBorder != null) {
                normal = resolvedBorder;
            }
        }
        if (withLeadingTrailing) {
            BorderSpecification resolvedBorder = collapsingBorderModel.determineWinner(
                    leadingTrailing, competitor.leadingTrailing);
            if (resolvedBorder != null) {
                leadingTrailing = resolvedBorder;
            }
        }
        if (withRest) {
            BorderSpecification resolvedBorder = collapsingBorderModel.determineWinner(rest,
                    competitor.rest);
            if (resolvedBorder != null) {
                rest = resolvedBorder;
            }
        }
    }

    /**
     * Updates this border after taking into account the given segment. The
     * CollapsingBorderModel is not expected to return null.
     *
     * @param segment
     * @param withNormal
     * @param withLeadingTrailing
     * @param withRest
     */
    void integrateSegment(ConditionalBorder segment, boolean withNormal,
            boolean withLeadingTrailing, boolean withRest) {
        if (withNormal) {
            normal = collapsingBorderModel.determineWinner(normal, segment.normal);
            assert normal != null;
        }
        if (withLeadingTrailing) {
            leadingTrailing = collapsingBorderModel.determineWinner(leadingTrailing,
                    segment.leadingTrailing);
            assert leadingTrailing != null;
        }
        if (withRest) {
            rest = collapsingBorderModel.determineWinner(rest, segment.rest);
            assert rest != null;
        }
    }

    /**
     * Returns a shallow copy of this border.
     *
     * @return a copy of this border
     */
    ConditionalBorder copy() {
        return new ConditionalBorder(normal, leadingTrailing, rest, collapsingBorderModel);
    }

    /** {@inheritDoc} */
    public String toString() {
        return "{normal: " + normal + ", leading: " + leadingTrailing + ", rest: " + rest + "}";
    }

    /**
     * Returns a default border specification.
     *
     * @param collapsingBorderModel the model that will be used to resolve borders
     * @return a border with style 'none' for all of the three components
     */
    static ConditionalBorder getDefaultBorder(CollapsingBorderModel collapsingBorderModel) {
        BorderSpecification defaultBorderSpec = BorderSpecification.getDefaultBorder();
        return new ConditionalBorder(defaultBorderSpec, defaultBorderSpec, defaultBorderSpec,
                collapsingBorderModel);
    }
}
