// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.trustosm.util;

import static org.openstreetmap.josm.tools.I18n.tr;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.JOptionPane;

import org.bouncycastle.openpgp.PGPSignature;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.gui.MainApplication;
import org.openstreetmap.josm.plugins.trustosm.TrustOSMplugin;
import org.openstreetmap.josm.plugins.trustosm.data.TrustNode;
import org.openstreetmap.josm.plugins.trustosm.data.TrustOsmPrimitive;
import org.openstreetmap.josm.plugins.trustosm.data.TrustSignatures;
import org.openstreetmap.josm.plugins.trustosm.data.TrustWay;

public final class TrustAnalyzer {

    private TrustAnalyzer() {
        // Hide default constructors for utilities classes
    }

    public static void showManipulationWarning() {
        JOptionPane.showMessageDialog(MainApplication.getMainFrame(), tr("The Signature is broken!"), tr("Manipulation Warning"), JOptionPane.WARNING_MESSAGE);
    }

    public static double computeReputation(TrustOsmPrimitive trust, Map<String, List<PGPSignature>> textsigs) {
        /** just for simplicity - count all valid sigs */
        int count = 0;
        for (List<PGPSignature> siglist : textsigs.values()) {
            count += siglist.size();
        }
        return count;
    }

    public static boolean isTagRatingValid(TrustOsmPrimitive trust, String key, String signedPlaintext) {
        /** Rating is valid if signed plaintext matches the current plaintext */
        String currentSigtext = TrustOsmPrimitive.generateTagSigtext(trust.getOsmPrimitive(), key);
        return currentSigtext.equals(signedPlaintext);
    }

    public static void checkTag(TrustOsmPrimitive trust, String key) {
        Map<String, List<PGPSignature>> validRatings = new HashMap<>();

        TrustSignatures sigs;
        if ((sigs = trust.getSigsOnKey(key)) != null) {
            for (PGPSignature sig : sigs.getSignatures()) {
                /** Here we have a full rating
                 *  The first question: Is the Signature valid?
                 *  It could be manipulated...
                 * */
                String signedPlaintext = sigs.getSigtext(sig);
                if (TrustOSMplugin.gpg.verify(signedPlaintext, sig)) {
                    /** If it is valid...
                     * Second question: Is the rating valid?
                     */
                    if (isTagRatingValid(trust, key, signedPlaintext)) {
                        /** if the rating is good, we can try to compute a reputation value at the end
                         *  so we save the important rating stuff
                         */
                        if (validRatings.containsKey(signedPlaintext)) {
                            validRatings.get(signedPlaintext).add(sig);
                        } else {
                            List<PGPSignature> l = new ArrayList<>();
                            l.add(sig);
                            validRatings.put(signedPlaintext, l);
                        }

                        //if (sigs.getStatus() == TrustSignatures.SIG_UNKNOWN) sigs.setStatus(TrustSignatures.SIG_VALID);
                    } /*else {
                        sigs.setStatus(TrustSignatures.SIG_BROKEN);
                    }*/
                } else {
                    //sigs.setStatus(TrustSignatures.SIG_BROKEN);
                    showManipulationWarning();
                }
            }
            /** now we know which ratings are valid to compute a reputation */
            sigs.setReputation(computeReputation(trust, validRatings));
            /** if all available signatures are valid we can set the TrustSignatures status to valid */
            System.out.println(validRatings.size()+":"+sigs.countSigs());
            if (validRatings.size() == 1) sigs.setStatus(TrustSignatures.SIG_VALID);
            else sigs.setStatus(TrustSignatures.SIG_BROKEN);
        }
    }

    public static boolean isNodeRatingValid(TrustNode trust, String signedPlaintext, PGPSignature sig) {
        /** Rating is valid if Node from signed plaintext is inside Tolerance given in Signature */
        Node signedNode = TrustNode.generateNodeFromSigtext(signedPlaintext);
        Node currentNode = (Node) trust.getOsmPrimitive();
        double dist = signedNode.greatCircleDistance(currentNode);

        /** is distance between signed Node and current Node inside tolerance? */
        return dist <= TrustGPG.searchTolerance(sig);
    }

    /**
     * Check if the ratings made for a Node are valid for the current position of that node
     * and compute reputation.
     * @param trust    The current TrustNode with its ratings
     */
    public static void checkNode(TrustNode trust) {
        Map<String, List<PGPSignature>> validRatings = new HashMap<>();
        TrustSignatures sigs;
        if ((sigs = trust.getNodeSigs()) != null) {
            for (String signedPlaintext : sigs.getAllPlainTexts()) {
                for (PGPSignature sig : sigs.getSignaturesByPlaintext(signedPlaintext)) {
                    /** first thing: check signature */
                    if (TrustOSMplugin.gpg.verify(signedPlaintext, sig)) {
                        /** if signature is valid check rating */
                        if (isNodeRatingValid(trust, signedPlaintext, sig)) {
                            /** if the rating is good, we can try to compute a reputation value at the end
                             *  so we save the important rating stuff
                             */
                            if (validRatings.containsKey(signedPlaintext)) {
                                validRatings.get(signedPlaintext).add(sig);
                            } else {
                                List<PGPSignature> l = new ArrayList<>();
                                l.add(sig);
                                validRatings.put(signedPlaintext, l);
                            }

                            //if (sigs.getStatus() == TrustSignatures.SIG_UNKNOWN) sigs.setStatus(TrustSignatures.SIG_VALID);
                        } /*else {
                            sigs.setStatus(TrustSignatures.SIG_BROKEN);
                        }*/
                    } else {
                        //sigs.setStatus(TrustSignatures.SIG_BROKEN);
                        showManipulationWarning();
                    }
                }
            }
            /** now we know which ratings are valid to compute a reputation */
            sigs.setReputation(computeReputation(trust, validRatings));
            /** if all available signatures are valid we can set the TrustSignatures status to valid */
            if (validRatings.size() == 1) sigs.setStatus(TrustSignatures.SIG_VALID);
            else sigs.setStatus(TrustSignatures.SIG_BROKEN);
        }
    }

    /**
     * Check if the ratings made for a specific WaySegment are valid for the current form of that WaySegment
     */
    public static boolean isSegmentRatingValid(TrustWay trust, List<Node> nodes, String signedPlaintext, PGPSignature sig) {
        /** Rating is valid if Nodes from Segment of signed plaintext are inside Tolerance given in Signature */
        List<Node> signedSegment = TrustWay.generateSegmentFromSigtext(signedPlaintext);

        double tolerance = TrustGPG.searchTolerance(sig);

        for (int i = 0; i < 2; i++) {
            Node signedNode = signedSegment.get(i);
            Node currentNode = nodes.get(i);
            double dist = signedNode.greatCircleDistance(currentNode);
            if (dist > tolerance) return false;
        }
        return true;
    }

    /**
     * Check if there are ratings for a current WaySegment of a TrustWay
     * and if so, compute Reputation
     * @param trust the current TrustWay
     * @param seg the current WaySegment to check for reputation
     */
    public static void checkSegment(TrustWay trust, List<Node> nodes) {
        Map<String, List<PGPSignature>> validRatings = new HashMap<>();

        TrustSignatures sigs;
        if ((sigs = trust.getSigsOnSegment(nodes)) != null) {
            for (String signedPlaintext : sigs.getAllPlainTexts()) {
                for (PGPSignature sig : sigs.getSignaturesByPlaintext(signedPlaintext)) {
                    /** first thing: check signature */
                    if (TrustOSMplugin.gpg.verify(signedPlaintext, sig)) {
                        /** if signature is valid check rating */
                        if (isSegmentRatingValid(trust, nodes, signedPlaintext, sig)) {
                            /** if the rating is good, we can try to compute a reputation value at the end
                             *  so we save the important rating stuff
                             */
                            if (validRatings.containsKey(signedPlaintext)) {
                                validRatings.get(signedPlaintext).add(sig);
                            } else {
                                List<PGPSignature> l = new ArrayList<>();
                                l.add(sig);
                                validRatings.put(signedPlaintext, l);
                            }

                            //if (sigs.getStatus() == TrustSignatures.SIG_UNKNOWN) sigs.setStatus(TrustSignatures.SIG_VALID);
                        } /*else {
                            sigs.setStatus(TrustSignatures.SIG_BROKEN);
                        }*/
                    } else {
                        //sigs.setStatus(TrustSignatures.SIG_BROKEN);
                        showManipulationWarning();
                    }
                }
            }
            /** now we know which ratings are valid to compute a reputation */
            sigs.setReputation(computeReputation(trust, validRatings));
            /** if all available signatures are valid we can set the TrustSignatures status to valid */
            if (validRatings.size() == sigs.countSigs()) sigs.setStatus(TrustSignatures.SIG_VALID);
            else sigs.setStatus(TrustSignatures.SIG_BROKEN);
        }
    }

    public static void checkEverything(TrustOsmPrimitive trust) {
        /** check every single tag for reputation */
        for (String key : trust.getSignedKeys()) {
            checkTag(trust, key);
        }
        if (trust instanceof TrustNode) {
            /** check all reputation of this single Node */
            checkNode((TrustNode) trust);
        } else if (trust instanceof TrustWay) {
            TrustWay tw = (TrustWay) trust;
            /** check all reputation for every Segment of this Way */
            List<Node> wayNodes = ((Way) tw.getOsmPrimitive()).getNodes();
            for (int i = 0; i < wayNodes.size()-1; i++) {
                List<Node> nodes = new ArrayList<>();
                nodes.add(wayNodes.get(i));
                nodes.add(wayNodes.get(i+1));
                checkSegment(tw, nodes);
            }
        } /*else if (trust instanceof TrustRelation) {
            TrustRelation tr = (TrustRelation) trust;
        }*/
    }
}
