
/* ====================================================================
   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.
==================================================================== */

package org.apache.poi.hssf.record.cf;

import java.util.Locale;

import org.apache.poi.hssf.record.RecordInputStream;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.LittleEndian;

/**
 * Font Formatting Block of the Conditional Formatting Rule Record.
 */
public final class FontFormatting implements Cloneable {
    private final byte[] _rawData = new byte[RAW_DATA_SIZE];

    private static final int OFFSET_FONT_NAME = 0;
    private static final int OFFSET_FONT_HEIGHT = 64;
    private static final int OFFSET_FONT_OPTIONS = 68;
    private static final int OFFSET_FONT_WEIGHT = 72;
    private static final int OFFSET_ESCAPEMENT_TYPE = 74;
    private static final int OFFSET_UNDERLINE_TYPE = 76;
    private static final int OFFSET_FONT_COLOR_INDEX = 80;
    private static final int OFFSET_OPTION_FLAGS = 88;
    private static final int OFFSET_ESCAPEMENT_TYPE_MODIFIED = 92;
    private static final int OFFSET_UNDERLINE_TYPE_MODIFIED = 96;
    private static final int OFFSET_FONT_WEIGHT_MODIFIED = 100;
    private static final int OFFSET_NOT_USED1 = 104;
    private static final int OFFSET_NOT_USED2 = 108;
    private static final int OFFSET_NOT_USED3 = 112; // for some reason Excel always writes  0x7FFFFFFF at this offset
    private static final int OFFSET_FONT_FORMATING_END = 116;
    private static final int RAW_DATA_SIZE = 118;


    public final static int  FONT_CELL_HEIGHT_PRESERVED   = 0xFFFFFFFF;

    // FONT OPTIONS MASKS
    private static final BitField posture       = BitFieldFactory.getInstance(0x00000002);
    private static final BitField outline       = BitFieldFactory.getInstance(0x00000008);
    private static final BitField shadow        = BitFieldFactory.getInstance(0x00000010);
    private static final BitField cancellation	= BitFieldFactory.getInstance(0x00000080);

    // OPTION FLAGS MASKS

    private static final BitField styleModified        = BitFieldFactory.getInstance(0x00000002);
    private static final BitField outlineModified      = BitFieldFactory.getInstance(0x00000008);
    private static final BitField shadowModified       = BitFieldFactory.getInstance(0x00000010);
    private static final BitField cancellationModified = BitFieldFactory.getInstance(0x00000080);

    /** Escapement type - None */
    public static final short SS_NONE  = 0;
    /** Escapement type - Superscript */
    public static final short SS_SUPER = 1;
    /** Escapement type - Subscript */
    public static final short SS_SUB   = 2;
    /** Underline type - None */
    public static final byte U_NONE               = 0;
    /** Underline type - Single */
    public static final byte U_SINGLE             = 1;
    /** Underline type - Double */
    public static final byte U_DOUBLE             = 2;
    /** Underline type - Single Accounting */
    public static final byte U_SINGLE_ACCOUNTING  = 0x21;
    /** Underline type - Double Accounting */
    public static final byte U_DOUBLE_ACCOUNTING  = 0x22;
    /** Normal boldness (not bold) */
    private static final short FONT_WEIGHT_NORMAL = 0x190;

    /**
     * Bold boldness (bold)
     */
    private static final short FONT_WEIGHT_BOLD	 = 0x2bc;

    public FontFormatting() {
        setFontHeight(-1);
        setItalic(false);
        setFontWieghtModified(false);
        setOutline(false);
        setShadow(false);
        setStrikeout(false);
        setEscapementType((short)0);
        setUnderlineType((byte)0);
        setFontColorIndex((short)-1);

        setFontStyleModified(false);
        setFontOutlineModified(false);
        setFontShadowModified(false);
        setFontCancellationModified(false);

        setEscapementTypeModified(false);
        setUnderlineTypeModified(false);

        setShort(OFFSET_FONT_NAME, 0);
        setInt(OFFSET_NOT_USED1, 0x00000001);
        setInt(OFFSET_NOT_USED2, 0x00000000);
        setInt(OFFSET_NOT_USED3, 0x7FFFFFFF);// for some reason Excel always writes  0x7FFFFFFF at this offset
        setShort(OFFSET_FONT_FORMATING_END, 0x0001);
    }

    /** Creates new FontFormatting */
    public FontFormatting(RecordInputStream in) {
        for (int i = 0; i < _rawData.length; i++) {
            _rawData[i] = in.readByte();
        }
    }

    private short getShort(int offset) {
        return LittleEndian.getShort( _rawData, offset);
    }
    private void setShort(int offset, int value) {
        LittleEndian.putShort( _rawData, offset, (short)value);
    }
    private int getInt(int offset) {
        return LittleEndian.getInt( _rawData, offset);
    }
    private void setInt(int offset, int value) {
        LittleEndian.putInt( _rawData, offset, value);
    }

    public byte[] getRawRecord()
    {
        return _rawData;
    }

    public int getDataLength() {
        return RAW_DATA_SIZE;
    }

    /**
     * sets the height of the font in 1/20th point units
     *
     *
     * @param height  fontheight (in points/20); or -1 to preserve the cell font height
     */

    public void setFontHeight(int height)
    {
        setInt(OFFSET_FONT_HEIGHT, height);
    }

    /**
     * gets the height of the font in 1/20th point units
     *
     * @return fontheight (in points/20); or -1 if not modified
     */
    public int getFontHeight()
    {
        return getInt(OFFSET_FONT_HEIGHT);
    }

    private void setFontOption(boolean option, BitField field)
    {
        int options = getInt(OFFSET_FONT_OPTIONS);
        options = field.setBoolean(options, option);
        setInt(OFFSET_FONT_OPTIONS, options);
    }

    private boolean getFontOption(BitField field)
    {
        int options = getInt(OFFSET_FONT_OPTIONS);
        return field.isSet(options);
    }

    /**
     * set the font to be italics or not
     *
     * @param italic - whether the font is italics or not
     * @see #setFontOption(boolean, org.apache.poi.util.BitField)
     */

    public void setItalic(boolean italic)
    {
        setFontOption(italic, posture);
    }

    /**
     * get whether the font is to be italics or not
     *
     * @return italics - whether the font is italics or not
     * @see #getFontOption(org.apache.poi.util.BitField)
     */

    public boolean isItalic()
    {
        return getFontOption(posture);
    }

    public void setOutline(boolean on)
    {
        setFontOption(on, outline);
    }

    public boolean isOutlineOn()
    {
        return getFontOption(outline);
    }

    public void setShadow(boolean on)
    {
        setFontOption(on, shadow);
    }

    public boolean isShadowOn()
    {
        return getFontOption(shadow);
    }

    /**
     * set the font to be stricken out or not
     *
     * @param strike - whether the font is stricken out or not
     */
    public void setStrikeout(boolean strike)
    {
        setFontOption(strike, cancellation);
    }

    /**
     * get whether the font is to be stricken out or not
     *
     * @return strike - whether the font is stricken out or not
     * @see #getFontOption(org.apache.poi.util.BitField)
     */
    public boolean isStruckout()
    {
        return getFontOption(cancellation);
    }

    /**
     * set the font weight (100-1000dec or 0x64-0x3e8).  Default is
     * 0x190 for normal and 0x2bc for bold
     *
     * @param bw - a number between 100-1000 for the fonts "boldness"
     */

    private void setFontWeight(short pbw)
    {
        short bw = pbw;
        if( bw<100) { bw=100; }
        if( bw>1000){ bw=1000; }
        setShort(OFFSET_FONT_WEIGHT, bw);
    }

    /**
     * set the font weight to bold (weight=700) or to normal(weight=400) boldness.
     *
     * @param bold - set font weight to bold if true; to normal otherwise
     */
    public void setBold(boolean bold)
    {
        setFontWeight(bold?FONT_WEIGHT_BOLD:FONT_WEIGHT_NORMAL);
    }

    /**
     * get the font weight for this font (100-1000dec or 0x64-0x3e8).  Default is
     * 0x190 for normal and 0x2bc for bold
     *
     * @return bw - a number between 100-1000 for the fonts "boldness"
     */

    public short getFontWeight()
    {
        return getShort(OFFSET_FONT_WEIGHT);
    }

    /**
     * get whether the font weight is set to bold or not
     *
     * @return bold - whether the font is bold or not
     */

    public boolean isBold()
    {
        return getFontWeight()==FONT_WEIGHT_BOLD;
    }

    /**
     * get the type of super or subscript for the font
     *
     * @return super or subscript option
     * @see org.apache.poi.hssf.usermodel.HSSFFontFormatting#SS_NONE
     * @see org.apache.poi.hssf.usermodel.HSSFFontFormatting#SS_SUPER
     * @see org.apache.poi.hssf.usermodel.HSSFFontFormatting#SS_SUB
     */
    public short getEscapementType()
    {
        return getShort(OFFSET_ESCAPEMENT_TYPE);
    }

    /**
     * set the escapement type for the font
     *
     * @param escapementType  super or subscript option
     * @see org.apache.poi.hssf.usermodel.HSSFFontFormatting#SS_NONE
     * @see org.apache.poi.hssf.usermodel.HSSFFontFormatting#SS_SUPER
     * @see org.apache.poi.hssf.usermodel.HSSFFontFormatting#SS_SUB
     */
    public void setEscapementType( short escapementType)
    {
        setShort(OFFSET_ESCAPEMENT_TYPE, escapementType);
    }

    /**
     * get the type of underlining for the font
     *
     * @return font underlining type
     *
     * @see org.apache.poi.hssf.usermodel.HSSFFontFormatting#U_NONE
     * @see org.apache.poi.hssf.usermodel.HSSFFontFormatting#U_SINGLE
     * @see org.apache.poi.hssf.usermodel.HSSFFontFormatting#U_DOUBLE
     * @see org.apache.poi.hssf.usermodel.HSSFFontFormatting#U_SINGLE_ACCOUNTING
     * @see org.apache.poi.hssf.usermodel.HSSFFontFormatting#U_DOUBLE_ACCOUNTING
     */
    public short getUnderlineType()
    {
        return getShort(OFFSET_UNDERLINE_TYPE);
    }

    /**
     * set the type of underlining type for the font
     *
     * @param underlineType underline option
     *
     * @see org.apache.poi.hssf.usermodel.HSSFFontFormatting#U_NONE
     * @see org.apache.poi.hssf.usermodel.HSSFFontFormatting#U_SINGLE
     * @see org.apache.poi.hssf.usermodel.HSSFFontFormatting#U_DOUBLE
     * @see org.apache.poi.hssf.usermodel.HSSFFontFormatting#U_SINGLE_ACCOUNTING
     * @see org.apache.poi.hssf.usermodel.HSSFFontFormatting#U_DOUBLE_ACCOUNTING
     */
    public void setUnderlineType( short underlineType)
    {
        setShort(OFFSET_UNDERLINE_TYPE, underlineType);
    }


    public short getFontColorIndex()
    {
        return (short)getInt(OFFSET_FONT_COLOR_INDEX);
    }

    public void setFontColorIndex(short fci )
    {
        setInt(OFFSET_FONT_COLOR_INDEX,fci);
    }

    private boolean getOptionFlag(BitField field) {
        int optionFlags = getInt(OFFSET_OPTION_FLAGS);
        int value = field.getValue(optionFlags);
        return value==0? true : false ;
    }

    private void setOptionFlag(boolean modified, BitField field)
    {
        int value = modified? 0 : 1;
        int optionFlags = getInt(OFFSET_OPTION_FLAGS);
        optionFlags = field.setValue(optionFlags, value);
        setInt(OFFSET_OPTION_FLAGS, optionFlags);
    }


    public boolean isFontStyleModified()
    {
        return getOptionFlag(styleModified);
    }


    public void setFontStyleModified(boolean modified)
    {
        setOptionFlag(modified, styleModified);
    }

    public boolean isFontOutlineModified()
    {
        return getOptionFlag(outlineModified);
    }

    public void setFontOutlineModified(boolean modified)
    {
        setOptionFlag(modified, outlineModified);
    }

    public boolean isFontShadowModified()
    {
        return getOptionFlag(shadowModified);
    }

    public void setFontShadowModified(boolean modified)
    {
        setOptionFlag(modified, shadowModified);
    }
    public void setFontCancellationModified(boolean modified)
    {
        setOptionFlag(modified, cancellationModified);
    }

    public boolean isFontCancellationModified()
    {
        return getOptionFlag(cancellationModified);
    }

    public void setEscapementTypeModified(boolean modified)
    {
        int value = modified? 0 : 1;
        setInt(OFFSET_ESCAPEMENT_TYPE_MODIFIED, value);
    }
    public boolean isEscapementTypeModified()
    {
        int escapementModified = getInt(OFFSET_ESCAPEMENT_TYPE_MODIFIED);
        return escapementModified == 0;
    }

    public void setUnderlineTypeModified(boolean modified)
    {
        int value = modified? 0 : 1;
        setInt(OFFSET_UNDERLINE_TYPE_MODIFIED, value);
    }

    public boolean isUnderlineTypeModified()
    {
        int underlineModified = getInt(OFFSET_UNDERLINE_TYPE_MODIFIED);
        return underlineModified == 0;
    }

    public void setFontWieghtModified(boolean modified)
    {
        int value = modified? 0 : 1;
        setInt(OFFSET_FONT_WEIGHT_MODIFIED, value);
    }

    public boolean isFontWeightModified()
    {
        int fontStyleModified = getInt(OFFSET_FONT_WEIGHT_MODIFIED);
        return fontStyleModified == 0;
    }

    public String toString()
    {
        StringBuilder buffer = new StringBuilder();
        buffer.append("    [Font Formatting]\n");

        buffer.append("	.font height = ").append(getFontHeight()).append(" twips\n");

        if( isFontStyleModified() )
        {
            buffer.append("	.font posture = ").append(isItalic()?"Italic":"Normal").append("\n");
        }
        else
        {
            buffer.append("	.font posture = ]not modified]").append("\n");
        }

        if( isFontOutlineModified() )
        {
            buffer.append("	.font outline = ").append(isOutlineOn()).append("\n");
        }
        else
        {
            buffer.append("	.font outline is not modified\n");
        }

        if( isFontShadowModified() )
        {
            buffer.append("	.font shadow = ").append(isShadowOn()).append("\n");
        }
        else
        {
            buffer.append("	.font shadow is not modified\n");
        }

        if( isFontCancellationModified() )
        {
            buffer.append("	.font strikeout = ").append(isStruckout()).append("\n");
        }
        else
        {
            buffer.append("	.font strikeout is not modified\n");
        }

        if( isFontStyleModified() )
        {
            buffer.append("	.font weight = ").
            append(getFontWeight()).
            append(
                    getFontWeight() == FONT_WEIGHT_NORMAL ? "(Normal)"
                    : getFontWeight() == FONT_WEIGHT_BOLD ? "(Bold)" 
                    : "0x"+Integer.toHexString(getFontWeight())).
            append("\n");
        }
        else
        {
            buffer.append("	.font weight = ]not modified]").append("\n");
        }

        if( isEscapementTypeModified() )
        {
            buffer.append("	.escapement type = ").append(getEscapementType()).append("\n");
        }
        else
        {
            buffer.append("	.escapement type is not modified\n");
        }

        if( isUnderlineTypeModified() )
        {
            buffer.append("	.underline type = ").append(getUnderlineType()).append("\n");
        }
        else
        {
            buffer.append("	.underline type is not modified\n");
        }
        buffer.append("	.color index = ").append("0x")
                .append(Integer.toHexString(getFontColorIndex()).toUpperCase(Locale.ROOT)).append('\n');

        buffer.append("    [/Font Formatting]\n");
        return buffer.toString();
    }

    @Override
    public FontFormatting clone() {
        FontFormatting other = new FontFormatting();
        System.arraycopy(_rawData, 0, other._rawData, 0, _rawData.length);
        return other;
    }
}
