import com.liferay.faces.portal.el.internal.Liferay
import cz.vrk.pi.crm.model.model.Asset
import cz.vrk.pi.crm.model.service.AssetLocalServiceUtil
import cz.vrk.pi.crm.api.asset.AssetUrlCreator
import cz.vrk.pi.dam.custom.EFileEntry
import org.apache.commons.collections4.SetUtils
import vrk.pi.shared.ViewContext
import com.liferay.portal.kernel.log.Log
import com.liferay.portal.kernel.log.LogFactoryUtil
import java.time.Instant

def log = LogFactoryUtil.getLog("rodokmen")

class DogNode {
    Long id;
    String name;
    String url;
    DogNode matka;
    DogNode otec;
    String pictureUrlThumbnail;
    String pictureUrl;
    String pictureTitle;
    Boolean isAlive = true;
    Boolean duplicity = false;
    List<String> extraAttr;
    List<String> healthResults;

    boolean equals(o) {
        if (this.is(o)) return true
        if (getClass() != o.class) return false

        DogNode dogNode = (DogNode) o

        if (id != dogNode.id) return false

        return true
    }

    int hashCode() {
        return (id != null ? id.hashCode() : 0)
    }
}

def startTime = System.currentTimeMillis()

try {
    Settings.organizationName = viewContext.organization.name
    NodeTreeLoader.urlCreator = urlCreator
    NodeTreeLoader.viewContext = viewContext
} catch (MissingPropertyException ignored) {
    //ignore zkratka tam není = null
}

def pes = NodeTreeLoader.createNode(asset, 1, 5)
NodeTreeLoader.findDuplicities(pes, 1, 5)

// def wrightPes = NodeTreeLoader.createNode(asset, 1, 5)

data = ["matka" : pes.matka,
        "otec"  : pes.otec,
        "wright": WrightComputer.wright(new DogNode(id: asset.id, name: asset.name, matka: pes.matka, otec: pes.otec), 1, 5),
        "showLifeStatus" : Settings.getShowLifeStatus(),
        "imagePath" : NodeTreeLoader.viewContext.themeDisplay.getPortalURL() + NodeTreeLoader.viewContext.themeDisplay.getPathThemeRoot() + "/primefaces/outputpanel/images/loading.gif",
        "extraAttrRodokmen" : Settings.getExtraAttr(),
        "extraAttrNameRodokmen" : Settings.getExtraAttrName(),
        "oneRowAttrNameRodokmen" : Settings.getOneRowAttrName(),
        "heathResults" : Settings.getHealthResults(),
]

log.info("\$EXTRA_HTML_ASSET_LOG HTML: 'RODOKMEN ACCESS' ORG: '" + viewContext.organization.name + "' DATETIME: '"+ Instant.now()+"' TIME_MS: '" + (System.currentTimeMillis() - startTime) + "'")

static class Settings {
    static String organizationName;
    static String KCHBO = 'KCHBO, z.s.';
    static String KPPPK = 'Klub přátel psů pražských krysaříků, z.s.';
    static String KSSP = 'Klub švýcarských salašnických psů z.s.';
    static String RKCZ = 'Retriever klub CZ - Spolek';
    static String KCHS = 'Klub chovatelů špiců, z.s.';

    // nastavení pro jednotlivé organizace
    static Boolean getShowLifeStatus() {
        if (organizationName == KCHBO) {
            return true;
        }
        return false;
    }

    static Boolean getIsAlive(Asset asset) {
        if (organizationName == KCHBO) {
            return !asset.attributes["Po smrti"];
        }
        return false;
    }

    static Boolean getExtraAttr() {
        if (organizationName == KCHS) {
            return true;
        }
        return false;
    }

    static List<String> getExtraAttrName() {
        List<String> names = new ArrayList();
        if (organizationName == KCHS) {
            names.add("Barva");
            names.add("Výška (v cm)");
            names.add("PL/DKK");
        }
        return names;
    }

    static List<String> getExtraAssetAttrName() {
        List<String> names = new ArrayList();
        if (organizationName == KCHS) {
            names.add("Barva");
            names.add("Výška (v cm)");
            names.add("PL/DKK");
        }
        return names;
    }

    static List<String> getOneRowAttrName() {
        List<String> names = new ArrayList();
        return names;
    }

    static Boolean getHealthResults() {
        if (organizationName == KSSP) {
            return true;
        }
        return false;
    }

    static List<String> getHealthResults(Asset asset) {
        List<String> result = new ArrayList()
        if (organizationName == KSSP) {
            if (asset.attributes["EU"] != null && asset.attributes["EU"] != "-") {
                result.add("EU: " + asset.attributes["EU"] + ", ");
            }
            if (asset.attributes["Oční vyšetření"] != null && getValueFromMap(asset.attributes["Oční vyšetření"], "Výsledek") != "") {
                result.add("OV: " + getValueFromMap(asset.attributes["Oční vyšetření"], "Výsledek") + ", ");
            }
            if (asset.attributes["SH"] != null && asset.attributes["SH"] == true) {
                result.add("SH: Ano, ");
            }
            if (asset.attributes["DM exon 1"] != null && asset.attributes["DM exon 1"] != "-") {
                result.add("DM1: " + asset.attributes["DM exon 1"] + ", ");
            }
            if (asset.attributes["DM exon 2"] != null && asset.attributes["DM exon 2"] != "-") {
                result.add("DM2: " + asset.attributes["DM exon 2"] + ", ");
            }
            if (asset.attributes["Vloha pro dlouhou srst"] != null && asset.attributes["Vloha pro dlouhou srst"] != "-") {
                result.add("Vloha DS: " + asset.attributes["Vloha pro dlouhou srst"] + ", ");
            }
            if (asset.attributes["Vyšetření srdce"] != null && asset.attributes["Vyšetření srdce"] == true) {
                result.add("VS: Ano, ");
            }
            if (asset.attributes["PRA"] != null && asset.attributes["PRA"] != "-") {
                result.add("PRA: " + asset.attributes["PRA"] + ", ");
            }
            String hd = "HD: ";
            if (asset.attributes["HD"] != null && asset.attributes["HD"] != "-") {
                hd += asset.attributes["HD"];
            }
            if (asset.attributes["HD - detail"] != null) {
                hd += formatMapOutput(asset.attributes["HD - detail"]) + ", ";
            } else {
                hd += "-, ";
            }
            if (hd != "HD: -, ") {
                result.add(hd);
            }
            String ed = "ED: ";
            if (asset.attributes["ED"] != null && asset.attributes["ED"] != "-") {
                ed += asset.attributes["ED"];
            }
            if (asset.attributes["ED - detail"] != null) {
                ed += formatMapOutput(asset.attributes["ED - detail"]) + ", ";
            } else {
                ed += "-, ";
            }
            if (ed != "ED: -, ") {
                result.add(ed);
            }
            if (asset.attributes["OCD"] != null && asset.attributes["OCD"] != "-") {
                result.add("OCD: " + asset.attributes["OCD"] + ", ");
            }
            if (asset.attributes["LTV"] != null && asset.attributes["LTV"] != "-") {
                result.add("LTV: " + asset.attributes["LTV"] + ", ");
            }
            String pl = "PL: ";
            if (asset.attributes["Patela Luxace - Levá"] != null) {
                pl += asset.attributes["Patela Luxace - Levá"] + "|";
            } else {
                pl += "-|"
            }
            if (asset.attributes["Patela Luxace - Pravá"] != null) {
                pl += asset.attributes["Patela Luxace - Pravá"] + ", ";
            } else {
                pl += "-, "
            }
            if (pl != "PL: -|-, ") {
                result.add(pl);
            }
        }

        if (result.size() > 0) {
            result[result.size() - 1] = result[result.size() - 1].substring(0, result[result.size() - 1].length() - 2);
        }

        return result;
    }

    static String formatMapOutput(Map<String, String> map) {
        StringBuilder output = new StringBuilder();
        int size = map.size();
        int count = 0;
        for (Map.Entry<String, String> entry : map.entrySet()) {
            String value = entry.getValue();
            output.append(value);
            if (++count < size) {
                output.append(" | ");
            }
        }

        return output.toString();
    }

    static String getValueFromMap(Map<String, String> map, String key) {
        for (Map.Entry<String, String> entry : map.entrySet()) {
            String value = entry.getValue();
            if (key.equals(entry.getKey())) {
                return value;
            }
        }

        return "";
    }
}

static class NodeTreeLoader {
    static AssetUrlCreator urlCreator;
    static ViewContext viewContext;
    static Map<Long, Integer> dogs = new HashMap<Long, Integer>();
    static ArrayList<String> nameAttr = Settings.getExtraAssetAttrName();

    static String calculateAge(Date birthday, Date deathday) {
        if (birthday == null || deathday == null)
            return;

        Calendar deathDate = Calendar.getInstance();
        Calendar birthDate = Calendar.getInstance();
        birthDate.setTime(birthday);
        deathDate.setTime(deathday);

        int years = deathDate.get(Calendar.YEAR) - birthDate.get(Calendar.YEAR);
        int months = deathDate.get(Calendar.MONTH) - birthDate.get(Calendar.MONTH);

        if (months < 0) {
            years--;
            months += 12;
        }

        if (years == 0) {
            return months + " m";
        } else {
            return years + " r + " + months + " m";
        }
    }

    static String calculateAge(Date birthday) {
        Calendar today = Calendar.getInstance();
        Calendar birthDate = Calendar.getInstance();
        birthDate.setTime(birthday);

        int years = today.get(Calendar.YEAR) - birthDate.get(Calendar.YEAR);
        int months = today.get(Calendar.MONTH) - birthDate.get(Calendar.MONTH);

        if (months < 0) {
            years--;
            months += 12;
        }

        if (years == 0) {
            return "(" + months + " m)";
        } else {
            return "(" + years + " r + " + months + " m)";
        }
    }

    static DogNode createNode(Asset asset, int depth, int maxDepth) {
        String pictureUrlThumbnail = null;
        String pictureUrl = null;
        String pictureTitle = null;
        List<String> attrs = new ArrayList();

        if (asset == null) {
            if (depth == maxDepth) {
                return new DogNode();
            } else {
                return new DogNode(matka: createNode(null, depth + 1, maxDepth), otec: createNode(null, depth + 1, maxDepth))
            }
        } else {
            if (dogs[asset.id] == null) {
                dogs[asset.id] = 0;
            } else {
                dogs[asset.id] += 1;
            }

            if (AssetLocalServiceUtil.getAssetPicture(asset) != null) {
                pictureUrlThumbnail = AssetLocalServiceUtil.getAssetPicture(asset).getThumbnailURL(viewContext.themeDisplay);
                pictureUrl = AssetLocalServiceUtil.getAssetPicture(asset).getFullURL(viewContext.themeDisplay);
                pictureTitle = AssetLocalServiceUtil.getAssetPicture(asset).getTitle();
            }

            for (String name in nameAttr) {
                if (name == "Věk dožití") {
                    attrs.add(calculateAge(asset.attributes["Datum narození"], asset.attributes["Datum úhynu"]));
                } else {
                    if (asset.attributes[name] instanceof Date) {
                        String attr = (Date) asset.attributes[name];
                        attrs.add(attr);
                    } else {
                        String attr = asset.attributes[name];
                        attrs.add(attr);
                    }
                }
            }

            if (depth == maxDepth) {
                return new DogNode(id: asset.id,
                        name: asset.name + ' (' + asset.number + ")",
                        url: urlCreator != null ? urlCreator.getAssetDetailUrl(asset) : null,
                        pictureUrlThumbnail: pictureUrlThumbnail,
                        pictureUrl: pictureUrl,
                        pictureTitle: pictureTitle,
                        isAlive: Settings.getIsAlive(asset),
                        extraAttr: attrs,
                        healthResults: Settings.getHealthResults(asset)
                )
            }
        }

        long matkaId = asset.attributes["Matka"] == null ? 0L : asset.attributes["Matka"];
        long otecId = asset.attributes["Otec"] == null ? 0L : asset.attributes["Otec"];

        def matka = AssetLocalServiceUtil.fetchAsset(matkaId)
        def otec = AssetLocalServiceUtil.fetchAsset(otecId)


        return new DogNode(id: asset.id,
                name: asset.name + ' (' + asset.number + ")",
                url: urlCreator != null ? urlCreator.getAssetDetailUrl(asset) : null,
                matka: createNode(matka, depth + 1, maxDepth),
                otec: createNode(otec, depth + 1, maxDepth),
                pictureUrlThumbnail: pictureUrlThumbnail,
                pictureUrl: pictureUrl,
                pictureTitle: pictureTitle,
                isAlive: Settings.getIsAlive(asset),
                extraAttr: attrs,
                healthResults: Settings.getHealthResults(asset)
        )
    }

    static void findDuplicities(DogNode node, int depth, int maxDepth) {
        if (node.id == null) {
            return
        }

        node.duplicity = isDuplicity(node.id)

        if (depth == maxDepth) {
            return;
        }

        findDuplicities(node.matka, depth + 1, maxDepth)
        findDuplicities(node.otec, depth + 1, maxDepth)
    }

    static Boolean isDuplicity(Long asset) {
        return dogs[asset] > 0
    }

}

static class WrightComputer {

    static double wright(DogNode node, int depth, int maxDepth) {
        if (node.id == null) {
            return 0;
        }
        if (node.matka == null || node.otec == null) {
            def asset = AssetLocalServiceUtil.fetchAsset(node.id);
            if (asset == null) {
                return 0;
            }
            node = NodeTreeLoader.createNode(asset, 1, maxDepth);
        }
        Map<DogNode, List<String>> motherSideDogsWithPaths = new HashMap<DogNode, List<String>>();
        traverse(node.matka, '', maxDepth) { dog, path ->
            if (motherSideDogsWithPaths[dog] == null) {
                motherSideDogsWithPaths[dog] = [];
            }
            motherSideDogsWithPaths[dog].add(path);
        };

        Map<DogNode, List<String>> fatherSideDogsWithPaths = new HashMap<DogNode, List<String>>();
        traverse(node.otec, '', maxDepth) { dog, path ->
            if (fatherSideDogsWithPaths[dog] == null) {
                fatherSideDogsWithPaths[dog] = [];
            }
            fatherSideDogsWithPaths[dog].add(path);
        };
        List<ImmediateWrightResult> countableResults = getCountableResults(node.matka, node.otec, motherSideDogsWithPaths, fatherSideDogsWithPaths)
        double sum = 0;
        for (def countableResult : countableResults) {

            sum += Math.pow(0.5, countableResult.motherGeneration + countableResult.fatherGeneration + 1) * (1 + wright(countableResult.dogNode, Math.max(countableResult.motherGeneration, countableResult.fatherGeneration),
                    maxDepth
            ));
        }
        return sum;
    }

    private static class ImmediateWrightResult {
        DogNode dogNode;
        int motherGeneration;
        int fatherGeneration;
    }

    private static List<ImmediateWrightResult> getCountableResults(DogNode matka, DogNode otec, Map<DogNode, List<String>> motherSideDogsWithPaths, Map<DogNode, List<String>> fatherSideDogsWithPaths) {
        def sameDogs = SetUtils.intersection(motherSideDogsWithPaths.keySet(), fatherSideDogsWithPaths.keySet());
        def results = []
        for (def dog : sameDogs) {
            List<String> motherPaths = motherSideDogsWithPaths[dog];
            for (def motherPath : motherPaths) {
                List<String> fatherPaths = fatherSideDogsWithPaths[dog];
                for (def fatherPath : fatherPaths) {
                    def motherNodeLine = nodesInPath(matka, motherPath);
                    def fatherNodeLine = nodesInPath(otec, fatherPath);
                    def isIncluded = motherNodeLine.size() == 1 || fatherNodeLine.size() == 1 ||
                            motherNodeLine[motherNodeLine.size() - 2] != fatherNodeLine[fatherNodeLine.size() - 2];
                    if (isIncluded) {
//                        console.log(`Including ${motherNodeLine[motherNodeLine.length - 1].name}, fatherPath: ${fatherPath}, motherPath: ${motherPath}`);
                        results.push(new ImmediateWrightResult(dogNode: motherNodeLine.last(),
                                motherGeneration: motherPath.size(),
                                fatherGeneration: fatherPath.size()));
                    }
                }
            }
        }
        return results
    }


    private static def traverse(node, path, maxDepth, visitor) {
        if (node == null || node.id == null) {
            return;
        }
        visitor(node, path);
        if (maxDepth == path.size()) {
            return;
        }
        traverse(node.matka, path + 'M', maxDepth, visitor);
        traverse(node.otec, path + 'F', maxDepth, visitor);
    }

    private static List<DogNode> nodesInPath(DogNode parent, String path) {
        def nodes = [parent];
        def node = parent;
        for (def i = 0; i < path.length(); i++) {
            char pathPart = path.charAt(i);
            if (pathPart == 'M') {
                node = node.matka;
            } else {
                node = node.otec;
            }
            nodes.push(node);
        }
        return nodes;
    }


}