package analyzer.tests;

import analyzer.Analyzer;
import analyzer.ClientLogParser;
import analyzer.ServerLogParser;
import analyzer.models.Client;
import analyzer.models.Hashtag;

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.Scanner;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TestMain {
    private static final int threadPoolSize = 8;

    private static volatile int error = 0;
    private static volatile double sum = 0.0;
    private static volatile int max = Integer.MIN_VALUE, min = Integer.MAX_VALUE;

    private static volatile int correct = 0;
    private static volatile double avgSetSize = 0.0;

    private static Analyzer A;

    static List<Client> group1 = new ArrayList<>(); //>1000 messages
    static List<Client> group2 = new ArrayList<>(); //100-1000 messages
    static List<Client> group3 = new ArrayList<>(); //10-100 messages

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        A = new Analyzer();
        determineUserGroups();

        testMethod1();
        testMethod2();
    }

    public static void determineUserGroups() {
        int numOfClients = ClientLogParser.clients.values().size();
        ClientLogParser.clients.values().forEach(c -> {
            if(c.getTotalPosts() >= 10 && c.getTotalPosts() < 100)
                group3.add(c);
            else if(c.getTotalPosts() >= 100 && c.getTotalPosts() < 1000)
                group2.add(c);
            else if(c.getTotalPosts() >= 1000)
                group1.add(c);
        });
        System.out.println(">= 1000: " + (double) group1.size()*100/numOfClients + "%" + " (" + group1.size() + " users)");
        System.out.println("100-1000: " + (double) group2.size()*100/numOfClients + "%" + " (" + group2.size() + " users)");
        System.out.println("10-100: " + (double) group3.size()*100/numOfClients + "%" + " (" + group3.size() + " users)");
        System.out.println();
    }

    public static void testMethod1() throws ExecutionException, InterruptedException {
        System.out.println("Method 1 on Group ");
        System.out.print(">= 1000: ");
        testMethodOneOnGroup(group1);
        System.out.println();
        System.out.print("100-1000: ");
        testMethodOneOnGroup(group2);
        System.out.println();
        System.out.print("10-100: ");
        testMethodOneOnGroup(group3);
        System.out.println();
    }

    private static void testMethodOneOnGroup(List<Client> group) throws ExecutionException, InterruptedException {
        int counter = 0;
        int correct;

        double averageGroup;
        System.out.println("clientList size " + group.size());
        ExecutorService executor = Executors.newFixedThreadPool(threadPoolSize);
        List<Future<?>> tasks = new ArrayList<>();

        for (Client client : group) {
            Future<?> task = executor.submit(new ExcludeUsersTask(client));
            tasks.add(task);
        }
        for(Future<?> task : tasks){
            task.get();
            counter++;
            float progress = (float) Math.round(((float) counter / (float) group.size()) * 1000.0F) / 10.0F;
            System.out.print("\r");
            System.out.print(progress + " % done");
        }
        executor.shutdown();
        correct = group.size() - error;
        averageGroup = sum /correct;
        System.out.println(" correct: " + correct + " \naverage no of rounds: " + averageGroup);
        System.out.println("Min: " + min + "\nAvg: " + averageGroup + "\nMax: " + max);
    }

    public static void testMethod2() throws ExecutionException, InterruptedException {
        System.out.println("Method 2 on Group ");
        System.out.print(">= 1000: ");
        testMethodTwoOnGroup(group1);
        System.out.println();
        System.out.print("100-1000: ");
        testMethodTwoOnGroup(group2);
        System.out.println();
        System.out.print("10-100: ");
        testMethodTwoOnGroup(group3);
        System.out.println();
    }

    private static void testMethodTwoOnGroup(List<Client> group) throws ExecutionException, InterruptedException {
        avgSetSize = 0.0;
        correct = 0;
        int counter = 0;
        double averageGroup;
        System.out.println("clientList size " + group.size());
        ExecutorService executor = Executors.newFixedThreadPool(threadPoolSize);
        List<Future<?>> tasks = new ArrayList<>();

        for (Client client : group) {
            Future<?> task = executor.submit(new RankTopicsTask(client));
            tasks.add(task);
        }
        for(Future<?> task : tasks){
            task.get();
            counter++;
            float progress = (float) Math.round(((float) counter / (float) group.size()) * 1000.0F) / 10.0F;
            System.out.print("\r");
            System.out.print(progress + " % done");
        }
        averageGroup = avgSetSize / group.size();
        System.out.print(" correct: " + correct + " average set size: " + averageGroup);
        executor.shutdown();
    }

    private record ExcludeUsersTask(Client client) implements Runnable {

        private static synchronized void incrementError() {
            error++;
        }

        private static synchronized void incrementAvg(int num) {
            if(num > max)
                max = num;
            if(num < min)
                min = num;
            sum += num;
        }

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

    private record RankTopicsTask(Client client) implements Runnable {
        public static final String boundnamesPath = Analyzer.logPath + "\\boundnames.txt" ;

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

        private static synchronized void incrementAvgSetSize(int num) {
            avgSetSize += num;
        }

        @Override
        public void run() {
            List<Map.Entry<String, Integer>> intersection = A.intersectHashtags(client, 10, false);
            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>> results;
            try {
                results = A.calculatePointsGivenHashtags(client, hashtags, 1, false);
                incrementAvgSetSize(results.size());
                results.forEach(e -> {
                    try {
                        check(e.getKey());
                    } catch (IOException ioException) {
                        ioException.printStackTrace();
                    }
                });

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

        private void check(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(String.valueOf(elements[0])).getHashtags().containsKey(hashtag)) {
                        br.close();
                        incrementCorrect();
                    }
                    break;
                }
            }
            br.close();
        }
    }
}
