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

import org.apache.poi.hwpf.model.ListTables;

import org.apache.poi.util.Internal;

import org.apache.poi.hwpf.model.LFO;
import org.apache.poi.hwpf.model.LFOData;
import org.apache.poi.hwpf.model.ListData;
import org.apache.poi.hwpf.model.ListFormatOverrideLevel;
import org.apache.poi.hwpf.model.ListLevel;
import org.apache.poi.hwpf.model.StyleSheet;
import org.apache.poi.hwpf.sprm.CharacterSprmCompressor;
import org.apache.poi.hwpf.sprm.ParagraphSprmCompressor;
import org.apache.poi.util.Removal;

/**
 * This class is used to create a list in a Word document. It is used in
 * conjunction with
 * {@link org.apache.poi.hwpf.HWPFDocument#registerList(HWPFList) registerList}
 * in {@link org.apache.poi.hwpf.HWPFDocument HWPFDocument}.
 *
 * In Word, lists are not ranged entities, meaning you can't actually add one to
 * the document. Lists only act as properties for list entries. Once you
 * register a list, you can add list entries to a document that are a part of
 * the list.
 *
 * The only benefit of this that I see, is that you can add a list entry
 * anywhere in the document and continue numbering from the previous list.
 */
public final class HWPFList
{
    private boolean _ignoreLogicalLeftIdentation;
    private LFO _lfo;
    private LFOData _lfoData;
    private ListData _listData;
    private ListTables _listTables;
    private StyleSheet _styleSheet;

    /**
     *
     * @param numbered
     *            true if the list should be numbered; false if it should be
     *            bulleted.
     * @param styleSheet
     *            The document's stylesheet.
     */
    public HWPFList( boolean numbered, StyleSheet styleSheet )
    {
        _listData = new ListData(
                (int) ( Math.random() * System.currentTimeMillis() ), numbered );
        _lfo = new LFO();
        _lfo.setLsid( _listData.getLsid() );
        _lfoData = new LFOData();
        _styleSheet = styleSheet;
    }

    public HWPFList( StyleSheet styleSheet, ListTables listTables, int ilfo )
    {
        _listTables = listTables;
        _styleSheet = styleSheet;

        /* See documentation for sprmPIlfo (0x460B) */
        if ( ilfo == 0 || ilfo == 0xF801 )
        {
            throw new IllegalArgumentException( "Paragraph not in list" );
        }
        else if ( 0x0001 <= ilfo && ilfo <= 0x07FE )
        {
            _lfo = listTables.getLfo( ilfo );
            _lfoData = listTables.getLfoData( ilfo );
        }
        else if ( 0xF802 <= ilfo && ilfo <= 0xFFFF )
        {
            int nilfo = ilfo ^ 0xFFFF;
            _lfo = listTables.getLfo( nilfo );
            _lfoData = listTables.getLfoData( nilfo );
            _ignoreLogicalLeftIdentation = true;
        }
        else
        {
            throw new IllegalArgumentException( "Incorrect ilfo: " + ilfo );
        }

        _listData = listTables.getListData( _lfo.getLsid() );
    }

    @Internal
    public LFO getLFO()
    {
        return _lfo;
    }

    @Internal
    public LFOData getLFOData()
    {
        return _lfoData;
    }

    @Internal
    public ListData getListData()
    {
        return _listData;
    }

    public int getLsid()
    {
        return _lfo.getLsid();
    }

    @Internal
    ListLevel getLVL( char level )
    {
        if ( level >= _listData.numLevels() )
        {
            throw new IllegalArgumentException( "Required level "
                    + ( (int) level )
                    + " is more than number of level for list ("
                    + _listData.numLevels() + ")" );
        }
        return _listData.getLevels()[level];
    }

    /**
     * An MSONFC, as specified in [MS-OSHARED] section 2.2.1.3, that specifies
     * the format of the level numbers that replace the placeholders for this
     * level in the xst fields of the LVLs in this list. This value MUST not be
     * equal to 0x08, 0x09, 0x0F, or 0x13. If this is equal to 0xFF or 0x17,
     * this level does not have a number sequence and therefore has no number
     * formatting. If this is equal to 0x17, the level uses bullets.
     */
    public int getNumberFormat( char level )
    {
        return getLVL( level ).getNumberFormat();
    }

    public String getNumberText( char level )
    {
        return getLVL( level ).getNumberText();
    }

    public int getStartAt( char level )
    {
        if ( isStartAtOverridden( level ) )
        {
            return _lfoData.getRgLfoLvl()[level].getIStartAt();
        }

        return getLVL( level ).getStartAt();
    }

    /**
     * "The type of character following the number text for the paragraph: 0 == tab, 1 == space, 2 == nothing."
     */
    public byte getTypeOfCharFollowingTheNumber( char level )
    {
        return getLVL( level ).getTypeOfCharFollowingTheNumber();
    }

    public boolean isIgnoreLogicalLeftIdentation()
    {
        return _ignoreLogicalLeftIdentation;
    }

    /**
     * @since POI 5.2.4
     */
    public boolean isStartAtOverridden(char level )
    {
        ListFormatOverrideLevel lfolvl = _lfoData.getRgLfoLvl().length > level ? _lfoData
                .getRgLfoLvl()[level] : null;

        return lfolvl != null && lfolvl.getIStartAt() != 0
                && !lfolvl.isFormatting();
    }

    /**
     * @deprecated use {@link #isStartAtOverridden(char)}
     */
    @Removal(version = "POI 6.0.0")
    public boolean isStartAtOverriden(char level )
    {
        ListFormatOverrideLevel lfolvl = _lfoData.getRgLfoLvl().length > level ? _lfoData
                .getRgLfoLvl()[level] : null;

        return lfolvl != null && lfolvl.getIStartAt() != 0
                && !lfolvl.isFormatting();
    }

    public void setIgnoreLogicalLeftIdentation(
            boolean ignoreLogicalLeftIdentation )
    {
        this._ignoreLogicalLeftIdentation = ignoreLogicalLeftIdentation;
    }

    /**
     * Sets the character properties of the list numbers.
     *
     * @param level
     *            the level number that the properties should apply to.
     * @param chp
     *            The character properties.
     */
    public void setLevelNumberProperties( int level, CharacterProperties chp )
    {
        ListLevel listLevel = _listData.getLevel( level );
        int styleIndex = _listData.getLevelStyle( level );
        CharacterProperties base = _styleSheet.getCharacterStyle( styleIndex );

        byte[] grpprl = CharacterSprmCompressor.compressCharacterProperty( chp, base );
        listLevel.setNumberProperties( grpprl );
    }

    /**
     * Sets the paragraph properties for a particular level of the list.
     *
     * @param level
     *            The level number.
     * @param pap
     *            The paragraph properties
     */
    public void setLevelParagraphProperties( int level, ParagraphProperties pap )
    {
        ListLevel listLevel = _listData.getLevel( level );
        int styleIndex = _listData.getLevelStyle( level );
        ParagraphProperties base = _styleSheet.getParagraphStyle( styleIndex );

        byte[] grpprl = ParagraphSprmCompressor.compressParagraphProperty( pap, base );
        listLevel.setLevelProperties( grpprl );
    }

    public void setLevelStyle( int level, int styleIndex )
    {
        _listData.setLevelStyle( level, styleIndex );
    }
}
