package org.mage.plugins.card.images;

import com.google.common.base.Function;
import com.google.common.collect.ComputationException;
import com.google.common.collect.MapMaker;
import com.mortennobel.imagescaling.ResampleOp;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
import mage.client.dialog.PreferencesDialog;
import mage.view.CardView;
import net.java.truevfs.access.TFile;
import net.java.truevfs.access.TFileInputStream;
import net.java.truevfs.access.TFileOutputStream;
import org.apache.log4j.Logger;
import org.mage.plugins.card.constants.Constants;
import org.mage.plugins.card.dl.sources.DirectLinksForDownload;
import org.mage.plugins.card.utils.CardImageUtils;

/**
 * This class stores ALL card images in a cache with soft values. this means
 * that the images may be garbage collected when they are not needed any more, but will
 * be kept as long as possible.
 * 
 * Key format: "<cardname>#<setname>#<type>#<collectorID>#<param>"
 * 
 * where param is:
 * 
 * <ul>
 * <li>#Normal: request for unrotated image</li>
 * <li>#Tapped: request for rotated image</li>
 * <li>#Cropped: request for cropped image that is used for Shandalar like card
 * look</li>
 * </ul>
 */
public class ImageCache {

    private static final Logger log = Logger.getLogger(ImageCache.class);

    private static final Map<String, BufferedImage> imageCache;

    /**
     * Common pattern for keys.
     * Format: "<cardname>#<setname>#<collectorID>"
     */
    private static final Pattern KEY_PATTERN = Pattern.compile("(.*)#(.*)#(.*)#(.*)#(.*)");

    static {
        imageCache = new MapMaker().softValues().makeComputingMap(new Function<String, BufferedImage>() {
            @Override
            public BufferedImage apply(String key) {
                try {
                    
                    boolean usesVariousArt = false;
                    if (key.endsWith("#usesVariousArt")) {
                        usesVariousArt = true;
                        key = key.replace("#usesVariousArt", "");
                    }
                    boolean thumbnail = false;
                    if (key.endsWith("#thumb")) {
                        thumbnail = true;
                        key = key.replace("#thumb", "");
                    }
                    Matcher m = KEY_PATTERN.matcher(key);

                    if (m.matches()) {
                        String name = m.group(1);
                        String set = m.group(2);
                        Integer type = Integer.parseInt(m.group(3));
                        Integer collectorId = Integer.parseInt(m.group(4));
                        String tokenSetCode = m.group(5);

                        CardDownloadData info = new CardDownloadData(name, set, collectorId, usesVariousArt, type, tokenSetCode);

                        String path;
                        if (collectorId == 0) {
                            info.setToken(true);
                            path = CardImageUtils.generateTokenImagePath(info);
                            if (path == null) {
                                path = DirectLinksForDownload.outDir + File.separator + DirectLinksForDownload.cardbackFilename;
                            }
                        } else {
                            path = CardImageUtils.generateImagePath(info);
                        }

                        if (path == null) {
                            return null;
                        }
                        TFile file = new TFile(path);
                        if (!file.exists()) {
                            log.debug("File does not exist: " + file.toString());
                            return null;
                        }

                        if (thumbnail && path.endsWith(".jpg")) {
                            String thumbnailPath = buildThumbnailPath(path);
                            TFile thumbnailFile = new TFile(thumbnailPath);
                            if (thumbnailFile.exists()) {
                                log.debug("loading thumbnail for " + key + ", path="+thumbnailPath);
                                return loadImage(thumbnailFile);
                            } else {
                                BufferedImage image = loadImage(file);
                                image = getWizardsCard(image);
                                if (image == null) {
                                    return null;
                                }
                                log.debug("creating thumbnail for " + key);
                                return makeThumbnail(image, thumbnailPath);
                            }
                        } else {
                            return getWizardsCard(loadImage(file));
                        }
                    } else {
                        throw new RuntimeException(
                                "Requested image doesn't fit the requirement for key (<cardname>#<setname>#<collectorID>): " + key);
                    }
                } catch (Exception ex) {
                    if (ex instanceof ComputationException) {
                        throw (ComputationException) ex;
                    } else {
                        throw new ComputationException(ex);
                    }
                }
            }
        });
    }

    public static BufferedImage getMorphImage() {
        CardDownloadData info = new CardDownloadData("Morph", "KTK", 0, false, 0, "KTK");
        info.setToken(true);
        String path = CardImageUtils.generateTokenImagePath(info);
        TFile file = new TFile(path);
        return loadImage(file);
    }

    private static String buildThumbnailPath(String path) {
        String thumbnailPath;
        if (PreferencesDialog.isSaveImagesToZip()) {
            thumbnailPath = path.replace(".zip", ".thumb.zip");
        } else {
            thumbnailPath = path.replace(".jpg", ".thumb.jpg");
        }
        return thumbnailPath;
    }

    public static BufferedImage getWizardsCard(BufferedImage image) {
        if (image.getWidth() == 265 && image.getHeight() == 370) {
            BufferedImage crop = new BufferedImage(256, 360, BufferedImage.TYPE_INT_RGB);
            Graphics2D graphics2D = crop.createGraphics();
            graphics2D.drawImage(image, 0, 0, 255, 360, 5, 5, 261, 365, null);
            graphics2D.dispose();
            return crop;
        } else {
            return image;
        }
    }

    public static BufferedImage getThumbnail(CardView card) {
        String key = getKey(card) + "#thumb";
        if (card.getUsesVariousArt()) {
            key += "#usesVariousArt";
        }
        // log.debug("#key: " + key);
        return getImage(key);
    }

    public static BufferedImage getImageOriginal(CardView card) {
        String key = getKey(card);
        if (card.getUsesVariousArt()) {
            key += "#usesVariousArt";
        }
        // log.warn("#key: " + key);
        return getImage(key);
    }

    public static BufferedImage getImageOriginalAlternateName(CardView card) {
        String key = getKeyAlternateName(card, card.getAlternateName());
        if (card.getUsesVariousArt()) {
            key += "#usesVariousArt";
        }
        // log.warn("#key: " + key);
        return getImage(key);
    }

//    /**
//     * Returns the Image corresponding to the Path
//     */
//    private static BufferedImage getImageByPath(String path) {
//        if (path == null) {
//            return null;
//        }
//        TFile file = new TFile(path);
//        if (!file.exists()) {
//            log.warn("File does not exist: " + file.toString());
//            return null;
//        }
//        return getWizardsCard(loadImage(file));
//
//    }

    /**
     * Returns the Image corresponding to the key
     */
    private static BufferedImage getImage(String key) {
        try {
            BufferedImage image = imageCache.get(key);
            return image;
        } catch (NullPointerException ex) {
            // unfortunately NullOutputException, thrown when apply() returns
            // null, is not public
            // NullOutputException is a subclass of NullPointerException
            // legitimate, happens when a card has no image
            return null;
        } catch (ComputationException ex) {
            if (ex.getCause() instanceof NullPointerException) {
                return null;
            }
            log.error(ex,ex);
            return null;
        }
    }

    /**
     * Returns the map key for a card, without any suffixes for the image size.
     */
    private static String getKey(CardView card) {
        StringBuilder sb = new StringBuilder(card.getName()).append("#");
        sb.append(card.getExpansionSetCode()).append("#");
        sb.append(card.getType()).append("#");
        sb.append(card.getCardNumber()).append("#");
        sb.append(card.getTokenSetCode() == null ? "":card.getTokenSetCode());
        return sb.toString();
    }

    /**
     * Returns the map key for the flip image of a card, without any suffixes for the image size.
     */
    private static String getKeyAlternateName(CardView card, String alternateName) {
        StringBuilder sb = new StringBuilder(alternateName).append("#");
        sb.append(card.getExpansionSetCode()).append("#");
        sb.append(card.getType()).append("#");
        sb.append(card.getCardNumber()).append("#");
        sb.append(card.getTokenSetCode() == null ? "":card.getTokenSetCode());
        return sb.toString();
    }

    /**
     * Load image from file
     * 
     * @param file
     *            file to load image from
     * @return {@link BufferedImage}
     */
    public static BufferedImage loadImage(TFile file) {
        BufferedImage image = null;
        if (!file.exists()) {
            log.debug("File does not exist: " + file.toString());
            return null;
        }
        try {
            TFileInputStream inputStream = new TFileInputStream(file);
            image = ImageIO.read(inputStream);
            inputStream.close();
        } catch (Exception e) {
            log.error(e, e);
        }

        return image;
    }

    public static BufferedImage makeThumbnail(BufferedImage original, String path) {
        BufferedImage image = getResizedImage(original, Constants.THUMBNAIL_SIZE_FULL);
        TFile imageFile = new TFile(path);
        try {
            TFileOutputStream outputStream = null;
            try {
                //log.debug("thumbnail path:"+path);
                outputStream = new TFileOutputStream(imageFile);
                ImageIO.write(image, "jpg", outputStream);
            } finally {
                if (outputStream != null) {
                    outputStream.close();
                }
            }
        } catch (IOException e) {
            log.error(e,e);
        }
        return image;
    }

    /**
     * Returns an image scaled to the size given
     * @param original
     * @return
     */
    public static BufferedImage getNormalSizeImage(BufferedImage original) {
        if (original == null) {
            return null;
        }

        int srcWidth = original.getWidth();
        int srcHeight = original.getHeight();

        int tgtWidth = Constants.CARD_SIZE_FULL.width;
        int tgtHeight = Constants.CARD_SIZE_FULL.height;

        if (srcWidth == tgtWidth && srcHeight == tgtHeight) {
            return original;
        }

        ResampleOp resampleOp = new ResampleOp(tgtWidth, tgtHeight);
        BufferedImage image = resampleOp.filter(original, null);
        return image;
    }

    /**
     * Returns an image scaled to the size appropriate for the card picture
     * panel For future use.
     */
    private static BufferedImage getFullSizeImage(BufferedImage original, double scale) {
        if (scale == 1) {
            return original;
        }
        ResampleOp resampleOp = new ResampleOp((int) (original.getWidth() * scale), (int) (original.getHeight() * scale));
        BufferedImage image = resampleOp.filter(original, null);
        return image;
    }

    /**
     * Returns an image scaled to the size appropriate for the card picture
     * panel
     * @param original
     * @param sizeNeed
     * @return
     */
    public static BufferedImage getResizedImage(BufferedImage original, Rectangle sizeNeed) {
        ResampleOp resampleOp = new ResampleOp(sizeNeed.width, sizeNeed.height);
        BufferedImage image = resampleOp.filter(original, null);
        return image;
    }

    /**
     * Returns the image appropriate to display the card in the picture panel
     * @param card
     * @param width
     * @param height
     * @return
     */
    public static BufferedImage getImage(CardView card, int width, int height) {
        if (Constants.THUMBNAIL_SIZE_FULL.width + 10 > width) {
            return getThumbnail(card);
        }
        String key = getKey(card);
        if (card.getUsesVariousArt()) {
            key += "#usesVariousArt";
        }
        // log.warn("getImage: " + key);
        BufferedImage original = getImage(key);
        if (original == null) {
            log.debug(key + " not found");
            return null;
        }

        double scale = Math.min((double) width / original.getWidth(), (double) height / original.getHeight());
        if (scale > 1) {
            scale = 1;
        }

        return getFullSizeImage(original, scale);
    }
}
