package adecWatt.model;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Collection;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Vector;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;

import misc.ScaledImage;
import misc.Util;
import static misc.Config.FS;

import static adecWatt.model.PermanentDB.backExtention;
import static adecWatt.model.PermanentDB.dataDir;
import static adecWatt.model.PermanentDB.localRemoteDir;
import static adecWatt.model.PermanentDB.localToken;
import static adecWatt.model.PermanentDB.serverToken;
import static adecWatt.model.PermanentDB.cacheToken;

public class ImageDB {
    static {
 	ImageIO.setUseCache (false);
    }

    // ========================================
    static public final String iconDirName = "icon", imageDirName = "image";
    static public class FileFilterImageName implements FileFilter {
	private String baseImageName;
	public FileFilterImageName (String imageName) {
	    baseImageName = Util.getBase (imageName);
	}
	public boolean accept (File file) {
	    return file.isFile () && !file.isHidden () && Util.getBase (file).equals (baseImageName) && ! Util.getExtention (file).toLowerCase ().equals (backExtention);
	}
    };

    // ========================================
    private AdecWatt adecWatt;
    private String dir;
    private TagDB tagDB;
    public PreviewCache previewCache;

    // ========================================
    private File getDir (String placeToken) {
	return new File (new File (dataDir, placeToken), dir);
    }
    public Collection<String> getTags (String imageName) {
	return tagDB.getTags (imageName);
    }
    public void putTags (String imageName, Collection<String> imageTags) {
	tagDB.putTags (imageName, imageTags);
    }

    // ========================================
    public ImageDB (AdecWatt adecWatt, String dir) {
	this.adecWatt = adecWatt;
	this.dir = dir;
	tagDB = new TagDB (dir);
	previewCache = new PreviewCache (new File (new File (dataDir, cacheToken), dir));
    }

    // ========================================
    Hashtable<ScaledImage, Hashtable<Color, ScaledImage>> coloredScaledImages = new Hashtable<ScaledImage, Hashtable<Color, ScaledImage>> ();
    final int toReplace = Color.BLUE.getRGB ();

    public ScaledImage getColoredScaledImages (ScaledImage scaledImage, Color color) {
	Hashtable<Color, ScaledImage> colorList = coloredScaledImages.get (scaledImage);
	if (colorList == null) {
	    colorList = new Hashtable<Color, ScaledImage> ();
	    coloredScaledImages.put (scaledImage, colorList);
	}
	ScaledImage coloredScaledImage = colorList.get (color);
	if (coloredScaledImage == null) {
	    int newColor = color.getRGB ();
	    BufferedImage reference = scaledImage.reference;
	    int width = reference.getWidth ();
	    int height = reference.getHeight ();
	    BufferedImage colored = new BufferedImage (width, height, BufferedImage.TYPE_INT_ARGB);
	    for (int x = 0; x < width; x++)
		for (int y = 0; y < height; y++) {
		    int c = reference.getRGB (x, y);
		    if (c == toReplace)
			c = newColor;
		    colored.setRGB (x, y, c);
		}
	    coloredScaledImage = new ScaledImage (colored);
	    colorList.put (color, coloredScaledImage);
	}
	return coloredScaledImage;
    }

    // ========================================
    private Hashtable<String, BufferedImage> bufferedImages = new Hashtable<String, BufferedImage> ();
    private Hashtable<BufferedImage, ScaledImage> scaledImages = new Hashtable<BufferedImage, ScaledImage> ();

    public ScaledImage getImage (String imageName, boolean horizontalSpin) {
	try {
	    ScaledImage result = getScaled (findBufferedImage (imageName));
	    return horizontalSpin ? result.getNewSpin (horizontalSpin, false) : result;
	} catch (Exception e) {
	    return null;
	}
    }
    public ScaledImage getScaled (BufferedImage bufferedImage) {
	if (bufferedImage == null)
	    return null;
	ScaledImage scaledImage = scaledImages.get (bufferedImage);
	if (scaledImage != null)
	    return scaledImage;
	scaledImage = new ScaledImage (bufferedImage);
	scaledImages.put (bufferedImage, scaledImage);
	return scaledImage;
    }

    static public Comparator<NameLocal> nameLocalComparator =
	new Comparator<NameLocal> () {
	    public int compare (NameLocal o1, NameLocal o2) {
		if (o1 == o2)
		    return 0;
		int diff = o1.fileName.compareToIgnoreCase (o2.fileName);
		if (diff != 0)
		    return diff > 0 ? 1 : -1;
		if (o1.isLocal == o2.isLocal)
		    return 0;
		return o1.isLocal ? 1 : -1;
	    }
	};

    public class NameLocal {
	public String fileName;
	public boolean isLocal;
	public ImageIcon icon;
	public NameLocal (String fileName, boolean isLocal) {
	    this.fileName = fileName;
	    this.isLocal = isLocal;
	}
    }
    public boolean isRemote (String imageName) {
	FileFilterImageName fileFilterImageName = new FileFilterImageName (imageName);
	boolean result = true;
	for (String srcDir : localRemoteDir)
	    try {
		result = !result;
		if (new File (new File (dataDir, srcDir), dir).listFiles (fileFilterImageName).length > 0)
		    return result;
	    } catch (Exception e) {
	    }
	return false;
    }

    public File getFile (String imageName, boolean isLocal) {
	if (!localRemoteDir.get (0).equals (localToken))
	    isLocal = ! isLocal;
	String srcDir = localRemoteDir.get (isLocal ? 0 : 1);
	try {
	    return getDir (srcDir).listFiles (new FileFilterImageName (imageName))[0];
	} catch (Exception e) {
	}
	return null;
    }

    public void renameLocalImage (String imageName, String newImageName) {
	if (imageName == null || newImageName == null || imageName.equals (newImageName))
	    return;
	BufferedImage image = bufferedImages.get (imageName);
	if (image != null) {
	    bufferedImages.remove (imageName);
	    bufferedImages.put (newImageName, image);
	}
	File oldFile = getFile (imageName, true);
	if (oldFile == null)
	    return;
	File newFile = new File (oldFile.getParent (), newImageName+"."+Util.getExtention (oldFile).toLowerCase ());
	try {
	    Files.move (oldFile.toPath (), newFile.toPath (), StandardCopyOption.REPLACE_EXISTING);
	} catch (Exception e) {
	}
	if (oldFile.exists ()) {
	    // XXX si pas de move ?
	    System.err.println ("coucou l'image n'a pas été renommée:"+oldFile);
	}
    }
    public void removeImage (String imageName) {
	try {
	    File oldFile = getFile (imageName, true);
	    Util.backup (oldFile, Util.getExtention (oldFile), backExtention);
	    clearBufferedImage (imageName);
	    previewCache.clean (imageName);
	} catch (Exception e) {
	}
    }

    private String getNewImageName (File file) {
	return adecWatt.getUser ().getDataId ()+"."+Util.getExtention (file).toLowerCase ();
    }
    private String getImageName (String imageName, File file) {
	return Util.getBase (imageName)+"."+Util.getExtention (file);
    }
    public String addImage (File file) {
	String newImageName = getNewImageName (file);
	File dstDir = new File (new File (dataDir, localToken), dir);
	try {
	    dstDir.mkdirs ();
	    Util.copy (new FileInputStream (file), new FileOutputStream (new File (dstDir, newImageName)), null, null);
	} catch (Exception e) {
	    e.printStackTrace ();
	}
	return newImageName;
    }
    public void renameVisitor (TreeSet<String> imageNames) {
	if (imageNames.size () == 0)
	    return;
	PermanentDB permanentDB = adecWatt.getPermanentDB ();
	TreeMap<String, String> translateMap = new TreeMap<String, String> ();
	String pattern = String.format ("%03d-", User.visitorId);
	User user = adecWatt.getUser ();
	for (String imageName : imageNames) {
	    if (!imageName.startsWith (pattern))
		continue;
	    String newImageName = user.getDataId ();
	    String ext = Util.getExtention (imageName);
	    if (ext != null) {
		System.err.println ("coucou renameVisitor has ext:"+imageName);
		imageNames.remove (imageName);
		imageName = Util.getBase (imageName);
	    }
	    renameLocalImage (imageName, newImageName);
	    translateMap.put (imageName, newImageName);
	}
	permanentDB.renameImages (translateMap);
	tagDB.renameImages (translateMap);
	if (translateMap.size () < 1)
	    return;
	imageNames.removeAll (translateMap.keySet ());
	imageNames.addAll (translateMap.values ());
    }
    public void promoteTag (TreeSet<String> imageNames) {
	tagDB.promote (imageNames);
    }
    public void promote (TreeSet<String> imageNames) {
	if (imageNames.size () == 0)
	    return;
	// liste des id à écrire
	// XXX tag par id
	for (String imageName : imageNames) {
	    // XXX mis à jours des "tags" local => sever
	    File file = getFile (imageName, true);
	    // XXX autre méthode ?
	    if (file == null) {
		// XXX si pas sauvé => trouver extension et utiliser scaledImage (pas copy)
		continue;
	    }
	    File newFile = new File (new File (new File (dataDir, serverToken), dir), file.getName ());
	    try {
		Files.move (file.toPath (), newFile.toPath (), StandardCopyOption.REPLACE_EXISTING);
	    } catch (Exception e) {
	    }
	    if (file.exists ()) {
		// XXX si pas de move ?
		System.err.println ("coucou l'image "+imageName+" n'a pas été déplacée");
	    }
	}
    }
    public String updateImage (NameLocal nameLocal, File file) {
	String newImageName = getImageName (nameLocal.fileName, file);
	File dstDir = getDir (localToken);
	if (nameLocal.isLocal)
	    removeImage (nameLocal.fileName);
	try {
	    dstDir.mkdirs ();
	    // XXX utiliser un tmp ?
	    Util.copy (new FileInputStream (file), new FileOutputStream (new File (dstDir, newImageName)), null, null);
	    clearBufferedImage (newImageName);
	} catch (Exception e) {
	    e.printStackTrace ();
	}
	return newImageName;
    }

    public boolean importFile (String dirName, String fileName, ByteArrayInputStream dataIn) {
	if (!dir.equals (dirName))
	    return false;
	try {
	    bufferedImages.put (fileName, ImageIO.read (dataIn));
	} catch (Exception e) {
	}
	return true;
    }

    public void addImageZip (ZipOutputStream out, String iconName)
	throws IOException {
	if (iconName == null || iconName.isEmpty ())
	    return;
	ZipEntry ze = new ZipEntry (dir+"/"+iconName);
	out.putNextEntry (ze);
	File srcDir = new File (new File (dataDir, localToken), dir);
	File srcFile = new File (srcDir, iconName);
	int length = (int) srcFile.length ();
	byte[] tmp = new byte[length];
	FileInputStream fileInputStream = new FileInputStream (srcFile);
	try {
	    fileInputStream.read (tmp);
	} finally {
	    fileInputStream.close ();
	}
	out.write (tmp);
	out.closeEntry();
	out.flush ();
    }
    public Vector<NameLocal> listImage () {
	Vector<NameLocal> result = new Vector<NameLocal> ();
	Vector<String> filesNames = new Vector<String> ();
	Boolean isLocal = localRemoteDir.get (0).equals (localToken);
	isLocal = ! isLocal;
	for (String srcDir : localRemoteDir)
	    try {
		isLocal = ! isLocal;
		File[] files = new File (dataDir, srcDir+FS+dir).listFiles (PreviewCache.fileImageFilter);
		if (files == null)
		    continue;
		int maxSize = result.size ()+filesNames.size ();
		result.ensureCapacity (maxSize);
		filesNames.ensureCapacity (maxSize);
		for (File file : files) {
		    String fileName = file.getName ();
		    if (!filesNames.contains (fileName)) {
			filesNames.add (fileName);
			result.add (new NameLocal (fileName, isLocal));
		    }
		}
	    } catch (Exception e) {
		e.printStackTrace ();
	    }
	result.sort (nameLocalComparator);
	return result;
    }

    public TreeMap<String, String> newNamePlan () {
	TreeMap<String, String> result = new TreeMap<String, String> ();
	for (String srcDir : localRemoteDir)
	    try {
		File[] files = new File (dataDir, srcDir+FS+dir).listFiles (PreviewCache.fileImageFilter);
		if (files == null)
		    continue;
		for (File file : files) {
		    String fileName = file.getName ();
		    if (User.splitId (fileName) != null)
			continue;
		    String newName = result.get (fileName);
		    if (newName == null)
			result.put (fileName, newName = getNewImageName (file));
		    Files.move (file.toPath (), new File (file.getParent (), newName).toPath (), StandardCopyOption.REPLACE_EXISTING);
		}
	    } catch (Exception e) {
		e.printStackTrace ();
	    }
	return result;
    }

    public void clearBufferedImage (String imageName) {
	if (imageName == null)
	    return;
	imageName = Util.getBase (imageName);
	BufferedImage bufferedImage = bufferedImages.get (imageName);
	if (bufferedImage == null)
	    return;
	ScaledImage scaledImage = scaledImages.get (bufferedImage);
	bufferedImages.remove (imageName);
	if (scaledImage == null)
	    return;
	scaledImages.remove (bufferedImage);
	coloredScaledImages.remove (scaledImage);
    }

    public BufferedImage findBufferedImage (String imageName) {
	if (imageName == null)
	    return null;
	imageName = Util.getBase (imageName);
	BufferedImage bufferedImage = bufferedImages.get (imageName);
	if (bufferedImage != null)
	    return bufferedImage;
	FileFilterImageName fileFilterImageName = new FileFilterImageName (imageName);
	for (String srcDir : localRemoteDir)
	    try {
		for (File file : new File (new File (dataDir, srcDir), dir).listFiles (fileFilterImageName)) {
		    bufferedImage = ImageIO.read (file);
		    bufferedImages.put (imageName, bufferedImage);
		    return bufferedImage;
		}
	    } catch (Exception e) {
	    }
	System.err.println ("coucou not found:"+dir+FS+imageName);
	return null;
    }

    // ========================================
    public void updateDataId () {
	User user = adecWatt.getUser ();
	for (String srcDir : localRemoteDir)
	    try {
		for (File file : new File (dataDir, srcDir+FS+dir).listFiles (PreviewCache.fileImageFilter))
		    user.updateDataId (file.getName ());
	    } catch (Exception e) {
	    }
    }

    // ========================================
    public void reload () {
	tagDB.reload ();
	bufferedImages.clear ();
	scaledImages.clear ();
	updateDataId ();
    }

    // ========================================
}
