package com.example.Controllers;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.example.Exceptions.BaseException;
import com.example.Models.Program.ProgramState;
import com.example.Models.Values.IValue;
import com.example.Models.Values.ReferenceValue;
import com.example.Repositories.IRepository;

public class Controller {
    private IRepository repository;
    private boolean displayFlag;
    private ExecutorService executor;

    public Controller(IRepository repository, boolean displayFlag) {
        this.repository = repository;
        this.displayFlag = displayFlag;
    }
    public List<ProgramState> getProgramStates(){
        return repository.getProgramStates();
    }
    public List<ProgramState> removeCompletedPrograms(List<ProgramState> programStates) {
        return programStates.stream().filter(p -> p.isNotCompleted()).collect(Collectors.toList());
    }

    public void oneStepForAllProgramStates(List<ProgramState> programStates) {
        programStates = programStates.stream().filter(p -> p != null).collect(Collectors.toList());
        programStates.forEach(programState -> {
            try{
                repository.logProgramStateExecution(programState);
            }
            catch(BaseException e){
                System.out.println(e.getMessage());
            }
        });
        List<Callable<ProgramState>> callList = programStates.stream().map((ProgramState p) -> (Callable<ProgramState>)(() -> {
            try{
                return p.oneStep();
            }
            catch(BaseException e){
                System.out.println(e.getMessage());
            }
            return null;
        })).filter(c -> c != null).collect(Collectors.toList());
        List<ProgramState> newProgramStates = new ArrayList<ProgramState>();
        try{
        newProgramStates = executor.invokeAll(callList).stream().map(future -> {
            try {
                return future.get();
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
            return null;
        }).filter(p -> p != null).collect(Collectors.toList());
        }
        catch(InterruptedException e){
            System.out.println(e.getMessage());
        }
        programStates.addAll(newProgramStates);
        programStates.forEach(programState -> {
            try{
                repository.logProgramStateExecution(programState);
            }
            catch(BaseException e){
                System.out.println(e.getMessage());
            }
        });
        repository.setProgramStates(programStates);
    }

    public void oneStepAll(){
        executor = Executors.newFixedThreadPool(2);
        List<ProgramState> programStates = removeCompletedPrograms(repository.getProgramStates());
        programStates.get(0).getHeap().setContent(this.garbageCollector(this.getAddressesFromSymbolTable(programStates.stream().map(programState -> programState.getSymbolTable().getContent()).collect(Collectors.toList()), programStates.get(0).getHeap().getContent()), programStates.get(0).getHeap().getContent()));
        oneStepForAllProgramStates(programStates);
        executor.shutdownNow();
        //repository.setProgramStates(programStates);
    }

    public void allSteps() {
        executor = Executors.newFixedThreadPool(2);
        List<ProgramState> programStates = removeCompletedPrograms(repository.getProgramStates());
        while(programStates.size() > 0){
            programStates.get(0).getHeap().setContent(this.garbageCollector(this.getAddressesFromSymbolTable(programStates.stream().map(programState -> programState.getSymbolTable().getContent()).collect(Collectors.toList()), programStates.get(0).getHeap().getContent()), programStates.get(0).getHeap().getContent()));
            oneStepForAllProgramStates(programStates);
            programStates = removeCompletedPrograms(repository.getProgramStates());
        }
        executor.shutdownNow();
        repository.setProgramStates(programStates);
    }

    public Hashtable<Integer, IValue> garbageCollector(Set<Integer> symbolTable, Hashtable<Integer, IValue> heap) {
        return heap.entrySet().stream().filter(e -> symbolTable.contains(e.getKey()))
                .collect(Hashtable::new, (m, e) -> m.put(e.getKey(), e.getValue()), Hashtable::putAll);
    }

    public Set<Integer> getAddressesFromSymbolTable(List<Collection<IValue>> symbolTablesValues, Hashtable<Integer, IValue> heap) {
        return symbolTablesValues.stream().flatMap(Collection::stream).filter(v -> v instanceof ReferenceValue)
                .flatMap(v -> Stream.iterate(((ReferenceValue) v).getAddress(), Objects::nonNull, addr -> {
                    var value = heap.get(addr);
                    return value instanceof ReferenceValue ? ((ReferenceValue) value).getAddress() : null;
                })).distinct().collect(Collectors.toSet());
    }
}
