package game.Manager;

import game.Engine.Annotation.GameManager;
import game.Engine.Utils.PathEx;
import javafx.scene.image.Image;
import javafx.scene.media.Media;
import javafx.scene.text.Font;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

@GameManager
public class ResouceManager {
    private Logger logger = LogManager.getLogger(ResouceManager.class);

    private HashMap<String, String> fonts = new HashMap<>();
    private HashMap<String, String> images = new HashMap<>();
    private HashMap<String, String> controls = new HashMap<>();
    private HashMap<String, String> media = new HashMap<>();
    private HashMap<String, String> maps = new HashMap<>();

    private boolean inJar = false;
    private String jarPath;

    public ResouceManager() {
        jarPath = getClass().getProtectionDomain().getCodeSource().getLocation().getPath();
        if (jarPath.endsWith(".jar")) {
            inJar = true;
            logger.info("running in jar");
        } else {
            inJar = false;
            logger.debug("不在jar包里面");
        }

        loadResources("fonts", fonts);
        loadResources("images", images);
        loadResources("controls", controls);
        loadResources("media", media);
        loadResources("maps", maps);
    }

    private void loadResources(String resourceType, HashMap<String, String> container) {
        if (inJar) {
            loadResourcesFromInner(resourceType, container);
        } else {
            loadResourcesFromOuter(resourceType, container);
        }
    }

    /**
     * 从jar文件内部读取资源
     *
     * @param resourceType
     * @param container
     */
    private void loadResourcesFromInner(String resourceType, HashMap<String, String> container) {
        try {
            String path = getClass().getProtectionDomain().getCodeSource().getLocation().getPath();
            JarFile localJarFile = new JarFile(new File(path));

            Enumeration<JarEntry> entries = localJarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry jarEntry = entries.nextElement();
                String innerPath = jarEntry.getName();
                if (innerPath.startsWith(resourceType) && !innerPath.endsWith("/")) {
                    File resFile = new File(innerPath);
                    container.put(PathEx.getFileName(resFile.getName()), innerPath);
                }
            }
        } catch (IOException ex) {
            logger.error("loadResourcesFromInner error: {}", ex.getMessage());
        }
    }

    /**
     * 从外部的资源文件夹读取资源
     *
     * @param resourceType
     * @param container
     */
    private void loadResourcesFromOuter(String resourceType, HashMap<String, String> container) {
        URL url = getClass().getClassLoader().getResource(resourceType);
        if (url == null) {
            logger.error("{} resources is not exist!", resourceType);
            return;
        }

        try {
            File resourceDir = new File(url.toURI());
            File[] resources = resourceDir.listFiles();
            if (resources == null) {
                logger.error("there is no resource in resource type '{}'", resourceType);
                return;
            }
            for (File res : resources) {
                container.put(PathEx.getFileName(res.getName()), resourceType + "/" + res.getName());
            }
        } catch (URISyntaxException ex) {
            logger.error("load resources error: {}", ex.getMessage());
        }
    }

    /**
     * 获取资源文件的相对路径
     *
     * @param resourceType 资源类型
     * @param resourceName 资源名称
     * @return 返回资源文件的相对路径
     */
    private String getResource(String resourceType, String resourceName) {
        switch (resourceType) {
            case "fonts":
                if (fonts.containsKey(resourceName))
                    return fonts.get(resourceName);
                break;
            case "images":
                if (images.containsKey(resourceName))
                    return images.get(resourceName);
                break;
            case "controls":
                if (controls.containsKey(resourceName))
                    return controls.get(resourceName);
                break;
            default:
                logger.error("Resource Type {] is not exist!", resourceType);
        }
        return null;
    }

    /**
     * 获取所有地图
     *
     * @return 地图相对路径数组
     */
    @Deprecated
    public String[] getAllMaps() {
        URL mapURL = getClass().getClassLoader().getResource("maps");
        if (mapURL == null) {
            logger.error("map path is not exist!");
            return null;
        }
        Path path = Paths.get(mapURL.getPath());
        ArrayList<String> mapList = new ArrayList<>();
        try (DirectoryStream<Path> children = Files.newDirectoryStream(path)) {
            for (Path child : children) {
                mapList.add(child.toString());
            }
        } catch (IOException ex) {
            logger.error("get all maps error: {}", ex.getMessage());
        }
        return mapList.toArray(new String[0]);
    }

    /**
     * 获取所有地图的图像
     *
     * @return Image类型的地图
     */
    public Image[] getAllMapsImage() {
        ArrayList<Image> images = new ArrayList<>();
        for (String imagePath : maps.values()) {
            InputStream stream = getResourceStream(imagePath);
            if (stream == null) {
                logger.error("create InputStream {} failed.", imagePath);
                return null;
            }
            images.add(new Image(stream));
        }
        return images.toArray(new Image[0]);
    }

    public InputStream getResourceStream(String resourcePath) {
        InputStream stream = getClass().getClassLoader().getResourceAsStream(resourcePath);
        if (stream == null) {
            logger.error("create InputStream {} failed.", resourcePath);
            return null;
        }
        return stream;
    }

    public Font getFont(String fontFamily, double fontSize) {
        if (fonts.containsKey(fontFamily)) {
            String fontPath = fonts.get(fontFamily);
            InputStream stream = getResourceStream(fontPath);
            if (stream != null)
                return Font.loadFont(stream, fontSize);
        } else {
            logger.error("font {} is not exist!", fontFamily);
        }
        return Font.getDefault();
    }

    public Image getImage(String imageName) {
        if (images.containsKey(imageName)) {
            String imagePath = images.get(imageName);
            InputStream stream = getResourceStream(imagePath);
            if (stream != null)
                return new Image(stream);
        } else {
            logger.error("image {} is not exist!", imageName);
        }
        return null;
    }

    public Image getControl(String controlName) {
        if (controls.containsKey(controlName)) {
            String controlPath = controls.get(controlName);
            InputStream stream = getResourceStream(controlPath);
            if (stream != null)
                return new Image(stream);
        } else {
            logger.error("control {} is not exist!", controlName);
        }
        return null;
    }

    /**
     * 获取音频资源
     *
     * @param musicName 名称
     * @return Media对象
     */
    public Media getMedia(String musicName) {
        if (media.containsKey(musicName)) {
            String relativePath = media.get(musicName);
            URL musicRes = getClass().getClassLoader().getResource(relativePath);
            if (musicRes == null) {
                logger.error("cannot get media resource {}", relativePath);
                return null;
            }
            if (inJar) {
                String path = String.format("jar:file:%s!/%s", jarPath, relativePath);
                logger.debug("the Fuck media path: {}", path);
                return new Media(path);
            } else
                return new Media("file:" + musicRes.getPath());
        } else
            return null;
    }

    /**
     * 获取字体资源的URL
     *
     * @param fontName 字体名称
     * @return URL格式的font资源
     */
    @Deprecated
    private String getFontURL(String fontName) {
        if (fonts.containsKey(fontName))
            return "file:" + fonts.get(fontName);
        else {
            logger.error("font {} is not exist!", fontName);
            return null;
        }
    }
}