/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/
package com.sun.star.report.pentaho.output;

import com.sun.star.report.pentaho.OfficeNamespaces;
import com.sun.star.report.pentaho.model.DataStyle;
import com.sun.star.report.pentaho.model.FontFaceDeclsSection;
import com.sun.star.report.pentaho.model.FontFaceElement;
import com.sun.star.report.pentaho.model.OfficeStyle;
import com.sun.star.report.pentaho.model.OfficeStyles;
import com.sun.star.report.pentaho.model.OfficeStylesCollection;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;

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

import org.jfree.report.ReportProcessingException;
import org.jfree.report.structure.Element;
import org.jfree.report.structure.Section;
import org.jfree.report.util.AttributeNameGenerator;


/**
 * Todo: Document me!
 *
 * @author Thomas Morgner
 * @since 13.03.2007
 */
public class StyleUtilities
{

    private static final Log LOGGER = LogFactory.getLog(StyleUtilities.class);
    private static final String STYLE = "style";

    private StyleUtilities()
    {
    }

    /**
     * Copies the specififed style (keyed by its family and name) into the current styles collection. This copies the
     * style and all inherited styles into the target collection. Inherited common styles will be always be added to the
     * common collection (which will be written into the 'styles.xml' later).
     * <p/>
     * This method does nothing if the specified style already exists in the styles collection.
     *
     * @param styleFamily      the family of the style to copy
     * @param styleName        the unique name of the style.
     * @param stylesCollection the current styles collection
     * @param commonCollection the global styles collection
     * @param predefCollection the predefined styles from where to copy the styles.
     * @throws ReportProcessingException if the style copying failed.
     */
    public static void copyStyle(final String styleFamily,
            final String styleName,
            final OfficeStylesCollection stylesCollection,
            final OfficeStylesCollection commonCollection,
            final OfficeStylesCollection predefCollection)
            throws ReportProcessingException
    {
        copyStyle(styleFamily, styleName, stylesCollection,
                commonCollection, predefCollection, new HashSet());
    }

    /**
     * Copies the specififed style (keyed by its family and name) into the current styles collection. This copies the
     * style and all inherited styles into the target collection. Inherited common styles will be always be added to the
     * common collection (which will be written into the 'styles.xml' later).
     * <p/>
     * This method does nothing if the specified style already exists in the styles collection.
     *
     * @param styleFamily        the family of the style to copy
     * @param styleName          the unique name of the style.
     * @param stylesCollection   the current styles collection
     * @param commonCollection   the global styles collection
     * @param predefCollection   the predefined styles from where to copy the styles.
     * @param inheritanceTracker a collection of all styles that have been touched. This is used to prevent infinite
     *                           loops and duplicates.
     * @throws ReportProcessingException if the style copying failed.
     */
    private static void copyStyle(final String styleFamily,
            final String styleName,
            final OfficeStylesCollection stylesCollection,
            final OfficeStylesCollection commonCollection,
            final OfficeStylesCollection predefCollection,
            final Set inheritanceTracker)
            throws ReportProcessingException
    {
        if (inheritanceTracker.contains(styleName))
        {
            return;
        }
        inheritanceTracker.add(styleName);

        if (stylesCollection.containsStyle(styleFamily, styleName) || commonCollection.getCommonStyles().containsStyle(styleFamily, styleName))
        {
            // fine, there's already a copy of the stylesheet.
            return;
        }

        final OfficeStyle predefCommonStyle =
                predefCollection.getCommonStyles().getStyle(styleFamily, styleName);
        if (predefCommonStyle != null)
        {
            // so we have an style from the predefined collection.
            // copy it an add it to the current stylescollection
            final OfficeStyles commonStyles = commonCollection.getCommonStyles();

            copyStyleInternal(predefCommonStyle, commonStyles, stylesCollection,
                    commonCollection, predefCollection, styleFamily, inheritanceTracker);
            return;
        }

        final OfficeStyle predefAutoStyle =
                predefCollection.getAutomaticStyles().getStyle(styleFamily, styleName);
        if (predefAutoStyle != null)
        {
            // so we have an style from the predefined collection.
            // copy it an add it to the current stylescollection
            final OfficeStyles autoStyles = stylesCollection.getAutomaticStyles();
            copyStyleInternal(predefAutoStyle, autoStyles, stylesCollection,
                    commonCollection, predefCollection, styleFamily, inheritanceTracker);
            return;
        }

        // There is no automatic style either. Now this means that someone
        // messed up the fileformat. Lets create a new empty style for this.
        final OfficeStyle autostyle = new OfficeStyle();
        autostyle.setNamespace(OfficeNamespaces.STYLE_NS);
        autostyle.setType(STYLE);
        autostyle.setStyleFamily(styleFamily);
        autostyle.setStyleName(styleName);

        final OfficeStyles autoStyles = stylesCollection.getAutomaticStyles();
        autoStyles.addStyle(autostyle);
    }

    private static OfficeStyle copyStyleInternal(
            final OfficeStyle predefCommonStyle,
            final OfficeStyles styles,
            final OfficeStylesCollection stylesCollection,
            final OfficeStylesCollection commonCollection,
            final OfficeStylesCollection predefCollection,
            final String styleFamily,
            final Set inheritanceTracker)
            throws ReportProcessingException
    {
        try
        {
            final OfficeStyle preStyle = (OfficeStyle) predefCommonStyle.clone();
            styles.addStyle(preStyle);
            performFontFaceProcessing(preStyle, stylesCollection, predefCollection);
            performDataStyleProcessing(preStyle, stylesCollection, predefCollection);

            // Lookup the parent style ..
            final String styleParent = preStyle.getStyleParent();
            final OfficeStyle inherited =
                    stylesCollection.getStyle(styleFamily, styleParent);
            if (inherited != null)
            {
                // OK, recurse (and hope that we dont run into an infinite loop) ..
                copyStyle(styleFamily, styleParent, stylesCollection,
                        commonCollection, predefCollection, inheritanceTracker);
            }
            else if (styleParent != null)
            {
                LOGGER.warn("Inconsistent styles: " + styleFamily + ":" + styleParent + " does not exist.");
            }
            return preStyle;
        }
        catch (CloneNotSupportedException e)
        {
            throw new ReportProcessingException("Failed to derive a stylesheet", e);
        }
    }

    private static void performFontFaceProcessing(final OfficeStyle style,
            final OfficeStylesCollection stylesCollection,
            final OfficeStylesCollection predefCollection)
            throws ReportProcessingException
    {
        final Element textProperties = style.getTextProperties();
        if (textProperties == null)
        {
            return;
        }

        try
        {
            final FontFaceDeclsSection currentFonts = stylesCollection.getFontFaceDecls();
            final FontFaceDeclsSection predefFonts = predefCollection.getFontFaceDecls();

            final String fontName = (String) textProperties.getAttribute(OfficeNamespaces.STYLE_NS, "font-name");
            if (fontName != null && !currentFonts.containsFont(fontName))
            {
                final FontFaceElement element = predefFonts.getFontFace(fontName);
                if (element != null)
                {
                    currentFonts.addFontFace((FontFaceElement) element.clone());
                }
            }

            final String fontNameAsian = (String) textProperties.getAttribute(OfficeNamespaces.STYLE_NS,
                    "font-name-asian");
            if (fontNameAsian != null && !currentFonts.containsFont(fontNameAsian))
            {
                final FontFaceElement element = predefFonts.getFontFace(
                        fontNameAsian);
                if (element != null)
                {
                    currentFonts.addFontFace((FontFaceElement) element.clone());
                }
            }

            final String fontNameComplex = (String) textProperties.getAttribute(OfficeNamespaces.STYLE_NS,
                    "font-name-complex");
            if (fontNameComplex != null && !currentFonts.containsFont(fontNameComplex))
            {
                final FontFaceElement element = predefFonts.getFontFace(
                        fontNameComplex);
                if (element != null)
                {
                    currentFonts.addFontFace((FontFaceElement) element.clone());
                }
            }
        }
        catch (CloneNotSupportedException e)
        {
            throw new ReportProcessingException("Failed to clone font-face element");
        }
    }

    private static void performDataStyleProcessing(final OfficeStyle style,
            final OfficeStylesCollection stylesCollection,
            final OfficeStylesCollection predefCollection)
            throws ReportProcessingException
    {
        final Section derivedStyle = performDataStyleProcessing(style, stylesCollection, predefCollection, "data-style-name");
        if (derivedStyle != null)
        {
            try
            {
                final Section styleMap = (Section) derivedStyle.findFirstChild(OfficeNamespaces.STYLE_NS, "map");
                if (styleMap != null)
                {
                    performDataStyleProcessing(styleMap, stylesCollection, predefCollection, "apply-style-name");
                }
            }
            catch (Exception e)
            {
            }
        }
    }

    private static Section performDataStyleProcessing(final Section style,
            final OfficeStylesCollection stylesCollection,
            final OfficeStylesCollection predefCollection,
            final String attributeName)
            throws ReportProcessingException
    {
        final Object attribute = style.getAttribute(OfficeNamespaces.STYLE_NS, attributeName);
        final DataStyle derivedStyle;
        if (attribute != null)
        {
            final String styleName = String.valueOf(attribute);
            if (!stylesCollection.getAutomaticStyles().containsDataStyle(styleName) && !stylesCollection.getCommonStyles().containsDataStyle(styleName))
            {
                try
                {
                    final OfficeStyles automaticStyles = predefCollection.getAutomaticStyles();
                    final DataStyle autoDataStyle = automaticStyles.getDataStyle(styleName);
                    if (autoDataStyle != null)
                    {
                        derivedStyle = (DataStyle) autoDataStyle.clone();
                        stylesCollection.getAutomaticStyles().addDataStyle(derivedStyle);
                    }
                    else
                    {
                        final OfficeStyles commonStyles = predefCollection.getCommonStyles();
                        final DataStyle commonDataStyle = commonStyles.getDataStyle(styleName);
                        if (commonDataStyle != null)
                        {
                            derivedStyle = (DataStyle) commonDataStyle.clone();
                            stylesCollection.getCommonStyles().addDataStyle(derivedStyle);
                        }
                        else
                        {
                            LOGGER.warn("Dangling data style: " + styleName);
                            derivedStyle = null;
                        }
                    }
                }
                catch (CloneNotSupportedException e)
                {
                    throw new ReportProcessingException("Failed to copy style. This should not have happened.");
                }
            }
            else
            {
                derivedStyle = null;
            }
        }
        else
        {
            derivedStyle = null;
        }
        return derivedStyle;
    }

    /**
     * Derives the named style. If the style is a common style, a new automatic style is generated and inserted into the
     * given stylesCollection. If the named style is an automatic style, the style is copied and inserted as new automatic
     * style.
     * <p/>
     * After the style has been created, the style's inheritance hierarchy will be copied as well.
     * <p/>
     * If there is no style with the given name and family, a new empty automatic style will be created.
     *
     * @param styleFamily      the family of the style to copy
     * @param styleName        the unique name of the style.
     * @param stylesCollection the current styles collection
     * @param commonCollection the global styles collection
     * @param predefCollection the predefined styles from where to copy the styles.
     * @param generator        the style-name-generator of the current report-target
     * @return the derived style instance.
     * @throws ReportProcessingException if the style copying failed.
     */
    public static OfficeStyle deriveStyle(final String styleFamily,
            final String styleName,
            final OfficeStylesCollection stylesCollection,
            final OfficeStylesCollection commonCollection,
            final OfficeStylesCollection predefCollection,
            final AttributeNameGenerator generator)
            throws ReportProcessingException
    {
        if (styleFamily == null)
        {
            throw new NullPointerException("StyleFamily must not be null");
        }
        if (styleName != null)
        {

            final OfficeStyle currentAuto =
                    stylesCollection.getAutomaticStyles().getStyle(styleFamily,
                    styleName);
            if (currentAuto != null)
            {
                // handle an automatic style ..
                final OfficeStyle derivedStyle =
                        deriveAutomaticStyle(currentAuto, styleFamily, styleName,
                        generator, commonCollection, predefCollection);
                stylesCollection.getAutomaticStyles().addStyle(derivedStyle);
                return derivedStyle;
            }

            final OfficeStyle currentCommon =
                    stylesCollection.getCommonStyles().getStyle(styleFamily, styleName);
            if (currentCommon != null)
            {
                // handle an common style ..
                final OfficeStyle derivedStyle =
                        deriveCommonStyle(currentCommon, styleFamily, styleName,
                        generator, commonCollection, predefCollection);
                stylesCollection.getAutomaticStyles().addStyle(derivedStyle);
                return derivedStyle;
            }

//      final OfficeStyle commonAuto =
//          commonCollection.getAutomaticStyles().getStyle(styleFamily,
//              styleName);
//      if (commonAuto != null)
//      {
//        // handle an automatic style ..
//        final OfficeStyle derivedStyle =
//            deriveAutomaticStyle(commonAuto, styleFamily, styleName,
//                generator, commonCollection, predefCollection);
//        stylesCollection.getAutomaticStyles().addStyle(derivedStyle);
//        return derivedStyle;
//      }

            final OfficeStyle commonCommon =
                    commonCollection.getCommonStyles().getStyle(styleFamily, styleName);
            if (commonCommon != null)
            {
                // handle an common style ..
                final OfficeStyle derivedStyle =
                        deriveCommonStyle(commonCommon, styleFamily, styleName,
                        generator, commonCollection, predefCollection);
                stylesCollection.getAutomaticStyles().addStyle(derivedStyle);
                return derivedStyle;
            }

            final OfficeStyle predefAuto =
                    predefCollection.getAutomaticStyles().getStyle(styleFamily,
                    styleName);
            if (predefAuto != null)
            {
                // handle an automatic style ..
                final OfficeStyle derivedStyle =
                        deriveAutomaticStyle(predefAuto, styleFamily, styleName,
                        generator, commonCollection, predefCollection);
                stylesCollection.getAutomaticStyles().addStyle(derivedStyle);
                return derivedStyle;
            }

            final OfficeStyle predefCommon =
                    predefCollection.getCommonStyles().getStyle(styleFamily, styleName);
            if (predefCommon != null)
            {
                // handle an common style ..
                final OfficeStyle derivedStyle =
                        deriveCommonStyle(predefCommon, styleFamily, styleName,
                        generator, commonCollection, predefCollection);
                stylesCollection.getAutomaticStyles().addStyle(derivedStyle);
                return derivedStyle;
            }
        }

        // No such style. Create a new one ..
        final OfficeStyle autostyle = new OfficeStyle();
        autostyle.setNamespace(OfficeNamespaces.STYLE_NS);
        autostyle.setType(STYLE);
        autostyle.setStyleFamily(styleFamily);
        if (styleName != null)
        {
            autostyle.setStyleName(styleName);
        }
        else
        {
            autostyle.setStyleName(generator.generateName("derived_anonymous"));
        }

        final OfficeStyles autoStyles = stylesCollection.getAutomaticStyles();
        autoStyles.addStyle(autostyle);
        return autostyle;
    }

    private static OfficeStyle deriveCommonStyle(final OfficeStyle commonStyle,
            final String styleFamily,
            final String styleName,
            final AttributeNameGenerator nameGenerator,
            final OfficeStylesCollection commonCollection,
            final OfficeStylesCollection predefCollection)
            throws ReportProcessingException
    {
        final OfficeStyle autostyle = new OfficeStyle();
        autostyle.setNamespace(OfficeNamespaces.STYLE_NS);
        autostyle.setType(STYLE);
        autostyle.setStyleFamily(styleFamily);
        autostyle.setStyleName(nameGenerator.generateName("derived_" + styleName));
        autostyle.setStyleParent(styleName);

        // now copy the common style ..
        final OfficeStyles commonStyles = commonCollection.getCommonStyles();
        if (!commonStyles.containsStyle(styleFamily, styleName))
        {
            copyStyleInternal(commonStyle, commonStyles,
                    commonCollection, commonCollection, predefCollection,
                    styleFamily, new HashSet());
        }
        return autostyle;
    }

    private static OfficeStyle deriveAutomaticStyle(final OfficeStyle commonStyle,
            final String styleFamily,
            final String styleName,
            final AttributeNameGenerator nameGenerator,
            final OfficeStylesCollection commonCollection,
            final OfficeStylesCollection predefCollection)
            throws ReportProcessingException
    {
        try
        {
            final OfficeStyle autostyle = (OfficeStyle) commonStyle.clone();
            autostyle.setNamespace(OfficeNamespaces.STYLE_NS);
            autostyle.setType(STYLE);
            autostyle.setStyleFamily(styleFamily);
            autostyle.setStyleName(nameGenerator.generateName("derived_auto_" + styleName));


            final String parent = autostyle.getStyleParent();
            if (parent != null)
            {
                copyStyle(styleFamily, parent, commonCollection, commonCollection,
                        predefCollection);
            }
            return autostyle;
        }
        catch (CloneNotSupportedException e)
        {
            throw new ReportProcessingException(
                    "Deriving the style failed. Clone error: ", e);
        }
    }

    public static String queryStyle(final OfficeStylesCollection predefCollection,
            final String styleFamily,
            final String styleName,
            final String sectionName,
            final String propertyNamespace,
            final String propertyName)
    {
        return queryStyle(predefCollection, styleFamily,
                styleName, sectionName, propertyNamespace, propertyName, new HashSet());
    }

    public static OfficeStyle queryStyleByProperties(final OfficeStylesCollection predefCollection,
            final String styleFamily,
            final String sectionName,
            final ArrayList propertyNamespace,
            final ArrayList propertyName,
            final ArrayList propertyValues)
    {
        if (propertyNamespace.size() != propertyName.size())
        {
            return null;
        }
        final OfficeStyle[] styles = predefCollection.getAutomaticStyles().getAllStyles();
        for (int i = 0; i < styles.length; i++)
        {
            final OfficeStyle officeStyle = styles[i];
            if (officeStyle.getStyleFamily().equals(styleFamily))
            {
                final Element section = officeStyle.findFirstChild(OfficeNamespaces.STYLE_NS, sectionName);
                if (section != null)
                {
                    int j = 0;
                    for (; j < propertyNamespace.size(); j++)
                    {
                        final String ns = (String) propertyNamespace.get(j);
                        final String prop = (String) propertyName.get(j);
                        final Object obj = section.getAttribute(ns, prop);
                        final Object value = propertyValues.get(j);
                        if (obj == null || value == null)
                        {
                            continue;
                        }
                        if (!propertyValues.get(j).equals(obj))
                        {
                            break;
                        }
                    }
                    if (j == propertyName.size())
                    {
                        return officeStyle;
                    }
                }
            }
        }
        return null;
    }

    private static String queryStyle(final OfficeStylesCollection predefCollection,
            final String styleFamily,
            final String styleName,
            final String sectionName,
            final String propertyNamespace,
            final String propertyName,
            final Set seenStyles)
    {
        if (seenStyles.contains(styleName))
        {
            return null;
        }
        seenStyles.add(styleName);

        final OfficeStyle style = predefCollection.getStyle(styleFamily, styleName);
        if (style == null)
        {
            return null; // no such style

        }
        final Element section = style.findFirstChild(OfficeNamespaces.STYLE_NS, sectionName);
        if (section != null)
        {
            final Object attribute = section.getAttribute(propertyNamespace, propertyName);
            if (attribute != null)
            {
                return String.valueOf(attribute);
            }
        }
        final String parent = style.getStyleParent();
        if (parent == null)
        {
            return null;
        }
        return queryStyle(predefCollection, styleFamily, parent, sectionName, propertyNamespace, propertyName, seenStyles);
    }
}
