
package org.apache.pdfbox.cos;

import java.io.IOException;

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

/**
 * This class represents a PDF object.
 *
 * @author Ben Litchfield
 * 
 */
public class COSObject extends COSBase implements COSUpdateInfo
{
    private COSBase baseObject;
    private long objectNumber;
    private int generationNumber;
    private ICOSParser parser;
    private boolean isDereferenced = false;
    private final COSUpdateState updateState;
    
    private static final Log LOG = LogFactory.getLog(COSObject.class);

    /**
     * Constructor.
     *
     * @param object The object that this encapsulates.
     *
     */
    public COSObject(COSBase object)
    {
        updateState = new COSUpdateState(this);
        baseObject = object;
        isDereferenced = true;
    }

    /**
     * Constructor.
     *
     * @param object The object that this encapsulates.
     * @param objectKey The COSObjectKey of the encapsulated object
     */
    public COSObject(COSBase object, COSObjectKey objectKey)
    {
        this(objectKey, null);
        baseObject = object;
        isDereferenced = true;
    }

    /**
     * Constructor.
     *
     * @param object The object that this encapsulates.
     * @param parser The parser to be used to load the object on demand
     *
     */
    public COSObject(COSBase object, ICOSParser parser)
    {
        updateState = new COSUpdateState(this);
        baseObject = object;
        isDereferenced = object != null;
        this.parser = parser;
    }

    /**
     * Constructor.
     *
     * @param key The object number of the encapsulated object.
     * @param parser The parser to be used to load the object on demand
     *
     */
    public COSObject(COSObjectKey key, ICOSParser parser)
    {
        updateState = new COSUpdateState(this);
        this.parser = parser;
        objectNumber = key.getNumber();
        generationNumber = key.getGeneration();
        setKey(key);
    }

    /**
     * Indicates if the referenced object is present or not.
     * 
     * @return true if the indirect object is dereferenced
     */
    public boolean isObjectNull()
    {
        return baseObject == null;
    }

    /**
     * This will get the object that this object encapsulates.
     *
     * @return The encapsulated object.
     */
    public COSBase getObject()
    {
        if (!isDereferenced && parser != null)
        {
            try
            {
                // mark as dereferenced to avoid endless recursions
                isDereferenced = true;
                baseObject = parser.dereferenceCOSObject(this);
                getUpdateState().dereferenceChild(baseObject);
            }
            catch (IOException e)
            {
                LOG.error("Can't dereference " + this, e);
            }
            finally
            {
                parser = null;
            }
        }
        return baseObject;
    }

    /**
     * Sets the referenced object to COSNull and removes the initially assigned parser.
     */
    public final void setToNull()
    {
        if(baseObject != null)
        {
            getUpdateState().update();
        }
        baseObject = COSNull.NULL;
        parser = null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString()
    {
        return "COSObject{" + objectNumber + ", " + generationNumber + "}";
    }

    /** 
     * Getter for property objectNumber.
     * @return Value of property objectNumber.
     */
    public long getObjectNumber()
    {
        return objectNumber;
    }

    /** 
     * Getter for property generationNumber.
     * @return Value of property generationNumber.
     */
    public int getGenerationNumber()
    {
        return generationNumber;
    }

    /**
     * visitor pattern double dispatch method.
     *
     * @param visitor The object to notify when visiting this object.
     * @throws IOException If an error occurs while visiting this object.
     */
    @Override
    public void accept( ICOSVisitor visitor ) throws IOException
    {
        COSBase object = getObject();
        if (object != null)
        {
            object.accept(visitor);
        }
        else
        {
            COSNull.NULL.accept(visitor);
        }
    }

    /**
     * Returns {@code true}, if the hereby referenced {@link COSBase} has already been parsed and loaded.
     *
     * @return {@code true}, if the hereby referenced {@link COSBase} has already been parsed and loaded.
     */
    public boolean isDereferenced()
    {
        return isDereferenced;
    }
    
    /**
     * Returns the current {@link COSUpdateState} of this {@link COSObject}.
     *
     * @return The current {@link COSUpdateState} of this {@link COSObject}.
     * @see COSUpdateState
     */
    @Override
    public COSUpdateState getUpdateState()
    {
        return updateState;
    }
    
}
