package com.example;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import com.example.Controllers.Controller;
import com.example.Exceptions.BaseException;
import com.example.Models.Program.GenericQueue;
import com.example.Models.Program.Heap;
import com.example.Models.Program.IHeap;
import com.example.Models.Program.IQueue;
import com.example.Models.Program.ISemaphoreTable;
import com.example.Models.Program.ProgramState;
import com.example.Models.Program.SemaphoreTable;
import com.example.Models.Statements.IStatement;
import com.example.Models.Values.IValue;

import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ListView;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.util.Pair;

import javafx.scene.control.TextField;
import javafx.util.Pair;

public class ProgramController {

    private Controller controller;

    @FXML
    private TableView<Pair<Integer, IValue>> heapTable;

    @FXML
    private TableColumn<Pair<Integer, IValue>, Integer> addressColumn;

    @FXML
    private TableColumn<Pair<Integer, IValue>, String> valueColumn;

    @FXML
    private ListView<String> outputList;

    @FXML
    private ListView<String> fileList;

    @FXML
    private ListView<Integer> programStateList;

    @FXML
    private ListView<String> executionStackList;

    @FXML
    private TableView<Pair<String, IValue>> symbolTable;

    @FXML
    private TableColumn<Pair<String, IValue>, String> symVariableColumn;

    @FXML
    private TableColumn<Pair<String, IValue>, String> symValueColumn;

    @FXML
    private TableView<Pair<Integer,Pair<Integer, ArrayList<Integer>>>> semaphoresTable;

    @FXML
    private TableColumn<Pair<Integer,Pair<Integer, ArrayList<Integer>>>, Integer> semaphoresVariableColumn;

    @FXML
    private TableColumn<Pair<Integer,Pair<Integer, ArrayList<Integer>>>, String> semaphoresValueColumn;

    @FXML
    private TextField numberOfProgramStates;

    @FXML
    private Button oneStep;

    @FXML
    public void initialize() {
        addressColumn.setCellValueFactory(p -> new SimpleIntegerProperty(p.getValue().getKey()).asObject());
        valueColumn.setCellValueFactory(p -> new SimpleStringProperty(p.getValue().getValue().toString()));
        symVariableColumn.setCellValueFactory(p -> new SimpleStringProperty(p.getValue().getKey()));
        symValueColumn.setCellValueFactory(p -> new SimpleStringProperty(p.getValue().getValue().toString()));
        semaphoresVariableColumn.setCellValueFactory(p -> new SimpleIntegerProperty(p.getValue().getKey()).asObject());
        semaphoresValueColumn.setCellValueFactory(p -> new SimpleStringProperty(p.getValue().getValue().toString()));
        oneStep.setOnAction(actionEvent -> {
            if(controller == null){
                Alert alert = new Alert(Alert.AlertType.ERROR, "The program was not selected", ButtonType.OK);
                alert.showAndWait();
                return;
            }

            boolean programStateLeft = !controller.getProgramStates().stream().anyMatch(p -> (p != null && p.isNotCompleted()));
            if(programStateLeft){
                Alert alert = new Alert(Alert.AlertType.ERROR, "Nothing left to execute", ButtonType.OK);
                alert.showAndWait();
                return;
            }
            try {
                controller.oneStepAll();
                populate();
            } catch (Exception interpreterError) {
                Alert alert = new Alert(Alert.AlertType.ERROR, interpreterError.getMessage(), ButtonType.OK);
                alert.showAndWait();
            }
        });
        programStateList.setOnMouseClicked(mouseEvent -> populate());
    }

    private ProgramState getCurrentProgramState(){
        if (controller.getProgramStates().size() == 0)
            return null;
        int currentId = programStateList.getSelectionModel().getSelectedIndex();
        if (currentId == -1)
            return controller.getProgramStates().get(0);
        return controller.getProgramStates().get(currentId);
    }

    public void setController(Controller controller) {
        this.controller = controller;
        populate();
    }

    private void populate() {
        populateHeap();
        populateProgramStateIdentifiers();
        populateFileTable();
        populateOutput();
        populateSymbolTable();
        populateExecutionStack();
        populateSemaphoreTable();
    }

    private void populateHeap() {
        IHeap heap;
        if (controller.getProgramStates().size() > 0)
            heap = controller.getProgramStates().get(0).getHeap();
        else heap = new Heap();
        List<Pair<Integer, IValue>> heapTableList = new ArrayList<>();
        for (Map.Entry<Integer, IValue> entry : heap.getContent().entrySet())
            heapTableList.add(new Pair<>(entry.getKey(), entry.getValue()));
        heapTable.setItems(FXCollections.observableList(heapTableList));
        heapTable.refresh();
    }

    private void populateProgramStateIdentifiers() {
        List<ProgramState> programStates = controller.getProgramStates();
        var idList = programStates.stream().map(ps -> ps.id).collect(Collectors.toList());
        programStateList.setItems(FXCollections.observableList(idList));
        numberOfProgramStates.setText("" + programStates.size());
    }

    private void populateFileTable() {
        ArrayList<String> files;
        if (controller.getProgramStates().size() > 0)
            files = new ArrayList<String>(controller.getProgramStates().get(0).getFileTable().getDictionary().keySet().stream().map(Object::toString).collect(Collectors.toList()));
        else files = new ArrayList<String>();
        fileList.setItems(FXCollections.observableArrayList(files));
    }

    private void populateOutput() {
        IQueue<IValue> output;
        if (controller.getProgramStates().size() > 0)
            output = controller.getProgramStates().get(0).getOutput();
        else output = new GenericQueue<>();
        outputList.setItems(FXCollections.observableList(output.getContent().stream().map(Object::toString).collect(Collectors.toList())));
        outputList.refresh();
    }

    private void populateSymbolTable() {
        ProgramState state = getCurrentProgramState();
        List<Pair<String, IValue>> symbolTableList = new ArrayList<>();
        if (state != null)
            for (Map.Entry<String, IValue> entry : state.getSymbolTable().getDictionary().entrySet())
                symbolTableList.add(new Pair<>(entry.getKey(), entry.getValue()));
        symbolTable.setItems(FXCollections.observableList(symbolTableList));
        symbolTable.refresh();
    }

    private void populateExecutionStack() {
        ProgramState state = getCurrentProgramState();
        List<String> executionStackListAsString = new ArrayList<>();
        if (state != null)
            for(IStatement s : state.getStack().getStack()){
                executionStackListAsString.add(s.toString());
            }
        executionStackList.setItems(FXCollections.observableList(executionStackListAsString));
        executionStackList.refresh();
    }

    private void populateSemaphoreTable(){
        ISemaphoreTable semaphoreTableInternal;
        if (controller.getProgramStates().size() > 0)
            semaphoreTableInternal = controller.getProgramStates().get(0).getSemaphoreTable();
        else semaphoreTableInternal = new SemaphoreTable();
        List<Pair<Integer,Pair<Integer, ArrayList<Integer>>>> semaphoreTableList = new ArrayList<>();
        for (var entry : semaphoreTableInternal.getDictionary().entrySet())
            semaphoreTableList.add(new Pair<>(entry.getKey(), entry.getValue()));
        semaphoresTable.setItems(FXCollections.observableList(semaphoreTableList));
        semaphoresTable.refresh();
    }
}