/*
 * ProcessedGifServlet.java
 *
 * Created on 20 August 2003, 10:13
 */

package org.bodington.servlet;

import javax.servlet.http.*;
import javax.servlet.*;
import java.io.*;
import java.awt.Color;
import java.awt.color.*;

import java.util.Enumeration;
import java.util.StringTokenizer;

import org.bodington.servlet.gif.*;
import org.bodington.util.ColourPreferenceMapper;

/**
 *
 * @author  jrm
 */
public class ProcessedGifServlet extends javax.servlet.http.HttpServlet
{
    
    /** Creates a new instance of ProcessedGifServlet */
    public ProcessedGifServlet()
    {
    }
    
    public void init() throws ServletException
    {
        super.init();
    }
    
    public void doGet( HttpServletRequest req, HttpServletResponse resp )
    throws ServletException, IOException
    {
        doPost( req, resp );
    }
    
    
    public void doPost( HttpServletRequest req, HttpServletResponse res )
    throws ServletException, IOException
    {
        String path_info = req.getPathInfo();
        String path_translated = req.getPathTranslated();
        String code;
        
        if ( !path_info.startsWith( "templates" ) && !path_info.startsWith( "/templates" ) )
        {
            res.sendError( 403, "No access to files in requested area." );
            return;
        }
        
        if ( path_info.indexOf( ".." ) >= 0 )
        {
            res.sendError( 403, "Use of '..' in URLs is not allowed in requested area." );
            return;
        }
        
        File file = new File( path_translated );

        if ( !path_translated.endsWith( ".gif" ) )
        {
            res.sendError( 403, "Access in this area is restricted to GIF files only." );
            return;
        }

        if ( !file.exists() || !file.isFile() )
        {
            res.sendError( 404, "GIF file not found." );
            return;
        }
        
        
        GifDescriptor gd = GifDescriptor.getGifDescriptor( file );
        
        code = req.getParameter( "code" );
        ColourPreferenceMapper mapper = null;
        if ( code != null && code.length()>0 )
            mapper = new ColourPreferenceMapper( code );

        // at this point the servlet will either forward to the plain file
        // or programatically output a modified GIF.
        
        if ( mapper == null || gd.getGifColourStatements() == null )
            forwardToOriginal( req, res );
        else
            sendProcessedGif( req, res, mapper, gd );
        }
        
    public void forwardToOriginal( HttpServletRequest req, HttpServletResponse res )
    throws ServletException, IOException
    {
        String path_info = req.getPathInfo();
        ServletContext context=getServletContext();
        RequestDispatcher dispatcher=context.getRequestDispatcher( path_info );
        
        if ( dispatcher==null )
        {
            res.sendError( 500, "Technical problem fetching file - couldn't find dispatcher." );
            return;
        }
        
        // will it set up all the appropriate headers including those for cache control?
        dispatcher.forward( req, res );
    }
    
    public void sendProcessedGif( HttpServletRequest req, HttpServletResponse resp, ColourPreferenceMapper mapper, GifDescriptor gd )
    throws ServletException, IOException
    {
        Response res = new Response( resp );
        
        long last_modified = gd.getLastModified();
        // HTTP header doesn't allow milliseconds so we should do
        // timestamp comparisons on rounded figures
        last_modified = (last_modified/1000)*1000;
        
        long if_mod_since_header = req.getDateHeader( "If-Modified-Since" );
        
        res.setDateHeader( "Last-Modified", last_modified );
        // a given GIF with given colour options will only go out of date if
        // a replacement GIF file or XML descriptor is applied.  In the Bodington
        // templates area this is quite rare so its OK not to include Expires header.
        //res.setDateHeader( "Expires", System.currentTimeMillis() + (1000 * 60 * 10) );
        res.setContentType( "image/gif" );
        
        if ( last_modified <= if_mod_since_header  )
        {
            res.setStatus( res.SC_NOT_MODIFIED );
            return;
        }
        
        ServletOutputStream out = res.getOutputStream();
        // output first part of header unmodified
        int count = gd.getColourCount();
        byte[] data = gd.getData();
        GifColourStatement[] statements = gd.getGifColourStatements();

        out.write( data, 0, 13 );

        if ( statements == null || statements.length == 0 )
            out.write( data, 13, 3*count );
        else
            sendProcessedGifColourTable( req, out, mapper, gd );


        if ( gd.isDataCached() )
            out.write( data, 13 + (count*3), data.length - (13 + (count*3)) );
        else
        {
            int n;
            FileInputStream fin = new FileInputStream( gd.getFile() );
            byte[] buffer = new byte[16*1024];
            fin.read( buffer, 0, 13 + (count*3) );

            while ( (n = fin.read( buffer, 0,  16*1024 ))> 0 )
                out.write( buffer, 0, n );
        }

        out.close();
    }
    
    public void sendProcessedGifColourTable( HttpServletRequest req, OutputStream out, ColourPreferenceMapper colour_mapper, GifDescriptor gd )
        throws java.io.IOException
    {
        Color b = colour_mapper.getReferenceBackgroundColor();
        Color f = colour_mapper.getReferenceForegroundColor();
        Color preferred_foreground = colour_mapper.getPreferredForegroundColor( f );
        Color preferred_background = colour_mapper.getPreferredBackgroundColor();
        Color active, gif_colour;
        
	int i, j, first, last;
        int count = gd.getColourCount();
        byte[] data = gd.getData();
	byte[] colourbuf = new byte[count*3];
        GifColourStatement[] statements = gd.getGifColourStatements();
	System.arraycopy( data, 13, colourbuf, 0, count*3 );
	
	for ( i=0; i<statements.length; i++ )
	{
	    if ( statements[i].index < GifColourStatement.ALL )
		continue;
	    if ( statements[i].index >= count )
		continue;
	    
            if ( !statements[i].author_can_modify && !statements[i].user_can_modify )
                continue;
            
	    if ( statements[i].index == GifColourStatement.ALL )
	    {
		first = 0;
		last = count-1;
	    }
	    else
	    {
		first = statements[i].index;
		last = statements[i].index;
	    }
	    
	    if ( statements[i].user_can_modify )
	    {
		active = preferred_foreground;
		if ( statements[i].plane == GifColourStatement.BACKGROUND )
		    active = preferred_background;
	    }
	    else
	    {
		active = f;
		if ( statements[i].plane == GifColourStatement.BACKGROUND )
		    active = b;
	    }
	    
	    for ( j=first; j<=last; j++ )
	    {
                // if the author can't change the colour but the user cam,
                // then we have to adjust the actual GIF colour entry 
                // against the reference background.  This could preserve
                // colour GIFs.  (Bit of a fudge because the reference
                // background is provided by the author but we assume that
                // it reflects the actual page background rather than the GIF
                // background and so is a good reference for deciding whether
                // the user wants the foreground adjusted.)
                if ( !statements[i].author_can_modify && statements[i].plane != GifColourStatement.BACKGROUND )
                {
                    gif_colour = new Color( 
                        ((int)colourbuf[(3*j)  ]) & 0xff, 
                        ((int)colourbuf[(3*j)+1]) & 0xff,
                        ((int)colourbuf[(3*j)+2]) & 0xff   );
                    active = colour_mapper.getPreferredForegroundColor( gif_colour );
                }
		colourbuf[(3*j)  ] = (byte)active.getRed();
		colourbuf[(3*j)+1] = (byte)active.getGreen();
		colourbuf[(3*j)+2] = (byte)active.getBlue();
	    }
	}

	out.write( colourbuf, 0, 3*count );
	
    }
    
    
}
