import com.runemate.game.api.hybrid.Environment;
import com.runemate.game.api.script.framework.AbstractScript;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.EventListener;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.function.Consumer;
/**
* Author: Tom
* Date: 04/03/2015
* Time: 20:38
*/
public class ImageManager {
    private static final Image NIL_IMAGE = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
    private final AbstractScript script;
    private final LRUImageCache cache = new LRUImageCache(this);
    private final LinkedList<ImageLoadExceptionEventListener> listeners = new LinkedList<>();
    private int maxImageCount;
    public ImageManager(AbstractScript script, int maxImageCount) {
        this.script = script;
        this.maxImageCount = maxImageCount;
    }
    public ImageManager(AbstractScript script) {
        this(script, -1);
    }
    public final int getMaxImageCount() {
        return maxImageCount;
    }
    public final void setMaxImageCount(int maxImageCount) {
        this.maxImageCount = maxImageCount;
    }
    public final void submitLoadExceptionListener(ImageLoadExceptionEventListener listener) {
        listeners.add(listener);
    }
    public final void removeLoadExceptionListener(ImageLoadExceptionEventListener listener) {
        listeners.remove(listener);
    }
    public final Image get(String path, boolean forceLoad, ImageLoadMode imageLoadMode) {
        String imageTag = imageLoadMode.name().concat(":").concat(path);
        Image image = cache.get(imageTag);
        if (forceLoad || image == null) {
            synchronized (cache) {
                image = cache.get(imageTag);
                if (image == null) {
                    cache.put(imageTag, image = imageLoadMode.get(this, path));
                }
            }
        }
        return image;
    }
    public enum ImageLoadMode {
        RESOURCE() {
            @Override
            Image get(ImageManager instance, String path) {
                try (InputStream stream = instance.script.getClass().getResourceAsStream(path)) {
                    return ImageIO.read(stream);
                } catch (IOException e) {
                    onError(instance, new ImageLoadExceptionEvent(path, this, e));
                }
                return NIL_IMAGE;
            }
        },
        LOCAL_SCRIPT() {
            @Override
            Image get(ImageManager instance, String path) {
                path = Environment.getStorageDirectory().getAbsolutePath().concat(File.separator).concat(path);
                try (InputStream stream = new FileInputStream(path)) {
                    return ImageIO.read(stream);
                } catch (IOException e) {
                    onError(instance, new ImageLoadExceptionEvent(path, this, e));
                }
                return NIL_IMAGE;
            }
        },
        LOCAL_GENERAL() {
            @Override
            Image get(ImageManager instance, String path) {
                path = Environment.getGeneralStorageDirectory().getAbsolutePath().concat(File.separator).concat(path);
                try (InputStream stream = new FileInputStream(path)) {
                    return ImageIO.read(stream);
                } catch (IOException e) {
                    onError(instance, new ImageLoadExceptionEvent(path, this, e));
                }
                return NIL_IMAGE;
            }
        };
        abstract Image get(ImageManager instance, String path);
        final void onError(ImageManager manager, final ImageLoadExceptionEvent exception) {
            manager.listeners.forEach(new Consumer<ImageLoadExceptionEventListener>() {
                @Override
                public void accept(ImageLoadExceptionEventListener listener) {
                    listener.onEvent(exception);
                }
            });
        }
    }
    public static interface ImageLoadExceptionEventListener extends EventListener {
        public void onEvent(ImageLoadExceptionEvent event);
    }
    public static final class ImageLoadExceptionEvent {
        private final String path;
        private final ImageLoadMode imageLoadMode;
        private final IOException exception;
        public ImageLoadExceptionEvent(String path, ImageLoadMode imageLoadMode, IOException exception) {
            this.path = path;
            this.imageLoadMode = imageLoadMode;
            this.exception = exception;
        }
        public final String getPath() {
            return path;
        }
        public final ImageLoadMode getImageLoadMode() {
            return imageLoadMode;
        }
        public final IOException getException() {
            return exception;
        }
        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder("IOExceptionEvent{");
            sb.append("path='").append(path).append('\'');
            sb.append(", mode=").append(imageLoadMode.name());
            sb.append(", exception=").append(exception.getMessage());
            sb.append('}');
            return sb.toString();
        }
    }
    private final class LRUImageCache extends LinkedHashMap<String, Image> {
        private final ImageManager manager;
        private LRUImageCache(ImageManager manager) {
            this.manager = manager;
        }
        @Override
        protected final boolean removeEldestEntry(Map.Entry<String, Image> eldest) {
            return manager.maxImageCount == -1 || size() > manager.maxImageCount;
        }
    }
}