package analyzer;

import analyzer.models.Client;
import analyzer.models.Hashtag;
import analyzer.models.User;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * This class evaluates results of the attacks on sets of subjects.
 */
public class Accuracy {
    private static final int threadPoolSize = 8;

    private static final int acceptRangeClientSet = 5;
    private static final int acceptRangeHashtagSet = 5;

    private static int counter;
    private static int totalHashtags = 1000;
    private static int totalClients = 1000;
    private static final int clientThreshold = 2;

    private static volatile int correct = 0;
    private static volatile int averageRounds = 0;
    private static volatile int averagePosts = 0;

    public static final String boundnamesPath = "C:\\Users\\Admin\\Desktop\\Skripts\\Thesis\\Repo\\local\\vm-mount\\logs\\" + Analyzer.logType + Analyzer.clientNo + "\\boundnames.txt" ;
    public static final List<String> identifiedLinks = new ArrayList<>();

    private static Analyzer A;

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        A = new Analyzer();
        //hashtagTraceAccuracyUserUnknown();
        //clientTraceAccuracyUserUnknown();
        clientTraceAccuracyUserKnown();
    }

    //Step: most popular hashtags -> for each hashtag calculate points -> for each client intersect sets -> for each client check result
    private static void hashtagTraceAccuracyUserUnknown() throws InterruptedException, ExecutionException {
        List<Hashtag> hashtagList = new ArrayList<>(ServerLogParser.hashtags.values());
        hashtagList.sort((h1, h2) -> Integer.compare(h2.getTotalPosts(), h1.getTotalPosts()));

        ExecutorService executor = Executors.newFixedThreadPool(threadPoolSize);
        List<Future<?>> tasks = new ArrayList<>();
        totalHashtags = Math.min(totalHashtags, ServerLogParser.hashtags.values().size());
        for(int i = 0; i < totalHashtags; i++) {
            Future<?> task = executor.submit(new CheckHashtagTask(hashtagList.get(i)));
            tasks.add(task);
        }
        for(Future<?> task : tasks)
            task.get();
        executor.shutdown();
        printResult(false);
    }

    //Step: set intersection -> calculate point ->
    private static void clientTraceAccuracyUserUnknown() throws InterruptedException, ExecutionException {
        List<Client> clientList = createClientSet(totalClients, clientThreshold);
        ExecutorService executor = Executors.newFixedThreadPool(threadPoolSize);
        List<Future<?>> tasks = new ArrayList<>();

        totalClients = Math.min(totalClients, clientList.size());
        for(int i = 0; i < totalClients; i++) {
            Future<?> task = executor.submit(new CheckClientTask(clientList.get(i)));
            tasks.add(task);
        }
        for(Future<?> task : tasks){
            task.get();
            counter++;
            float progress = (float) Math.round(((float) counter / (float) totalHashtags) * 1000.0F) / 10.0F;
            System.out.print("\r");
            System.out.print(progress + " % done");
        }
        executor.shutdown();
        printResult(false);
    }

    private static void clientTraceAccuracyUserKnown() throws InterruptedException, ExecutionException {
        List<Client> clientList = createClientSet(totalClients, clientThreshold);
        ExecutorService executor = Executors.newFixedThreadPool(threadPoolSize);
        List<Future<?>> tasks = new ArrayList<>();

        totalClients = Math.min(totalClients, clientList.size());
        for(int i = 0; i < totalClients; i++) {
            Future<?> task = executor.submit(new CheckClientTaskUserKnown(clientList.get(i)));
            tasks.add(task);
        }
        for(Future<?> task : tasks){
            task.get();
            counter++;
            float progress = (float) Math.round(((float) counter / (float) totalHashtags) * 1000.0F) / 10.0F;
            System.out.print("\r");
            System.out.print(progress + " % done");
        }
        executor.shutdown();
        printResult(true);
    }

    private static boolean checkClientHashtagLink(Client client, Hashtag hashtag) throws IOException {
        FileReader fr = new FileReader(boundnamesPath);
        BufferedReader br= new BufferedReader(fr);
        String line;
        while((line = br.readLine()) != null) {
            String[] elements = line.trim().split("\t");//userID client-ID

            if(elements[1].equals(client.getId())) {
                if(ServerLogParser.users.get(Long.parseLong(elements[0])).getHashtags().containsKey(hashtag)) {
                    br.close();
                    identifiedLinks.add(client.getId() + "-" + hashtag.getName());
                    return true;
                }
                break;
            }
        }
        br.close();
        return false;
    }

    private static boolean checkClientUserLink(Client client, User user) throws IOException {
        FileReader fr = new FileReader(boundnamesPath);
        BufferedReader br= new BufferedReader(fr);
        String line;
        while((line = br.readLine()) != null) {
            String[] elements = line.trim().split("\t");//userID client-ID

            if(Long.parseLong(elements[0]) == user.getId())
                return elements[1].equals(client.getId());
        }
        System.out.println("User not found");
        return false;
    }

    private static List<Client> createClientSet(int num, int threshold) {
        List<Client> result = new ArrayList<>();
        for(Client c : ClientLogParser.clients.values()) {
            if(result.size() == num)
                break;
            else {
                if(c.getTotalPosts() >= threshold)
                    result.add(c);
            }
        }
        if(result.size() < num)
            totalClients = result.size();
        return result;
    }


    private static void printResult(boolean userKnown) {
        System.out.println();
        if(!userKnown) {
            //System.out.println("acceptRangeClientSet: " + acceptRangeClientSet);
            //System.out.println("acceptRangeHashtagSet: " + acceptRangeHashtagSet);
            System.out.println("clientThreshold: " + clientThreshold);
            double result = ((double) correct / (double) totalClients) * 100.00;
            System.out.println(result + " % of " + totalClients);
            System.out.println("average most ranked: " + (double) averagePosts/(double)totalClients);
            System.out.print(identifiedLinks.size() + " identified links: ");
            for(String s: identifiedLinks) {
                System.out.print(s + " ");
            }
        } else  {
            double result = ((double) averageRounds / (double) totalClients);
            System.out.println(result + " rounds of " + totalClients);
        }
        System.out.println();
    }

    private static synchronized void incrementCorrect() {
        correct++;
    }

    private static synchronized void addToAverageRounds(int n) {
        averageRounds += n;
    }

    private static synchronized void addToAveragePosts(int n) {
        averagePosts += n;
    }

    private record CheckHashtagTask(Hashtag hashtag) implements Runnable {

        @Override
        public void run() {
            try {
                List<Map.Entry<Client, Integer>> lst = A.calculatePointsAllClients(hashtag, acceptRangeClientSet, false);
                for(int i = 0; i < acceptRangeClientSet; i++) {
                    for(Map.Entry<String, Integer> e: A.intersectHashtags(lst.get(i).getKey(), acceptRangeHashtagSet, false))
                        if(e.getKey().equals(hashtag.getName())) {
                            if(checkClientHashtagLink(lst.get(i).getKey(), hashtag)) {
                                incrementCorrect();
                                break;
                            }
                        }
                }
                counter++;
                float progress = (float) Math.round(((float) counter / (float) totalHashtags) * 1000.0F) / 10.0F;
                System.out.print("\r");
                System.out.print(progress + " % done");
            } catch (InterruptedException | IOException | ExecutionException e) {
                e.printStackTrace();
            }
        }
    }

    private record CheckClientTask(Client client) implements Runnable {

        @Override
        public void run() {
            try {
                List<Map.Entry<String, Integer>> intersection = A.intersectHashtags(client, 1, false);
                intersection.removeIf(e -> e.getValue() < 0);
                Hashtag[] hashtags = new Hashtag[intersection.size()];
                for(int i = 0; i < hashtags.length; i++)
                    hashtags[i] = ServerLogParser.hashtags.get(intersection.get(i).getKey());

                List<Map.Entry<Hashtag, Integer>> points = A.calculatePointsGivenHashtags(client, hashtags, 1, false);
                addToAveragePosts(points.size());
                for(Map.Entry<Hashtag, Integer> e : points) {
                    boolean identified = checkClientHashtagLink(client, e.getKey());
                    if(identified){
                        incrementCorrect();
                        return;
                    }
                }

            } catch (InterruptedException | ExecutionException | IOException e) {
                e.printStackTrace();
            }
        }
    }

    private record CheckClientTaskUserKnown(Client client) implements Runnable {

        @Override
        public void run() {
            int rounds = A.intersectUsers(client, 1, false);
            if (rounds == -1) {
                totalClients--;
            } else {
                addToAverageRounds(rounds);
            }
        }
    }
}
