/*
 * 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: TextAttributesConverter.java 1761019 2016-09-16 10:43:45Z ssteiner $ */

package org.apache.fop.render.rtf;

import java.awt.Color;

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

import org.apache.fop.apps.FOPException;
import org.apache.fop.datatypes.Length;
import org.apache.fop.datatypes.PercentBaseContext;
import org.apache.fop.fo.Constants;
import org.apache.fop.fo.FONode;
import org.apache.fop.fo.FOText;
import org.apache.fop.fo.flow.Block;
import org.apache.fop.fo.flow.BlockContainer;
import org.apache.fop.fo.flow.Inline;
import org.apache.fop.fo.flow.Leader;
import org.apache.fop.fo.flow.PageNumber;
import org.apache.fop.fo.flow.table.TableCell;
import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
import org.apache.fop.fo.properties.CommonFont;
import org.apache.fop.fo.properties.CommonMarginBlock;
import org.apache.fop.fo.properties.CommonTextDecoration;
import org.apache.fop.fo.properties.PercentLength;
import org.apache.fop.render.rtf.rtflib.rtfdoc.IBorderAttributes;
import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfAttributes;
import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfColorTable;
import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfFontManager;
import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfLeader;
import org.apache.fop.render.rtf.rtflib.rtfdoc.RtfText;

/**
 * <p>Converts FO properties to RtfAttributes.</p>
 *
 * <p>This work was authored by Bertrand Delacretaz (bdelacretaz@codeconsult.ch),
 * Boris Poudérous (boris.pouderous@eads-telecom.com),
 * Peter Herweg (pherweg@web.de),
 * Normand Massé,
 * Christopher Scott (scottc@westinghouse.com), and
 * Roberto Marra (roberto@link-u.com).</p>
 */
final class TextAttributesConverter {

    private static Log log = LogFactory.getLog(TextAttributesConverter.class);

    /**
     * Constructor is private, because it's just a utility class.
     */
    private TextAttributesConverter() {
    }

    /**
     * Converts all known text FO properties to RtfAttributes
     * @param fobj the FO for which the attributes are to be converted
     */
    public static RtfAttributes convertAttributes(Block fobj)
                throws FOPException {
        FOPRtfAttributes attrib = new FOPRtfAttributes();
        attrFont(fobj.getCommonFont(), attrib);
        attrFontColor(fobj.getColor(), attrib);
        //attrTextDecoration(fobj.getTextDecoration(), attrib);
        attrBlockBackgroundColor(fobj.getCommonBorderPaddingBackground(), attrib);
        attrBlockMargin(fobj.getCommonMarginBlock(), attrib);
        attrBlockTextAlign(fobj.getTextAlign(), attrib);
        attrBorder(fobj.getCommonBorderPaddingBackground(), attrib, fobj);
        attrBreak(fobj, attrib);

        return attrib;
    }

    private static void attrBreak(Block fobj, FOPRtfAttributes attrib) {
        int breakValue = fobj.getBreakBefore();
        if (breakValue != Constants.EN_AUTO) {
            //"sect" Creates a new section and a page break,
            //a simple page break with control word "page" caused
            //some problems
            boolean bHasTableCellParent = false;
            FONode f = fobj;
            while (f.getParent() != null) {
                f = f.getParent();
                if (f instanceof TableCell) {
                    bHasTableCellParent = true;
                    break;
                }
            }
            if (!bHasTableCellParent) {
                attrib.set("sect");
                switch (breakValue) {
                case Constants.EN_EVEN_PAGE:
                    attrib.set("sbkeven");
                    break;
                case Constants.EN_ODD_PAGE:
                    attrib.set("sbkodd");
                    break;
                case Constants.EN_COLUMN:
                    attrib.set("sbkcol");
                    break;
                default:
                    attrib.set("sbkpage");
                }
            } else {
                log.warn("Cannot create break-before for a block inside a table.");
            }
        }
        //Break after is handled in RtfCloseGroupMark
    }

    /**
     * Converts all known text FO properties to RtfAttributes
     * @param fobj FObj whose properties are to be converted
     */
    public static RtfAttributes convertBlockContainerAttributes(BlockContainer fobj)
                throws FOPException {
        FOPRtfAttributes attrib = new FOPRtfAttributes();
        attrBackgroundColor(fobj.getCommonBorderPaddingBackground(), attrib);
        attrBlockMargin(fobj.getCommonMarginBlock(), attrib);
        //attrBlockDimension(fobj, attrib);
        attrBorder(fobj.getCommonBorderPaddingBackground(), attrib, fobj);

        return attrib;
    }

    /**
     * Converts all character related FO properties to RtfAttributes.
     * @param fobj FObj whose properties are to be converted
     */
    public static RtfAttributes convertCharacterAttributes(
            FOText fobj) throws FOPException {

        FOPRtfAttributes attrib = new FOPRtfAttributes();
        attrFont(fobj.getCommonFont(), attrib);
        attrFontColor(fobj.getColor(), attrib);
        attrTextDecoration(fobj.getTextDecoration(), attrib);
        attrBaseLineShift(fobj.getBaseLineShift(), attrib);
        return attrib;
    }

    /**
     * Converts all character related FO properties to RtfAttributes.
     * @param fobj FObj whose properties are to be converted
     */
    public static RtfAttributes convertCharacterAttributes(
            PageNumber fobj) throws FOPException {

        FOPRtfAttributes attrib = new FOPRtfAttributes();
        attrFont(fobj.getCommonFont(), attrib);
        attrTextDecoration(fobj.getTextDecoration(), attrib);
        attrBackgroundColor(fobj.getCommonBorderPaddingBackground(), attrib);
        return attrib;
    }

    /**
     * Converts all character related FO properties to RtfAttributes.
     * @param fobj FObj whose properties are to be converted
     */
    public static RtfAttributes convertCharacterAttributes(
            Inline fobj) throws FOPException {

        FOPRtfAttributes attrib = new FOPRtfAttributes();
        attrFont(fobj.getCommonFont(), attrib);
        attrFontColor(fobj.getColor(), attrib);

        attrBackgroundColor(fobj.getCommonBorderPaddingBackground(), attrib);
        attrInlineBorder(fobj.getCommonBorderPaddingBackground(), attrib);
        return attrib;
    }


    /**
     * Converts FO properties used by RtfLeader to RtfAttributes.
     * @param fobj Leader
     * @param context PercentBaseContext
     * @return RtfAttributes
     * @throws FOPException
     */
    public static RtfAttributes convertLeaderAttributes(Leader fobj, PercentBaseContext context)
                throws FOPException {
        boolean tab = false;
        FOPRtfAttributes attrib = new FOPRtfAttributes();
        attrib.set(RtfText.ATTR_FONT_FAMILY,
        RtfFontManager.getInstance().getFontNumber(fobj.getCommonFont().getFirstFontFamily()));

        if (fobj.getLeaderLength() != null) {
            attrib.set(RtfLeader.LEADER_WIDTH, convertMptToTwips(fobj.getLeaderLength().getMaximum(
                    context).getLength().getValue(context)));

            if (fobj.getLeaderLength().getMaximum(context) instanceof PercentLength) {
                if (((PercentLength)fobj.getLeaderLength().getMaximum(context)).getString().equals(
                            "100.0%")) {
                    // Use Tab instead of white spaces
                    attrib.set(RtfLeader.LEADER_USETAB, 1);
                    tab = true;
                }
            }
        }

        attrFontColor(fobj.getColor(), attrib);

        /* if (fobj.getLeaderPatternWidth() != null) {
            //TODO calculate pattern width not possible for white spaces, because its using
            //underlines for tab it would work with LEADER_PATTERN_WIDTH (expndtw)
        } */

        switch(fobj.getLeaderPattern()) {
        case Constants.EN_DOTS:
            if (tab) {
                attrib.set(RtfLeader.LEADER_TABLEAD, RtfLeader.LEADER_TAB_DOTTED);
            } else {
                attrib.set(RtfLeader.LEADER_TABLEAD, RtfLeader.LEADER_DOTTED);
            }
            break;
        case Constants.EN_SPACE:
            //nothing has to be set for spaces
            break;
        case Constants.EN_RULE:
            //Things like start-indent, space-after, ... not supported?
            //Leader class does not offer these properties
            //TODO aggregate them with the leader width or
            // create a second - blank leader - before

            if (fobj.getRuleThickness() != null) {
                //TODO See inside RtfLeader, better calculation for
                //white spaces would be necessary
                //attrib.set(RtfLeader.LEADER_RULE_THICKNESS,
                //    fobj.getRuleThickness().getValue(context));
                log.warn("RTF: fo:leader rule-thickness not supported");
            }

            switch (fobj.getRuleStyle()) {
            case Constants.EN_SOLID:
                if (tab) {
                    attrib.set(RtfLeader.LEADER_TABLEAD, RtfLeader.LEADER_TAB_THICK);
                } else {
                    attrib.set(RtfLeader.LEADER_TABLEAD, RtfLeader.LEADER_THICK);
                }
                break;
            case Constants.EN_DASHED:
                if (tab) {
                    attrib.set(RtfLeader.LEADER_TABLEAD, RtfLeader.LEADER_TAB_MIDDLEDOTTED);
                } else {
                    attrib.set(RtfLeader.LEADER_TABLEAD, RtfLeader.LEADER_MIDDLEDOTTED);
                }
                break;
            case Constants.EN_DOTTED:
                if (tab) {
                    attrib.set(RtfLeader.LEADER_TABLEAD, RtfLeader.LEADER_TAB_DOTTED);
                } else {
                    attrib.set(RtfLeader.LEADER_TABLEAD, RtfLeader.LEADER_DOTTED);
                }
                break;
            case Constants.EN_DOUBLE:
                if (tab) {
                    attrib.set(RtfLeader.LEADER_TABLEAD, RtfLeader.LEADER_TAB_EQUAL);
                } else {
                    attrib.set(RtfLeader.LEADER_TABLEAD, RtfLeader.LEADER_EQUAL);
                }
                break;
            case Constants.EN_GROOVE:
                if (tab) {
                    attrib.set(RtfLeader.LEADER_TABLEAD, RtfLeader.LEADER_TAB_HYPHENS);
                } else {
                    attrib.set(RtfLeader.LEADER_TABLEAD, RtfLeader.LEADER_HYPHENS);
                }
                break;
            case Constants.EN_RIDGE:
                if (tab) {
                    attrib.set(RtfLeader.LEADER_TABLEAD, RtfLeader.LEADER_TAB_UNDERLINE);
                } else {
                    attrib.set(RtfLeader.LEADER_TABLEAD, RtfLeader.LEADER_UNDERLINE);
                }
                break;
            default:
                break;
            }
            break;
        case Constants.EN_USECONTENT:
            log.warn("RTF: fo:leader use-content not supported");
            break;
        default:
            break;
        }

        if (fobj.getLeaderAlignment() == Constants.EN_REFERENCE_AREA) {
            log.warn("RTF: fo:leader reference-area not supported");
        }
        return attrib;
    }

    private static int convertMptToTwips(int mpt) {
        return Math.round(FoUnitsConverter.getInstance().convertMptToTwips(mpt));
    }

    private static void attrFont(CommonFont font, FOPRtfAttributes rtfAttr) {
        rtfAttr.set(RtfText.ATTR_FONT_FAMILY,
                RtfFontManager.getInstance().getFontNumber(font.getFirstFontFamily()));
        rtfAttr.setHalfPoints(RtfText.ATTR_FONT_SIZE, font.fontSize);

        if (font.getFontWeight() == Constants.EN_700
                || font.getFontWeight() == Constants.EN_800
                || font.getFontWeight() == Constants.EN_900) {
            //Everything from 700 and above is declared as bold
            rtfAttr.set("b", 1);
        } else {
            rtfAttr.set("b", 0);
        }

        if (font.getFontStyle() == Constants.EN_ITALIC) {
            rtfAttr.set(RtfText.ATTR_ITALIC, 1);
        } else {
            rtfAttr.set(RtfText.ATTR_ITALIC, 0);
        }


    }


    private static void attrFontColor(Color colorType, RtfAttributes rtfAttr) {
        // Cell background color
        if (colorType != null) {
           if (colorType.getAlpha() != 0
                    || colorType.getRed() != 0
                    || colorType.getGreen() != 0
                    || colorType.getBlue() != 0) {
                rtfAttr.set(RtfText.ATTR_FONT_COLOR,
                        convertFOPColorToRTF(colorType));
            }
        }
    }



    private static void attrTextDecoration(CommonTextDecoration textDecoration,
                RtfAttributes rtfAttr) {
        if (textDecoration == null) {
            rtfAttr.set(RtfText.ATTR_UNDERLINE, 0);
            rtfAttr.set(RtfText.ATTR_STRIKETHROUGH, 0);
            return;
        }

        if (textDecoration.hasUnderline()) {
            rtfAttr.set(RtfText.ATTR_UNDERLINE, 1);
        } else {
            rtfAttr.set(RtfText.ATTR_UNDERLINE, 0);
        }

        if (textDecoration.hasLineThrough()) {
            rtfAttr.set(RtfText.ATTR_STRIKETHROUGH, 1);
        } else {
            rtfAttr.set(RtfText.ATTR_STRIKETHROUGH, 0);
        }
    }

    private static void attrBlockMargin(CommonMarginBlock cmb, FOPRtfAttributes rtfAttr) {
        rtfAttr.setTwips(RtfText.SPACE_BEFORE,
                cmb.spaceBefore.getOptimum(null).getLength());
        rtfAttr.setTwips(RtfText.SPACE_AFTER,
                cmb.spaceAfter.getOptimum(null).getLength());
        rtfAttr.setTwips(RtfText.LEFT_INDENT_BODY, cmb.startIndent);
        rtfAttr.setTwips(RtfText.RIGHT_INDENT_BODY, cmb.endIndent);
    }


    /*
    private static void attrBlockDimension(FObj fobj, FOPRtfAttributes rtfAttr) {
        Length ipd = fobj.getProperty(Constants.PR_INLINE_PROGRESSION_DIMENSION)
                .getLengthRange().getOptimum().getLength();
        if (ipd.getEnum() != Constants.EN_AUTO) {
            rtfAttr.set(RtfText.FRAME_WIDTH, ipd);
        }
        Length bpd = fobj.getProperty(Constants.PR_BLOCK_PROGRESSION_DIMENSION)
                .getLengthRange().getOptimum().getLength();
        if (bpd.getEnum() != Constants.EN_AUTO) {
            rtfAttr.set(RtfText.FRAME_HEIGHT, bpd);
        }
    }
    */

    private static void attrBlockTextAlign(int alignment, RtfAttributes rtfAttr) {
        String rtfValue = null;
        switch (alignment) {
            case Constants.EN_CENTER:
                rtfValue = RtfText.ALIGN_CENTER;
                break;
            case Constants.EN_END:
                rtfValue = RtfText.ALIGN_RIGHT;
                break;
            case Constants.EN_JUSTIFY:
                rtfValue = RtfText.ALIGN_JUSTIFIED;
                break;
            default:
                rtfValue = RtfText.ALIGN_LEFT;
                break;
        }

        rtfAttr.set(rtfValue);
    }

    /**
     * Reads background-color for block from <code>bpb</code> and writes it to
     * <code>rtfAttr</code>.
     */
    private static void attrBlockBackgroundColor(
                CommonBorderPaddingBackground bpb, RtfAttributes rtfAttr) {
        if (bpb.hasBackground()) {
            rtfAttr.set(RtfText.SHADING, RtfText.FULL_SHADING);
            rtfAttr.set(RtfText.SHADING_FRONT_COLOR,
                    convertFOPColorToRTF(bpb.backgroundColor));
        }
    }

    /** Adds border information from <code>bpb</code> to <code>rtrAttr</code>. */
    private static void attrBorder(CommonBorderPaddingBackground bpb,
           RtfAttributes rtfAttr, FONode fobj) {
       if (hasBorder(fobj.getParent())) {
           attrInlineBorder(bpb, rtfAttr);
           return;
       }

       BorderAttributesConverter.makeBorder(bpb,
               CommonBorderPaddingBackground.BEFORE, rtfAttr,
               IBorderAttributes.BORDER_TOP);
       BorderAttributesConverter.makeBorder(bpb,
               CommonBorderPaddingBackground.AFTER, rtfAttr,
               IBorderAttributes.BORDER_BOTTOM);
       BorderAttributesConverter.makeBorder(bpb,
               CommonBorderPaddingBackground.START, rtfAttr,
               IBorderAttributes.BORDER_LEFT);
       BorderAttributesConverter.makeBorder(bpb,
               CommonBorderPaddingBackground.END, rtfAttr,
               IBorderAttributes.BORDER_RIGHT);
    }

    /** @return true, if element <code>node</code> has border. */
    private static boolean hasBorder(FONode node) {
        while (node != null) {
            CommonBorderPaddingBackground commonBorderPaddingBackground = null;
            if (node instanceof Block) {
                Block block = (Block) node;
                commonBorderPaddingBackground = block.getCommonBorderPaddingBackground();
            } else if (node instanceof BlockContainer) {
                BlockContainer container = (BlockContainer) node;
                commonBorderPaddingBackground = container.getCommonBorderPaddingBackground();
            }

            if (commonBorderPaddingBackground != null
                    && commonBorderPaddingBackground.hasBorder()) {
                return true;
            }

            node = node.getParent();
        }
        return false;
    }

    /** Adds inline border information from <code>bpb</code> to <code>rtrAttr</code>. */
    private static void attrInlineBorder(CommonBorderPaddingBackground bpb,
            RtfAttributes rtfAttr) {
        BorderAttributesConverter.makeBorder(bpb,
                CommonBorderPaddingBackground.BEFORE, rtfAttr,
                IBorderAttributes.BORDER_CHARACTER);
    }

    /**
     * Reads background-color from bl and writes it to rtfAttr.
     *
     * @param bpb the CommonBorderPaddingBackground from which the properties are read
     * @param rtfAttr the RtfAttributes object the attributes are written to
     */
    private static void attrBackgroundColor(CommonBorderPaddingBackground bpb,
                RtfAttributes rtfAttr) {
        Color fopValue = bpb.backgroundColor;
        int rtfColor = 0;
        /* FOP uses a default background color of "transparent", which is
           actually a transparent black, which is generally not suitable as a
           default here. Changing FOP's default to "white" causes problems in
           PDF output, so we will look for the default here & change it to
           "auto". */
        if ((fopValue == null)
                || ((fopValue.getRed() == 0)
                && (fopValue.getGreen() == 0)
                && (fopValue.getBlue() == 0)
                && (fopValue.getAlpha() == 0))) {
            return;
        } else {
            rtfColor = convertFOPColorToRTF(fopValue);
        }

        rtfAttr.set(RtfText.ATTR_BACKGROUND_COLOR, rtfColor);
   }

   private static void attrBaseLineShift(Length baselineShift, RtfAttributes rtfAttr) {

       int s = baselineShift.getEnum();

       if (s == Constants.EN_SUPER) {
           rtfAttr.set(RtfText.ATTR_SUPERSCRIPT);
       } else if (s == Constants.EN_SUB) {
           rtfAttr.set(RtfText.ATTR_SUBSCRIPT);
       }
   }

   /**
    * Converts a FOP ColorType to the integer pointing into the RTF color table
    * @param fopColor the ColorType object to be converted
    * @return integer pointing into the RTF color table
    */
   public static int convertFOPColorToRTF(Color fopColor) {
       // TODO: This code is duplicated in FOPRtfAttributesConverter
       int redComponent = fopColor.getRed();
       int greenComponent = fopColor.getGreen();
       int blueComponent = fopColor.getBlue();
       return RtfColorTable.getInstance().getColorNumber(redComponent,
               greenComponent, blueComponent);
   }
}
