package net.psammead.commonist.thumb;

import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.io.*;
import java.util.*;

import javax.imageio.*;
import javax.imageio.stream.*;
import javax.swing.*;

import net.psammead.commonist.*;
import net.psammead.util.*;

/** manages thumbnail images */
public class Thumbnails {
	private static final Logger log = new Logger(Thumbnails.class);
	
	private FileCache	cache;
	private int		maxSize;

	/** creates and caches thumbnails */
	public Thumbnails(FileCache cache, int maxSize) {
		this.cache		= cache;
		this.maxSize	= maxSize;
	}

	/** creates a thumbnail Icon from an image File or returns null */ 
	public Icon thumbnail(File file) {
		try {
			Image	thumb	= cachedThumbnail(file);
			if (thumb == null)	return null;
			return new ImageIcon(thumb);
		}
		catch (Exception e) {
			log.info("cannot create thumbnail from " + file, e);
			return null;
		}
	}
	
	/** make a thumbnail or return a cached version */
	private Image cachedThumbnail(File file) throws IOException {
		// get cached
		File	thumbFile	= cache.get(file);
		if (thumbFile != null) {
			return ImageIO.read(thumbFile);
		}
		
		// read original
		//BufferedImage	image	= ImageIO.read(file);
		BufferedImage	image	= readSubsampled(file);
		if (image == null)	{
			log.warn("could not read original: " + file);
			return null;
		}
		
		// make thumb
		BufferedImage	thumb	= makeThumbnail(image, maxSize);
		
		// cache thumb
		thumbFile	= cache.put(file);
		boolean	success	= ImageIO.write(thumb, "jpg", thumbFile);
		if (!success) {
			log.warn("could not create thumbnail: " + thumbFile);
			cache.remove(file);
		}
		
		return thumb;
	}
	
	/** makes a thumbnail from an image */
	private BufferedImage makeThumbnail(BufferedImage image, int maxSize) {
		double	scale	= (double)maxSize / Math.max(image.getWidth(), image.getHeight());
		if (scale >= 1.0)	return image;
		
		// TODO: check more image types
		
		// seen: TYPE_BYTE_GRAY and TYPE_BYTE_INDEXED,
		// TYPE_CUSTOM			needs conversion or throws an ImagingOpException at transformation time
		// TYPE_3BYTE_BGR		works without conversion
		// TYPE_BYTE_GRAY		needs conversion or creates distorted grey version
		// TYPE_INT_RGB			needs conversion or creates distorted grey version
		// TYPE_BYTE_INDEXED	works, but inverts color if converted to TYPE_3BYTE_BGR
		
		// normalize image type
		int	imageType	= image.getType();
		if (imageType != BufferedImage.TYPE_3BYTE_BGR 
		&& imageType != BufferedImage.TYPE_BYTE_INDEXED) {
//		if (imageType == BufferedImage.TYPE_CUSTOM  
//		|| imageType == BufferedImage.TYPE_BYTE_GRAY
//		|| imageType == BufferedImage.TYPE_INT_RGB) {
			BufferedImage	normalized	= new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
			Graphics		g		= normalized.getGraphics();
			g.drawImage(image, 0, 0, null);
			g.dispose();
			image	= normalized;
		}
		
		Dimension			size	= new Dimension((int)(image.getWidth() * scale), (int)(image.getHeight() * scale));
		BufferedImage		thumb	= new BufferedImage(size.width, size.height, image.getType());
		AffineTransformOp	op		= new AffineTransformOp(
			new AffineTransform(scale, 0, 0, scale, 0, 0),	// AffineTransform.getScaleInstance(sx, sy)
			AffineTransformOp.TYPE_BILINEAR					// TYPE_NEAREST_NEIGHBOR, TYPE_BILINEAR, TYPE_BICUBIC
		);
		
		op.filter(image, thumb);
		return thumb;
	}
	
	/** scales down when the image is too big */
	private BufferedImage readSubsampled(File input) throws IOException {
		ImageInputStream stream = ImageIO.createImageInputStream(input);
		if (stream == null)	throw new IOException("cannot create ImageInputStream for file: " + input);
		
		Iterator<ImageReader>	it	= ImageIO.getImageReaders(stream);
        if (!it.hasNext())	return null;	// throw new IOException("cannot create ImageReader for file: " + input);
        
        ImageReader		reader = it.next();
        reader.setInput(stream, true, true);
        
		ImageReadParam	param	= reader.getDefaultReadParam();
		
		int	imageIndex	= 0;

		int	sizeX		= reader.getWidth(imageIndex);
		int	sizeY		= reader.getHeight(imageIndex);
		int	size		= Math.min(sizeX, sizeY);
		int	scale		= size / Constants.THUMBNAIL_SIZE;
		int	sampling	= smallerPowerOf2(scale * 100 / Constants.THUMBNAIL_SCALE_HEADROOM);
//		System.err.println("#### scale=" + scale + "\t=> sampling=" + sampling);
		
		// TODO: could scale at load time!
        if (sampling > 1)	param.setSourceSubsampling(sampling, sampling, 0, 0);
        BufferedImage	image	= reader.read(imageIndex, param);
        
        // TODO: finally
        stream.close();	
        reader.dispose();
        
        return image;
	}

	/** returns the biggers power of 2 smaller than or equal to x */
	private int smallerPowerOf2(int x) {
		int	exp	= 1;
		while (x > 0) {
			exp	<<= 1;
			x	>>= 1;
		}
		return exp >> 1;
	}
}
