package visual.statik.sampled;

import java.awt.*;
//[skeleton1.
import java.awt.color.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.util.*;


/**
 * A class that can be used to construct BufferedImageOp objects that
 * can then be used to operate on static sampled visual content
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class BufferedImageOpFactory
{
//]skeleton1.
    private ColorConvertOp   grayOp;
    
//[skeleton2.

    private Hashtable<Convolutions, 
                      Hashtable<Integer, ConvolveOp>> convolutionPools;
//]skeleton2.
    private Hashtable<Integer, GrayExceptOp>  grayExceptPool;    

    private LookupOp   negativeOp, nightVisionOp;
    private RescaleOp  brightenOp, darkenOp, metalOp;
    
//[skeleton3.    

    private static BufferedImageOpFactory     instance = 
                               new BufferedImageOpFactory();
    



    

    /**
     * Default Constructor
     */
    private BufferedImageOpFactory()
    {
       Hashtable<Integer, ConvolveOp>    pool;
       
       // Initialize the pool of ConvolveOp objects
       convolutionPools = new Hashtable<Convolutions, 
                                        Hashtable<Integer, ConvolveOp>>();
       
       for (Convolutions type : Convolutions.values())
       {
          pool = new Hashtable<Integer, ConvolveOp>();
          convolutionPools.put(type, pool);          
       }
//]skeleton3.
//[skeleton4.
       
       // Initialize the pool of grayExceptOp objects
       grayExceptPool = new Hashtable<Integer, GrayExceptOp>();
    }
    


    /**
     * Create a BufferedImageOpFactory object
     */
    public static BufferedImageOpFactory createFactory()
    {
       return instance;       
    }
//]skeleton4.
//[createOp.    

    /**
     * Create a ConvolveOp
     *
     * @param type   The type of ConvolveOp
     * @param size   The size of the kernel
     */
    private ConvolveOp createOp(Convolutions type, int size)
    {
       ConvolveOp                       op;
       Hashtable<Integer, ConvolveOp>   pool;
       Integer                          key;
       
       key  = new Integer(size);       
       pool = convolutionPools.get(type);
       op   = pool.get(key);
       
       if (op == null)
       {
          op = new ConvolveOp(new Kernel(size,size,
                                         type.getKernelValues(size)),
                                         ConvolveOp.EDGE_NO_OP,
                                         null);
          pool.put(key, op);          
       }

       return op;       
    }
//]createOp.    

//[createBlurOp.

    /**
     * Create a blur operation
     *
     * @param size   The size of the convolution kernel
     */
    public ConvolveOp createBlurOp(int size)
    {
       return createOp(Convolutions.BLUR, size);       
    }
//]createBlurOp.


    /**
     * Create a brighten operation
     */
    public RescaleOp createBrightenOp()
    {
       if (brightenOp == null)
       {
          //[RescaleOp1.
          brightenOp = new RescaleOp(1.5f, 0.0f, null);
          //]RescaleOp1.
       }
       return brightenOp;
    }


    /**
     * Create a darken operation
     */
    public RescaleOp createDarkenOp()
    {
       if (darkenOp == null)
       {
          //[RescaleOp2.
          darkenOp = new RescaleOp(0.5f, 0.0f, null);
          //]RescaleOp2.
       }
       return darkenOp;
    }

//[createEdgeDetectionOp.
    /**
     * Create an edge detection operation
     *
     * @param size   The size of the convolution kernel
     */
    public ConvolveOp createEdgeDetectionOp(int size)
    {
	return createOp(Convolutions.EDGE, size);
    }
//]createEdgeDetectionOp.

//[createEmbossOp.

    /**
     * Create an embossing operation
     *
     * @param size   The size of the convolution kernel
     */
    public ConvolveOp createEmbossOp(int size)
    {
	return createOp(Convolutions.EMBOSS, size);
    }
//]createEmbossOp.

//[createGrayOp.
    /**
     * Create an operation that converts to a gray colorspace
     */
    public ColorConvertOp createGrayOp()
    {
       if (grayOp == null)
       {
          //[ColorConvertOp.
          ColorConvertOp         op;
          ColorSpace             cs;
          
          cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);          
          op = new ColorConvertOp(cs, null);
          //]ColorConvertOp.
          grayOp = op;
       }
       return grayOp;       
    }
//]createGrayOp.


    /**
     * Create an operation that converts "all colors but one"
     * to gray
     *
     * Note: This method actually leaves all colors that are 
     * close to the specified color
     */
    public GrayExceptOp createGrayExceptOp(int r, int g, int b)
    {
       Color                color;
       GrayExceptOp         op;
       Integer              key;
       

       color = new Color(r,g,b);
       key   = new Integer(color.getRGB());
       
       op    = grayExceptPool.get(key);

       if (op == null)
       {
          //[GrayExceptOp.
          
          op = new GrayExceptOp(r, g, b);
          //]GrayExceptOp.
       }
       grayExceptPool.put(key, op);

       return op;       
    }



    /**
     * Create an operation that does not change the
     * image (i.e., an identity)
     *
     * @param size   The size of the convolution kernel
     */
    public ConvolveOp createIdentityOp(int size)
    {
	return createOp(Convolutions.IDENTITY, size);
    }


    /**
     * Create a "metal" operation
     */
    public RescaleOp createMetalOp()
    {
       if (metalOp == null)
       {
          //[RescaleOp3.
          metalOp = new RescaleOp(1.0f, 128.0f, null);
          //]RescaleOp3.
       }
       return metalOp;
    }


    /**
     * Create a photo-negative operation
     */
    public LookupOp createNegativeOp()
    {
       if (negativeOp == null)
       {
          //[LookupOp1.
          // Using a look-up operation to create a
          // "color negative"
          LookupTable               lookupTable;
          short[]                   lookup;
          
          lookup = new short[256];
          
          for (int i=0; i<lookup.length; i++) 
          {
             lookup[i] = (short)(255 - i);
          }       
          
          lookupTable = new ShortLookupTable(0, lookup);
          negativeOp  = new LookupOp(lookupTable, null);
          //]LookupOp1.
       }
       
       return negativeOp;
    }


    /**
     * Create a night-vision operation (i.e., an operation that
     * makes eveything appear green)
     */
    public LookupOp createNightVisionOp()
    {
       if (nightVisionOp == null)
       {
          //[LookupOp2.
          // Using a look-up operation to create a
          // "night vision" effect
          LookupTable               lookupTable;
          short[]                   leave, remove;
          short[][]                 lookupMatrix;
          
          
          leave  = new short[256];
          remove = new short[256];
          
          for (int i=0; i < leave.length; i++) {
             
             leave[i]  = (short)(i);
             remove[i] = (short)(0);
          }
          
          lookupMatrix    = new short[3][];
          
          lookupMatrix[0] = remove;
          lookupMatrix[1] = leave;
          lookupMatrix[2] = remove;
          
          
          lookupTable   = new ShortLookupTable(0, lookupMatrix);
          nightVisionOp = new LookupOp(lookupTable, null);
          //]LookupOp2.
       }
	return nightVisionOp;
    }
    

    /**
     * Create a scaling operation
     *
     * @param xScale   The horizontal scaling factor
     * @param yScale   The vertical scaling factor
     */
    public AffineTransformOp createScaleOp(double xScale, double yScale)
    {
       // We could use an object pool in which 
       // the AffineTransform is the key

       //[AffineTransformOp.
       AffineTransform      at;       
       AffineTransformOp    op;
       
       at = AffineTransform.getScaleInstance(xScale, yScale);
       op = new AffineTransformOp(
                          at, 
                          AffineTransformOp.TYPE_BILINEAR);
       //]AffineTransformOp.

       return op;       
    }

//[createSharpenOp.

    /**
     * Create a sharpen operation
     *
     * @param size   The size of the convolution kernel
     */
    public ConvolveOp createSharpenOp(int size)
    {
	return createOp(Convolutions.SHARPEN, size);
    }
//]createSharpenOp.
//[skeleton5.

}
//]skeleton5.
