package pt.utl.ist.scripts.process.updateData;

import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import net.sourceforge.fenixedu.domain.CurricularCourse;
import net.sourceforge.fenixedu.domain.CurricularCourseEquivalence;
import net.sourceforge.fenixedu.domain.Enrolment;
import net.sourceforge.fenixedu.domain.StudentCurricularPlan;
import net.sourceforge.fenixedu.domain.degree.enrollment.NotNeedToEnrollInCurricularCourse;
import net.sourceforge.fenixedu.domain.student.Registration;
import net.sourceforge.fenixedu.domain.student.Student;
import pt.utl.ist.fenix.tools.util.excel.Spreadsheet;
import pt.utl.ist.fenix.tools.util.excel.Spreadsheet.Row;
import pt.utl.ist.scripts.commons.AtomicScript;

public class AssociateEnrolmentsToNotNeedToEnrol extends AtomicScript {

    @Override
    protected void run() throws Exception {
        // List<Student> students = new
        // ArrayList<Student>(rootDomainObject.getStudents());
        Set<Student> noMatch = new HashSet<Student>();
        Set<Student> match = new HashSet<Student>();
        Set<Student> both = new HashSet<Student>();

        int size = rootDomainObject.getStudentsSet().size();

        Spreadsheet spreadsheet1 = new Spreadsheet("D");
        Spreadsheet spreadsheet2 = new Spreadsheet("D");

        // Student student = Student.readStudentByNumber(49981);

        Thread thread1 =
                new ProcessFread(spreadsheet1, spreadsheet2, rootDomainObject.getStudentCurricularPlansSet(), noMatch, match,
                        both);
        // Thread thread2 = new ProcessFread(spreadsheet2,
        // students.subList(2500, 5000));

        thread1.start();
        // thread2.start();

        thread1.join();
        // thread2.join();

        spreadsheet1.exportToCSV(new FileOutputStream(EXPORT_DIR_PATH + "/dispensas1.txt"), "\t");
        spreadsheet2.exportToCSV(new FileOutputStream(EXPORT_DIR_PATH + "/dispensas2.txt"), "\t");

        spreadsheet1.setHeaders(new String[] { "Número", "Matricula", "Estado", "Plano Curricular", "Plano Curricular Dispensa",
                "Nome Dispensa", "Código Dispensa", "Nome Origem", "Codigo Origem" });
        spreadsheet2.setHeaders(new String[] { "Número", "Matricula", "Estado", "Plano Curricular", "Plano Curricular Dispensa",
                "Nome Dispensa", "Código Dispensa" });

        logger.info("No Match: " + noMatch.size());
        logger.info("Match: " + match.size());
        logger.info("Both: " + both.size());

        getExternalStudentCount(noMatch);

    }

    private void getExternalStudentCount(Set<Student> noMatch) {
        int i = 0;
        for (Student student : noMatch) {
            if (hasOnlyOneSCP(student)) {
                i++;
            }
        }
        logger.info("External Students: " + i);
    }

    private boolean hasOnlyOneSCP(Student student) {
        boolean found = false;
        for (Registration registration : student.getRegistrationsSet()) {
            for (StudentCurricularPlan studentCurricularPlan : registration.getStudentCurricularPlansSet()) {
                if (!found) {
                    found = true;
                } else {
                    return false;
                }
            }
        }
        return found;
    }

    private abstract static class Node {
        private Set<CurricularCourse> curricularCourses;

        protected Node(Set<CurricularCourse> curricularCourses) {
            this.curricularCourses = curricularCourses;
        }

        public abstract Set<Enrolment> verify(Set<Enrolment> enrolments);

        protected Enrolment getEnrolment(CurricularCourse curricularCourse, Set<Enrolment> enrolments) {
            for (Enrolment enrolment : enrolments) {
                if (!enrolment.isBolonhaDegree()) {
                    if (/* !enrolment.getEnrolmentEvaluationType().equals(EnrolmentEvaluationType.EQUIVALENCE) && */
                    /* !enrolment.isExtraCurricular() && */
                    enrolment.getCurricularCourse().getCode().equals(curricularCourse.getCode())) {
                        return enrolment;
                    }
                }
            }
            return null;
        }

        protected Set<CurricularCourse> getCurricularCourses() {
            return this.curricularCourses;
        }
    }

    private static class OrNode extends Node {
        public OrNode(Set<CurricularCourse> curricularCourses) {
            super(curricularCourses);
        }

        @Override
        public Set<Enrolment> verify(Set<Enrolment> enrolments) {
            Set<Enrolment> result = new HashSet<Enrolment>();
            for (CurricularCourse curricularCourse : getCurricularCourses()) {
                Enrolment enrolment = getEnrolment(curricularCourse, enrolments);
                if (enrolment != null) {
                    result.add(enrolment);
                }
            }
            return result;
        }
    }

    private static class AndNode extends Node {
        public AndNode(Set<CurricularCourse> curricularCourses) {
            super(curricularCourses);
        }

        @Override
        public Set<Enrolment> verify(Set<Enrolment> enrolments) {
            Set<Enrolment> result = new HashSet<Enrolment>();
            for (CurricularCourse curricularCourse : getCurricularCourses()) {
                Enrolment enrolment = getEnrolment(curricularCourse, enrolments);
                if (enrolment == null) {
                    return Collections.emptySet();
                }
                result.add(enrolment);
            }
            return result;
        }
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        processWriteTransaction(new AssociateEnrolmentsToNotNeedToEnrol());
        System.exit(0);
    }

    private class ProcessFread extends Thread {
        private Spreadsheet spreadsheet;
        private Spreadsheet spreadsheet2;
        private Set<StudentCurricularPlan> students;
        private Set<Student> noMatch = new HashSet<Student>();
        private Set<Student> match = new HashSet<Student>();
        private Set<Student> both = new HashSet<Student>();

        public ProcessFread(Spreadsheet spreadsheet, Spreadsheet spreadsheet2, Set<StudentCurricularPlan> students,
                Set<Student> noMatch, Set<Student> match, Set<Student> both) {
            this.spreadsheet = spreadsheet;
            this.spreadsheet2 = spreadsheet2;
            this.students = students;
            this.both = both;
            this.match = match;
            this.noMatch = noMatch;
        }

        @Override
        public void run() {
            doAction(new DoAction(spreadsheet, spreadsheet2, students, both, match, noMatch));
        }
    }

    public class DoAction extends AtomicAction {
        private Spreadsheet spreadsheet;
        private Spreadsheet spreadsheet2;
        private Set<StudentCurricularPlan> students;
        private Set<Student> noMatch = new HashSet<Student>();
        private Set<Student> match = new HashSet<Student>();
        private Set<Student> both = new HashSet<Student>();

        public DoAction(Spreadsheet spreadsheet, Spreadsheet spreadsheet2, Set<StudentCurricularPlan> students,
                Set<Student> both, Set<Student> match, Set<Student> noMatch) {
            this.spreadsheet = spreadsheet;
            this.spreadsheet2 = spreadsheet2;
            this.students = students;
            this.both = both;
            this.match = match;
            this.noMatch = noMatch;

        }

        @Override
        public void doIt() {

            /*
             * for (Student student : students) { Set<Enrolment>
             * approvedEnrolments = student.getApprovedEnrolments(); for
             * (Registration registration : student.getRegistrationsSet()) {
             * if(registration.isActive()) { for (StudentCurricularPlan
             * studentCurricularPlan :
             * registration.getStudentCurricularPlansSet()) {
             * processSCP(studentCurricularPlan, approvedEnrolments); } } } }
             */

            for (StudentCurricularPlan studentCurricularPlan : students) {
                if (studentCurricularPlan.hasAnyNotNeedToEnrollCurricularCourses()
                        && studentCurricularPlan.getRegistration().isActive()) {
                    Set<Enrolment> approvedEnrolments =
                            studentCurricularPlan.getRegistration().getStudent().getApprovedEnrolments();
                    processSCP(studentCurricularPlan, approvedEnrolments);
                }
            }
        }

        private void processSCP(StudentCurricularPlan studentCurricularPlan, Set<Enrolment> approvedEnrolments) {
            // if(studentCurricularPlan.hasAnyNotNeedToEnrollCurricularCourses())
            // {
            for (NotNeedToEnrollInCurricularCourse notNeedToEnrollInCurricularCourse : studentCurricularPlan
                    .getNotNeedToEnrollCurricularCoursesSet()) {
                processNotNeedToEnrol(notNeedToEnrollInCurricularCourse, approvedEnrolments);
            }
            // }
        }

        private void processNotNeedToEnrol(NotNeedToEnrollInCurricularCourse notNeedToEnrollInCurricularCourse,
                Set<Enrolment> approvedEnrolments) {
            List<Node> nodes = new ArrayList<Node>();
            nodes.add(new AndNode(Collections.singleton(notNeedToEnrollInCurricularCourse.getCurricularCourse())));
            for (CurricularCourseEquivalence curricularCourseEquivalence : notNeedToEnrollInCurricularCourse
                    .getCurricularCourse().getCurricularCourseEquivalencesSet()) {
                Node node = new AndNode(curricularCourseEquivalence.getOldCurricularCourses());
                nodes.add(node);
            }

            Set<Enrolment> result = searchBreadth(nodes, approvedEnrolments);

            notNeedToEnrollInCurricularCourse.getEnrolments().clear();
            for (Enrolment enrolment : result) {
                notNeedToEnrollInCurricularCourse.addEnrolments(enrolment);
            }

            writeResult(notNeedToEnrollInCurricularCourse, result);
        }

        private void writeResult(NotNeedToEnrollInCurricularCourse notNeedToEnrollInCurricularCourse, Set<Enrolment> result) {
            Row row = null;
            if (!result.isEmpty()) {
                row = spreadsheet.addRow();
                match.add(notNeedToEnrollInCurricularCourse.getStudentCurricularPlan().getRegistration().getStudent());
            } else {
                row = spreadsheet2.addRow();
                noMatch.add(notNeedToEnrollInCurricularCourse.getStudentCurricularPlan().getRegistration().getStudent());
            }

            both.add(notNeedToEnrollInCurricularCourse.getStudentCurricularPlan().getRegistration().getStudent());

            row.setCell(notNeedToEnrollInCurricularCourse.getStudentCurricularPlan().getRegistration().getStudent().getNumber()
                    .toString());
            row.setCell(notNeedToEnrollInCurricularCourse.getStudentCurricularPlan().getRegistration().getDegree().getName());
            row.setCell(notNeedToEnrollInCurricularCourse.getStudentCurricularPlan().getRegistration().getActiveStateType()
                    .toString());
            row.setCell(notNeedToEnrollInCurricularCourse.getStudentCurricularPlan().getDegreeCurricularPlan().getName());
            row.setCell(notNeedToEnrollInCurricularCourse.getCurricularCourse().getDegreeCurricularPlan().getName());
            row.setCell(notNeedToEnrollInCurricularCourse.getCurricularCourse().getName());
            row.setCell(notNeedToEnrollInCurricularCourse.getCurricularCourse().getCode());
            if (!result.isEmpty()) {
                for (Enrolment enrolment : result) {
                    row.setCell(enrolment.getCurricularCourse().getName());
                    row.setCell(enrolment.getCurricularCourse().getCode());
                }
            }

        }

        private Set<Enrolment> searchBreadth(List<Node> nodes, Set<Enrolment> approvedEnrolments) {
            Set<Enrolment> result = new HashSet<Enrolment>();

            for (Node node : nodes) {
                result.addAll(node.verify(approvedEnrolments));
                if (!result.isEmpty()) {
                    return result;
                }
            }

            return result;
        }
    }

}
