package in.twizmwaz.cardinal.repository.repositories;

import com.google.common.collect.Lists;
import in.twizmwaz.cardinal.repository.LoadedMap;
import in.twizmwaz.cardinal.repository.exception.RotationLoadException;
import in.twizmwaz.cardinal.util.CollectionUtils;
import in.twizmwaz.cardinal.util.Config;
import in.twizmwaz.cardinal.util.Contributor;
import in.twizmwaz.cardinal.util.DomUtil;
import in.twizmwaz.cardinal.util.Numbers;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.jdom2.Document;
import org.jdom2.Element;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
import java.util.stream.Collectors;

public abstract class Repository {

    private static final List<String> requirements = Arrays.asList("map.xml", "region", "level.dat");

    private List<LoadedMap> loaded = Lists.newArrayList();
    private Map<String, File> includes = new HashMap<>();
    private String path;
    private File root;
    private int id = -1;

    private static int maxId = 0;

    public int getId() {
        return id;
    }

    public String getSource(boolean show) {
        return show ? path : null;
    }

    protected String getPath() {
        return path;
    }

    public File getRoot() {
        return root;
    }

    public String toChatMessage(boolean op) {
        String source = getSource(op);
        return ChatColor.YELLOW + "#" + getId() + " " + ChatColor.GOLD +
                (source == null ? ChatColor.STRIKETHROUGH + "Hidden" : source);
    }

    public File getInclude(String name) {
        return includes.containsKey(name) ? includes.get(name) : null;
    }

    Repository(String path) throws RotationLoadException, IOException {
        this.path = path;
        this.root = new File(path);
    }

    public void refreshRepo() throws RotationLoadException, IOException {
        if (id == -1) id = maxId++;
        includes.clear();
        root = new File(path);
        if (!root.exists()) root.mkdirs();
        loadIncludes(root);
        loaded = CollectionUtils.updateAndRun(loaded, loadMapsIn(root), LoadedMap::update)
                .peek(LoadedMap::load).collect(Collectors.toList());
    }

    private void loadIncludes(File file) {
        File[] children = file.listFiles();
        if (children != null) {
            for (File child : children) {
                if (child.isFile()) {
                    if (child.getName().endsWith(".xml") && !child.getName().equals("map.xml")) {
                        includes.put(child.getName(), child);
                    }
                } else
                    loadIncludes(child);
            }
        }
    }

    private List<LoadedMap> loadMapsIn(File file) {
        List<LoadedMap> result = new LinkedList<>();
        if (file == null || file.listFiles() == null) {
            return result;
        }
        for (File map : file.listFiles()) {
            if (map.isFile() || map.list() == null) continue;
            if (Arrays.asList(map.list()).containsAll(requirements)) {
                try {
                    result.add(loadMap(map));
                } catch (Exception e) {
                    Bukkit.getLogger().log(Level.WARNING, "Failed to load map at " + map.getAbsolutePath());
                    if (Config.debug_displayMapLoadErrors) {
                        Bukkit.getLogger().log(Level.INFO, "Showing error, this can be disabled in the config: ");
                        e.printStackTrace();
                    }
                }
            } else {
                result.addAll(loadMapsIn(map));
            }
        }
        return result;
    }

    private LoadedMap loadMap(File map) throws Exception {
        Document xml = DomUtil.parseMap(this, new File(map.getPath() + "/map.xml"));
        String name = xml.getRootElement().getChild("name").getTextNormalize();
        String version = xml.getRootElement().getChild("version").getTextNormalize();
        String objective = xml.getRootElement().getChild("objective").getTextNormalize();
        List<Contributor> authors = new ArrayList<>();
        for (Element authorsElement : xml.getRootElement().getChildren("authors")) {
            for (Element author : authorsElement.getChildren()) {
                authors.add(parseContributor(author));
            }
        }
        List<Contributor> contributors = new ArrayList<>();
        for (Element contributorsElement : xml.getRootElement().getChildren("contributors")) {
            for (Element contributor : contributorsElement.getChildren()) {
                contributors.add(parseContributor(contributor));
            }
        }
        List<String> rules = new ArrayList<>();
        for (Element rulesElement : xml.getRootElement().getChildren("rules")) {
            for (Element rule : rulesElement.getChildren()) {
                rules.add(rule.getTextNormalize());
            }
        }
        int maxPlayers = 0;
        for (Element teams : xml.getRootElement().getChildren("teams")) {
            for (Element team : teams.getChildren()) {
                maxPlayers = maxPlayers + Numbers.parseInt(team.getAttributeValue("max"));
            }
        }
        return new LoadedMap(name, version, objective, authors, contributors, rules, maxPlayers, map);
    }

    /**
     * @return Returns all loaded maps
     */
    public List<LoadedMap> getLoaded() {
        return loaded;
    }

    private static Contributor parseContributor(Element element) {
        if (element.getAttributeValue("uuid") != null) {
            return new Contributor(UUID.fromString(element.getAttributeValue("uuid")), element.getAttributeValue("contribution"));
        } else return new Contributor(element.getText(), element.getAttributeValue("contribution"));
    }

    @Override
    public String toString() {
        return this.getClass().getSimpleName() + " id:" + id + ", Path:" + path;
    }

    @Override
    public boolean equals(Object other) {
        return other.getClass().equals(this.getClass()) && ((Repository) other).path.equals(this.path);
    }

}