import java.util.*;
import java.io.*;
import java.time.*;
import java.time.temporal.ChronoUnit;

class Department
{
    //Private and static fields
    private static int dpId = 0; //represents the department ID
    private static ArrayList<Integer> dpIds; // maintains a list of department IDs
    private static ArrayList<String> dpNames; // maintains a list of department names - dpIds.get(i) corresponds to dpNames.get(i)

    // Instance fields
    private int depId;
    private String depName;
    private String joinDate;

    public static int getNextDepID() // Increments dpID(integer) and returns it
    {
        dpId++;
        return dpId;
    }

    /**
     * Search arraylist dpNames for depName
     * if found at index i, set depId = dpIds.get(i)
     * else create new id via getNextDepID(), add this new id to dpIds, and add depName to dpNames
     * store/update all instance fields(depId, depName, joinDate)
     */
    public Department(String depName, String joinDate)
    {
        if(dpNames == null)
        {
            dpNames = new ArrayList<String>();
        }

        if (dpIds == null)
        {
            dpIds = new ArrayList<Integer>();
        }

        for(int i = 0; i < dpNames.size(); i++)
        {
            String index = dpNames.get(i);

            if (index.equals(depName)) // finds the department id
            {
                depId = dpIds.get(i);
            }
            else // creates a new id and adds the name
            {
                int newId = getNextDepID();
                dpIds.add(newId);

                dpNames.add(depName);
            }
        }
        this.depName = depName;
        this.joinDate = joinDate;
    }

    //Accessor Methods -----------------------------------
    public int get_dep_id()
    {
        return depId;
    }

    public String get_dep_name()
    {
        return depName;
    }

    public String get_join_date()
    {
        return joinDate;
    }

    @Override
    public String toString() // Return a String "Department[id=<depId>, name=<depName>, joinDate=<joinDate>]"
    {
        return "Department[id=" + get_dep_id() + ", name=" + get_dep_name() + ", joinDate=" + get_join_date() + "]";
    }
}

class Employee
{
    // Private and static fields
    private static int emID = 0; // Represents the employee ID

    //Instance fields
    private String empId;
    private String fName;
    private String lName;
    private ArrayList<Department> depLst; // maintains a list of department objects

    // Static methods -----------------------------------------
    public static String getNextEmpId() // Increments emId and returns "EMP" + 4 - digit zero-padded(e.g. EMP0001)
    {
        emID++;
        String paddedNumber = String.format("%04d", emID);
        return "EMP" + paddedNumber;
    }

    public static void decrementEmpID() // Decrements emID by 1(never below 0)
    {
        if(emID > 0)
        {
            emID--;
        }
    }

    /**
     * generates empId using getNextEmpId()
     * stores names (fName, lName)
     * initializes depLst
     */
    public Employee(String fName, String lName)
    {
        empId = getNextEmpId();

        set_fName(fName);
        set_lName(lName);

        depLst = new ArrayList<Department>();
    }

    // Accessor Methods -------------------------------
    public String get_emp_id()
    {
        return empId;
    }

    public String get_fName()
    {
        return fName;
    }

    public String get_lName()
    {
        return lName;
    }

    public ArrayList<Department> get_depLst()
    {
        return depLst;
    }

    // Mutator Methods -----------------------------
    public void set_fName(String f)
    {
        fName = f;
    }

    public void set_lName(String l)
    {
        lName = l;
    }

    // Methods -----------------------------------------
    public void add_dept(Department d) // Add department d to the depLst
    {
        depLst.add(d);
    }

    @Override
    public String toString() // returns a string "Employee[id=EMPxxxx, name=First Last, #appts=N]"
    {
        return "Employee[id=" + get_emp_id() + ", name=" + get_fName() + get_lName() + " #appts=" + get_depLst().size();
    }
}

class EmployeeDB 
{
    // Instance fields
    private String filename;
    private ArrayList<ArrayList<String>> emp_rows; // each row is an array list [FirstName, LastName, DepartmentName, DateOfJoining]
    private ArrayList<Employee> emp_obj_list;

    public EmployeeDB(String filename) // Updates/initializes filename; initializes emp_rows, emp_obj_list
    {
        this.filename = filename;
        emp_rows = new ArrayList<ArrayList<String>>();
        emp_obj_list = new ArrayList<Employee>();
    }

    // Reads the given file and populates the emp_rows list (skips the first line)
    public void read_file() 
    {
        int data = 0;
        try (BufferedReader br = new BufferedReader(new FileReader(filename))) 
        {
            String line;
            boolean first = true;
            while ((line = br.readLine()) != null) // While we aren't as the end of the file, continue reading
            {
                if (line.trim().length() == 0) // if the line is empty, then skip it
                {
                    continue;
                }

                String[] parts = line.split(",", -1); // turn the line into a list of strings

                if (first) // Skips the first line of the file
                {
                    first = false;
                    continue;
                }

                if (parts.length < 5) // If the parts list is missing a component, then skip it
                {
                    continue;
                }

                // Adds all of the components of a line into the row lst and adds each row to the emp_rows list
                String f = parts[1].trim();
                String l = parts[2].trim();
                String d = parts[3].trim();
                String date = parts[4].trim();
                ArrayList<String> row = new ArrayList<String>();
                row.add(f);
                row.add(l);
                row.add(d);
                row.add(date);
                emp_rows.add(row);
                data++;
            }
            System.out.println(emp_rows.size() + " records added to the dictionary.");
        } catch (Exception e) {
            System.out.println("Error reading file: " + e.getMessage());
        }
    }

    public int dict_size() 
    {
        return emp_rows.size();
    }

    public ArrayList<Employee> get_obj_lst() 
    {
        return emp_obj_list;
    }

    // Checks to see if the current employee object exists in the list of all employees
    public int emp_exists(Employee e) 
    {
        String emp_fName;
        String emp_lName;

        // Checks to make sure the names match before deciding if the employee exists or not
        for(int i = 0; i < emp_obj_list.size(); i++)
        {
            emp_fName = emp_obj_list.get(i).get_fName();
            emp_lName = emp_obj_list.get(i).get_lName();

            if(e.get_fName().equals(emp_fName) && e.get_lName().equals(emp_lName))
            {
                return i;
            }
        }
        return -1;

    }

    /**
     * The purpose is to add data of unique employees from
     * emp_rows to emp_obj_list. An employee can be cross-appointed
     * (appointed in multiple departments with the same or different
     * joining dates). It means that the same employee can appear
     * multiple times in emp_rows(same employee information, but
     * different department information). remember that a unique
     * employee (same first and last name) will have exactly one
     * employee ID. so, for each new row in emp_rows, if you create an
     * Employee object, it will create a new employee ID. In case the 
     * employee already exists in emp_obj_list, you will decrement the ID
     * and use the existing employee object to add the new 
     * department information
     * 
     * For each row in emp_rows(index-based loop):
     * - create a temp Employee(f, l)
     * - if the employee does not exist, append to emp_obj_list; else
     *   call Employee.decrementEmpID() and use the existing object
     * - create Department(dep, date) and add via add_dept(...)
     * 
     * Towards the end, print:
     * "<emp_obj_list.size()> unique employees added to the database"
     */
    public void add_emp_data() 
    {
        for(int i = 0; i < dict_size(); i++)
        {
            Employee temp = new Employee(emp_rows.get(i).get(0), emp_rows.get(i).get(1));
            Department dep3 = new Department(emp_rows.get(i).get(2), emp_rows.get(i).get(3));
            temp.add_dept(dep3);
            int e = emp_exists(temp);

            if(e == -1) // If the employee id doesn't exist, then add the employee to the employee object list
            {
                emp_obj_list.add(temp);               
            }
            else // Otherwise, decrease the empId and add the department to the employee object
            {
                Employee.decrementEmpID();
                // Find ALL locations that there is a repeating employee name and add ALL of the departments to that employee
                for(int j = 0; j < emp_obj_list.size(); j++)
                {
                    String emp_fName = emp_obj_list.get(j).get_fName();
                    String emp_lName = emp_obj_list.get(j).get_lName();

                    if (temp.get_fName().equals(emp_fName) && temp.get_lName().equals(emp_lName)) 
                    {
                        Department dep2 = new Department(emp_rows.get(j).get(2), emp_rows.get(j).get(3));
                        temp.add_dept(dep2);

                        System.out.println("Departments " + temp.get_depLst().size());
                    }
                }
            }
            System.out.println("Departments " + temp.get_depLst().size());
            System.out.println(temp.get_fName() + " " + temp.get_lName());
            System.out.println("Employee ID: " + temp.get_emp_id());
            date_diff(findEmpIdByName(temp.get_fName(), temp.get_lName()));
            System.out.println("\n");
        }
        System.out.println(emp_obj_list.size() + " unique employees added to the database.");
    }

    // Checks if an employee object is apart of more than one department
    public void print_cross_emp() 
    {
        int record = 0;

        for(int i = 0; i < emp_obj_list.size(); i++) // Loops through the employee object list
        {
            Employee temp = new Employee(emp_obj_list.get(i).get_fName(), emp_obj_list.get(i).get_lName());
            Department dep = new Department(temp.get_fName(), temp.get_lName());

            if (temp.get_depLst().size() > 1) // If the employee has more than one department, then print out the required statement
            {
                record++;
                String paddedNumber = String.format("%02d", record);
                System.out.println("Record " + paddedNumber + " found! " + temp.toString() + " Appointments found: \n");
                for(int j = 0; j < temp.get_depLst().size(); j++)
                {
                    System.out.println(temp.get_depLst().get(j).toString() + "\n");
                }
            }
        }
    }

    // Searches for given empId and then removes the employee corresponding to the ID if the employee exists
    public void remove_emp(String empId) 
    {
        for(int i = 0; i < emp_obj_list.size(); i++)
        {
            Employee temp = new Employee(emp_obj_list.get(i).get_fName(), emp_obj_list.get(i).get_lName());

            // Searches for the employee ID
            if(temp.get_emp_id().equals(empId))
            {
                // Removes that employee
                emp_obj_list.remove(i);
                System.out.println("Succesfully removed employee with employee id: " + empId);
            }
            else // If the employee isn't found
            {
                System.out.println("Employee not found!");
            }
        }
    }

    // Finds the difference between dates for a given employee id
    public void date_diff(String empId) 
    {
        System.out.println("Employee ID Date: " + empId);
        long date = date_diff_value(empId);

        if (date == -1) // If the employee id is not found or is not apart of more than 1 department, print "employee not found!"
        {
            System.out.println("Employee not found!");
        }
        else if (date == -2)
        {
            System.out.println("Employee is not cross-appointed.");
        }
        else // Otherwise, print the difference between join dates of departments
        {
            System.out.println("Difference between cross-appointments (in days) is: " + date);
        }


    }

    // ==== Extra methods for testing ====
    public String findEmpIdByName(String f, String l) 
    {
        for (int i = 0; i < emp_obj_list.size(); i++) 
        {
            Employee e = emp_obj_list.get(i);
            if (e.get_fName().equals(f) && e.get_lName().equals(l))
                return e.get_emp_id();
        }
        return null;
    }

    // Finds the difference in date values
    public long date_diff_value(String empId) 
    {
        int idx = -1;

        // Loops through the list of all employee objects
        for (int i = 0; i < emp_obj_list.size(); i++) 
        {
            // Checks whether the current empId matches the one found
            if (emp_obj_list.get(i).get_emp_id().equals(empId)) 
            {
                idx = i;
                break;
            }
        }
        // return -1 if we do not find a match with the empId
        if (idx == -1)
        {
            return -1;
        }

        ArrayList<Department> deps = emp_obj_list.get(idx).get_depLst();

        // returns -1 if the employee is appointed in one department or less
        if (deps.size() <= 1)
        {
            return -2;
        }

        LocalDate min = null, max = null;
        for (int i = 0; i < deps.size(); i++) 
        {
            LocalDate d = LocalDate.parse(deps.get(i).get_join_date());
            if (min == null || d.isBefore(min))
                min = d;
            if (max == null || d.isAfter(max))
                max = d;
        }
        return ChronoUnit.DAYS.between(min, max);
    }
}