/*
 * 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.pdfbox.util;

import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSString;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSNumber;

import org.apache.pdfbox.pdmodel.common.COSArrayList;
import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpaceFactory;

import java.io.IOException;
import java.util.List;
import java.util.Map;

/**
 * This contains all of the image parameters for in inlined image.
 *
 * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
 * @version $Revision: 1.4 $
 */
public class ImageParameters
{
    private COSDictionary dictionary;

    /**
     * Constructor.
     */
    public ImageParameters()
    {
        dictionary = new COSDictionary();
    }

    /**
     * Constructor.
     *
     * @param params The image parameters.
     */
    public ImageParameters( COSDictionary params )
    {
        dictionary = params;
    }

    /**
     * This will get the dictionary that stores the image parameters.
     *
     * @return The COS dictionary that stores the image parameters.
     */
    public COSDictionary getDictionary()
    {
        return dictionary;
    }

    private COSBase getCOSObject( COSName abbreviatedName, COSName name )
    {
        COSBase retval = dictionary.getDictionaryObject( abbreviatedName );
        if( retval == null )
        {
            retval = dictionary.getDictionaryObject( name );
        }
        return retval;
    }

    private int getNumberOrNegativeOne( COSName abbreviatedName, COSName name )
    {
        int retval = -1;
        COSNumber number = (COSNumber)getCOSObject( abbreviatedName, name );
        if( number != null )
        {
            retval = number.intValue();
        }
        return retval;
    }

    /**
     * The bits per component of this image.  This will return -1 if one has not
     * been set.
     *
     * @return The number of bits per component.
     */
    public int getBitsPerComponent()
    {
        return getNumberOrNegativeOne( COSName.BPC, COSName.BITS_PER_COMPONENT );
    }

    /**
     * Set the number of bits per component.
     *
     * @param bpc The number of bits per component.
     */
    public void setBitsPerComponent( int bpc )
    {
        dictionary.setInt( COSName.BPC, bpc );
    }


    /**
     * This will get the color space or null if none exists.
     *
     * @return The color space for this image.
     *
     * @throws IOException If there is an error getting the colorspace.
     */
    public PDColorSpace getColorSpace() throws IOException
    {
        return getColorSpace( null );
    }
    
    /**
     * This will get the color space or null if none exists.
     *
     * @param colorSpaces The ColorSpace dictionary from the current resources, if any.
     *
     * @return The color space for this image.
     *
     * @throws IOException If there is an error getting the colorspace.
     */
    public PDColorSpace getColorSpace( Map colorSpaces ) throws IOException
    {
        COSBase cs = getCOSObject( COSName.CS, COSName.COLORSPACE );
        PDColorSpace retval = null;
        if( cs != null )
        {
            retval = createColorSpace( cs, colorSpaces );
        }
        return retval;
    }

    /**
     * This will create the correct color space for the given name.
     * This method is specific for parsing the color space specified within the content stream of inline image.
     *
     * @param cs the object denoting the color space (the name or array)
     * @param colorSpaces The ColorSpace dictionary from the current resources, if any.
     *
     * @return The color space.
     * @throws IOException If the color space name is unknown.
     */
    private PDColorSpace createColorSpace(COSBase cs, Map colorSpaces) throws IOException
    {
        if (cs instanceof COSName)
        {
            return PDColorSpaceFactory.createColorSpace( toLongName(cs), colorSpaces );
        }

        // In InlinedImage only DeviceGray/RGB/CMYK and restricted Indexed color spaces are allowed.
        if (cs instanceof COSArray && ((COSArray) cs).size() == 4 && ((COSArray) cs).get(3) instanceof COSString)
        {
            COSArray srcArray = (COSArray) cs;
            COSBase csType = srcArray.get(0);
            if (COSName.I.equals(csType) || COSName.INDEXED.equals(csType))
            {
                COSArray dstArray = new COSArray();
                dstArray.addAll(srcArray);
                dstArray.set(0, COSName.INDEXED);
                dstArray.set(1, toLongName(srcArray.get(1)));
                return PDColorSpaceFactory.createColorSpace( dstArray, colorSpaces );
            }

            throw new IOException("Illegal type of inline image color space: " + csType);
        }

        throw new IOException("Illegal type of object for inline image color space: " + cs);
    }

    /**
     * Deliver the long name of a device colorspace, or the parameter
     *
     * @param cs the color space name object
     * @return
     */
    private COSBase toLongName(COSBase cs)
    {
        if (COSName.getPDFName("RGB").equals(cs))
        {
            return COSName.DEVICERGB;
        }
        if (COSName.getPDFName("CMYK").equals(cs))
        {
            return COSName.DEVICECMYK;
        }
        if (COSName.getPDFName("G").equals(cs))
        {
            return COSName.DEVICEGRAY;
        }
        return cs;
    }

    /**
     * This will set the color space for this image.
     *
     * @param cs The color space for this image.
     */
    public void setColorSpace( PDColorSpace cs )
    {
        COSBase base = null;
        if( cs != null )
        {
            base = cs.getCOSObject();
        }
        dictionary.setItem( COSName.CS, base );
    }

    /**
     * The height of this image.  This will return -1 if one has not
     * been set.
     *
     * @return The height.
     */
    public int getHeight()
    {
        return getNumberOrNegativeOne( COSName.H, COSName.HEIGHT );
    }

    /**
     * Set the height of the image.
     *
     * @param h The height of the image.
     */
    public void setHeight( int h )
    {
        dictionary.setInt( COSName.H, h );
    }

    /**
     * The width of this image.  This will return -1 if one has not
     * been set.
     *
     * @return The width.
     */
    public int getWidth()
    {
        return getNumberOrNegativeOne( COSName.W, COSName.WIDTH );
    }

    /**
     * Set the width of the image.
     *
     * @param w The width of the image.
     */
    public void setWidth( int w )
    {
        dictionary.setInt( COSName.W, w );
    }

    /**
     * This will get the list of filters that are associated with this stream.  Or
     * null if there are none.
     * @return A list of all encoding filters to apply to this stream.
     */
    public List getFilters()
    {
        List retval = null;
        COSBase filters = dictionary.getDictionaryObject( new String[] {"Filter", "F"} );
        if( filters instanceof COSName )
        {
            COSName name = (COSName)filters;
            retval = new COSArrayList( name.getName(), name, dictionary, COSName.FILTER );
        }
        else if( filters instanceof COSArray )
        {
            retval = COSArrayList.convertCOSNameCOSArrayToList( (COSArray)filters );
        }
        return retval;
    }

    /**
     * This will set the filters that are part of this stream.
     *
     * @param filters The filters that are part of this stream.
     */
    public void setFilters( List filters )
    {
        COSBase obj = COSArrayList.convertStringListToCOSNameCOSArray( filters );
        dictionary.setItem( "Filter", obj );
    }
    
    /**
     * Returns true if the image is a stencil mask.
     *
     * @return true if the image is a stencil mask.
     */
    public boolean isStencil()
    {
        return dictionary.getBoolean(COSName.IM, COSName.IMAGE_MASK, false);
    }
    
    /**
     * Sets whether or not the image is a stencil.
     * This corresponds to the {@code ImageMask} entry in the image stream's dictionary.
     * @param isStencil True to make the image a stencil.
     */
    public void setStencil(boolean isStencil)
    {
        dictionary.setBoolean(COSName.IM, isStencil);
    }    
    
}