/* ======================================================================
   Parts Copyright 2006 University of Leeds, Oxford University, University of the Highlands and Islands.

   Licensed 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.bodington.servlet.gif;

import java.io.*;
import java.util.*;
import javax.xml.parsers.*;
import org.apache.log4j.Logger;
import org.bodington.xml.ClasspathEntityResolver;
import org.w3c.dom.*;
/**
 *
 * @author  jrm
 */
public class GifDescriptor
{
    private static Logger log = Logger.getLogger(GifDescriptor.class);
    private static Hashtable cache = new Hashtable();
    
    File source, xml_file;
    long last_modified=0L, xml_last_modified=0L;
    
    private GifColourStatement[] statements;
    private byte[] data;
    private int colours;
    private boolean data_cached;
    
    private static final int MAX_BUFFER = 8*1024;
    private static final int MAX_HEADER = 13+(256*3);
    private static DocumentBuilderFactory docbuilderfactory;
    private static org.xml.sax.EntityResolver entity_resolver;
    static
    {
        docbuilderfactory = DocumentBuilderFactory.newInstance();
        docbuilderfactory.setValidating( true );
        entity_resolver = new ClasspathEntityResolver(GifDescriptor.class);
    }
    
    
    /** Creates a new instance of GifDescriptor */
    private GifDescriptor( File source )
    {
        this.source = source;
        xml_file = new File( source.getParent(), source.getName() + "x" );
        statements = null;
    }
    
    public static GifDescriptor getGifDescriptor( File file )
    {
        GifDescriptor gd=null;
        String path;
        try
        {
            path = file.getCanonicalPath();
        }
        catch ( IOException ioe )
        {
            return null;
        }
        
        synchronized( cache )
        {
            gd = (GifDescriptor)cache.get( path );
            if ( gd == null && file.exists() )
            {
                gd = new GifDescriptor( file );
                // only cache it if the GIF file exists
                cache.put( path, gd );
            }
        }
        
        gd.load();
        
        return gd;
    }
    
    private synchronized void load()
    {
        long m = source.lastModified();
        
        if ( xml_file.exists() )
        {
            long xm = xml_file.lastModified();
            if ( xm != xml_last_modified )
            {
                statements = loadStatements();
                xml_last_modified = xm;
            }
        }
        else
            statements = null;
        
        if ( data == null || last_modified != m )
        {
            log.debug( "Loading GIF header. " + source.getAbsolutePath() );
            
            last_modified = m;
            long l = source.length();
            
            data_cached = l <= (long)MAX_BUFFER;
            data = new byte[data_cached?(int)l:MAX_HEADER];
            
            FileInputStream fin=null;
            
            try
            {
                int n;
                
                fin = new FileInputStream( source );
                
                n = fin.read( data );
                if ( n != data.length )
                    data = null;
                
                colours = 2 << (data[10] & 0x7);
                
            }
            catch ( IOException ioex )
            {
                data = null;
                log.error( ioex.getMessage(), ioex );
                data=null;
            }
            finally
            {
                if ( fin != null )
                    try
                    {
                        fin.close();
                    }
                    catch ( Exception e )
                    {
                        log.error( e.getMessage(), e );
                    }
            }
            
        }
    }
    
    
    private GifColourStatement[] loadStatements()
    {
        try
        {
            log.debug( "Loading dynamic GIF XML file. " + xml_file.getAbsolutePath() );
            
            DocumentBuilder docbuilder = docbuilderfactory.newDocumentBuilder();
            docbuilder.setEntityResolver( entity_resolver );
            Document doc =  docbuilder.parse( xml_file );
            
            Element template_element = doc.getDocumentElement();
            if ( !"dynamicgif".equals( template_element.getTagName() ) )
                throw new IOException( "Invalid template file - no dynamicgif element at root." );
            
            NodeList nlist = template_element.getElementsByTagName( "colourtable" );
            if ( nlist.getLength()!=1 )
                throw new IOException( "Invalid template file - must be one colourtable element." );
            
            template_element = (Element)nlist.item( 0 );
            nlist = template_element.getChildNodes();
            Node node;
            int i, n=0;
            for ( i=0; i<nlist.getLength(); i++ )
            {
                node = nlist.item( i );
                if ( node.getNodeType() == Node.ELEMENT_NODE )
                    n++;
            }
            
            if ( n==0 )
                return null;
            
            GifColourStatement[] statements = new GifColourStatement[n];
            n=0;
            String strindex, strcolour, strbool;
            for ( i=0; i<nlist.getLength(); i++ )
            {
                node = nlist.item( i );
                if ( node.getNodeType() == Node.ELEMENT_NODE )
                {
                    template_element = (Element)node;
                    statements[n] = new GifColourStatement();
                    strindex = template_element.getAttribute( "index" );
                    if ( "all".equals( strindex ) )
                        statements[n].index = GifColourStatement.ALL;
                    else
                    {
                        try
                        {
                            statements[n].index = Integer.parseInt( strindex );
                        }
                        catch ( NumberFormatException nfex )
                        {
                        }
                    }
                    strcolour = template_element.getAttribute( "plane" );
                    if ( "background".equals( strcolour ) )
                        statements[n].plane = GifColourStatement.BACKGROUND;
                    else
                        statements[n].plane = GifColourStatement.FOREGROUND;
                    
                    strbool = template_element.getAttribute( "authorcanmodify" );
                    statements[n].author_can_modify = "true".equals( strbool );
                    strbool = template_element.getAttribute( "usercanmodify" );
                    statements[n].user_can_modify = "true".equals( strbool );
                    n++;
                }
            }
            return statements;
            
        }
        catch ( Throwable th )
        {
            log.error( th.getMessage(), th );
            return null;
        }
    }

    public GifColourStatement[] getGifColourStatements()
    {
        return statements;
    }
    
    public int getColourCount()
    {
        return colours;
    }
    
    public byte[] getData()
    {
        return data;
    }
    
    public File getFile()
    {
        return source;
    }
    
    public boolean isDataCached()
    {
        return data_cached;
    }
    
    public long getLastModified()
    {
        if ( last_modified > xml_last_modified )
            return last_modified;
        return xml_last_modified;
    }
}
