package org.unicode.cldr.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.common.collect.Multimap;
import com.ibm.icu.impl.Relation;
import com.ibm.icu.util.TimeZone;

public class Containment {
    private static final SupplementalDataInfo supplementalData = SupplementalDataInfo.getInstance();
    static final Relation<String, String> containmentCore = supplementalData
        .getContainmentCore();
    static final Set<String> continents = containmentCore.get("001");
    static final Set<String> subcontinents;
    static {
        LinkedHashSet<String> temp = new LinkedHashSet<String>();
        for (String continent : continents) {
            temp.addAll(containmentCore.get(continent));
        }
        subcontinents = Collections.unmodifiableSet(temp);
    }
    static final Relation<String, String> containmentFull = supplementalData
        .getTerritoryToContained();
    static final Relation<String, String> containedToContainer = (Relation<String, String>) Relation
        .of(new HashMap<String, Set<String>>(),
            HashSet.class)
            .addAllInverted(containmentFull)
            .freeze();

    static final Relation<String, String> leavesToContainers;
    static {
        leavesToContainers = Relation
            .of(new HashMap<String, Set<String>>(),
                HashSet.class);
        // for each container, get all of its leaf nodes
        Set<String> containers = supplementalData.getContainers();
        for (String s : containers) {
            HashSet<String> leaves = new HashSet<String>();
            addLeaves(s, leaves, containers);
            leavesToContainers.putAll(leaves, s);
        }
        leavesToContainers.freeze();
//        for (Entry<String, Set<String>> e : leavesToContainers.keyValuesSet()) {
//            System.out.println(e.getKey() + " " + e.getValue());
//        }
    }

    static final Relation<String, String> containedToContainerCore = (Relation<String, String>) Relation
        .of(new HashMap<String, Set<String>>(),
            HashSet.class)
            .addAllInverted(containmentCore)
            .freeze();
    static final Map<String, Integer> toOrder = new LinkedHashMap<String, Integer>();
    static int level = 0;
    static int order;
    static {
        initOrder("001");
        // Special cases. Cyprus is because it is in the wrong location because it gets picked up in the EU.
        resetOrder("003", "021");
        resetOrder("419", "005");
        resetOrder("CY", "BH");
    }

    // static Map<String, String> zone2country = StandardCodes.make().getZoneToCounty();

    public static String getRegionFromZone(String tzid) {
        if ("Etc/Unknown".equals(tzid)) {
            return "001";
        }
        try {
            return TimeZone.getRegion(tzid);
        } catch (IllegalArgumentException e) {
            return "ZZ";
        }
        // return zone2country.get(source0);
    }

    private static void addLeaves(String s, Set<String> target, Set<String> nonLeaf) {
        Set<String> contained = supplementalData.getContained(s);
        if (contained == null) {
            return;
        }
        for (String child : contained) {
            if (!nonLeaf.contains(child)) {
                target.add(child);
            } else {
                addLeaves(child, target, nonLeaf);
            }
        }
    }

    public static String getContainer(String territory) {
        Set<String> containers = containedToContainerCore.get(territory);
        if (containers == null) {
            containers = containedToContainer.get(territory);
        }
        String container = containers != null
            ? containers.iterator().next()
                : territory.equals("001") ? "001" : "ZZ";
            return container;
    }

    /**
     * Return all the containers, including deprecated.
     * @param territory
     * @return
     */
    public static Set<String> getContainers(String territory) {
        return containedToContainer.get(territory);
    }

    /**
     * Return the Continent containing the territory, or 001 if the territory is 001, otherwise ZZ
     *
     * @param territory
     */
    public static String getContinent(String territory) {
        while (true) {
            if (territory == null
                || territory.equals("001")
                || territory.equals("ZZ")
                || continents.contains(territory)) {
                return territory;
            }
            String newTerritory = getContainer(territory);
            if (newTerritory == null) {
                return territory;
            }
            territory = newTerritory;
        }
    }

    /**
     * Return the Subcontinent containing the territory, or the continent if it is a continent, or
     * 001 if it is 001, otherwise ZZ.
     *
     * @param territory
     */
    public static String getSubcontinent(String territory) {
        while (true) {
            if (territory.equals("001")
                || territory.equals("ZZ")
                || continents.contains(territory)
                || subcontinents.contains(territory)) {
                return territory;
            }
            territory = getContainer(territory);
        }
    }

    public static int getOrder(String territory) {
        Integer temp = toOrder.get(territory);
        return temp != null ? temp.intValue() : level;
    }

    private static void initOrder(String territory) {
        if (!toOrder.containsKey(territory)) {
            toOrder.put(territory, ++level);
        }
        Set<String> contained = containmentFull.get(territory);
        if (contained == null) {
            return;
        }
        for (String subitem : contained) {
            if (!toOrder.containsKey(subitem)) {
                toOrder.put(subitem, ++level);
            }
        }
        for (String subitem : contained) {
            initOrder(subitem);
        }
    }

    private static void resetOrder(String newTerritory, String oldTerritory) {
        // final Integer newOrder = toOrder.get(newTerritory);
        // if (newOrder != null) {
        // throw new IllegalArgumentException(newTerritory + " already defined as " + newOrder);
        // }
        final Integer oldOrder = toOrder.get(oldTerritory);
        if (oldOrder == null) {
            throw new IllegalArgumentException(oldTerritory + " not yet defined");
        }
        toOrder.put(newTerritory, oldOrder);
    }

    public Set<String> getContinents() {
        return continents;
    }

    public Set<String> getSubontinents() {
        return subcontinents;
    }

    public static Set<List<String>> getAllDirected(Multimap<String, String> multimap, String lang) {
        LinkedHashSet<List<String>> result = new LinkedHashSet<List<String>>();
        getAllDirected(multimap, lang, new ArrayList<String>(), result);
        return result;
    }

    private static void getAllDirected(Multimap<String, String> multimap, String lang, ArrayList<String> target, Set<List<String>> targets) {
        target.add(lang);
        Collection<String> parents = multimap.get(lang);
        int size = parents.size();
        if (size == 0) {
            targets.add(target); 
        } else if (size == 1) {
            for (String parent : parents) {
                getAllDirected(multimap, parent, target, targets);
            }
        } else {
            for (String parent : parents) {
                getAllDirected(multimap, parent, (ArrayList<String>) target.clone(), targets);
            }
        }
    }

    /**
     * For each leaf region (eg "CO"), return all containers [019, 419, 005, 001]
     * @param leaf
     * @return
     */
    public static Set<String> leafToContainer(String leaf) {
        return leavesToContainers.get(leaf);
    }

    public static boolean isLeaf(String region) {
        return leavesToContainers.containsKey(region);
    }
}