/**
 * Copyright (C) 2019 Patrik Dufresne Service Logiciel <info@patrikdufresne.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package com.planimod.core;

import java.util.AbstractSet;
import java.util.Collections;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;

import com.patrikdufresne.util.AbstractBidiMultiMap;
import com.patrikdufresne.util.BidiMultiMap;

/**
 * This class is used to represent the qualification of an employee. If the employee is qualify to work on a task, an
 * entry exists in this table.
 * 
 * @author Patrik Dufresne
 * 
 */
public class TaskQualificationTable extends AbstractBidiMultiMap<Employee, Task> {

    /**
     * Private implementation of entry set.
     * 
     * @author Patrik Dufresne
     * 
     */
    private class EntrySet extends AbstractSet<Entry<Employee, Task>> {

        /**
         * This implementation use the qualification table.
         */
        @Override
        public boolean contains(Object o) {
            return o instanceof Entry && containsEntry(((Entry) o).getKey(), ((Entry) o).getValue());
        }

        /**
         * This implementation does a cross product between the known employees and known tasks.
         */
        @Override
        public Iterator<Entry<Employee, Task>> iterator() {

            final Iterator<Employee> empIter = qualificationTable.keySet().iterator();

            return new Iterator<Entry<Employee, Task>>() {
                private Employee employee;
                private Entry<Employee, Task> nextObject;
                private boolean nextObjectSet = false;
                private Iterator<Task> taskIter;

                @Override
                public boolean hasNext() {
                    if (this.nextObjectSet) {
                        return true;
                    }
                    return setNextObject();
                }

                @Override
                public Entry<Employee, Task> next() {
                    if (!this.nextObjectSet) {
                        if (!setNextObject()) {
                            throw new NoSuchElementException();
                        }
                    }
                    this.nextObjectSet = false;
                    return this.nextObject;
                }

                /**
                 * This implementation always throw UnsupportedOperationException.
                 */
                @Override
                public void remove() {
                    throw new UnsupportedOperationException();
                }

                /**
                 * Does iterate on both iterator to create the cross product.
                 * 
                 * @return
                 */
                private boolean setNextObject() {
                    // Check if the iterator is empty.
                    if (this.taskIter == null && !empIter.hasNext()) {
                        return false;
                    }

                    while (true) {
                        while (this.taskIter != null && this.taskIter.hasNext()) {
                            Task task = this.taskIter.next();
                            if (qualificationTable.containsEntry(this.employee, task.getPosition())) {
                                this.nextObject = new SimpleEntry<Employee, Task>(this.employee, task);
                                this.nextObjectSet = true;
                                return true;
                            }
                        }
                        if (empIter.hasNext()) {
                            employee = empIter.next();
                            this.taskIter = tasks.iterator();
                        } else {
                            return false;
                        }
                    }

                }
            };
        }

        @Override
        public int size() {
            int count = 0;
            Iterator iter = iterator();
            while (iter.hasNext()) {
                iter.next();
                count++;
            }
            return count;
        }

    }

    private EntrySet entries;

    /**
     * Qualification table to know if an employee is qualify for a position.
     */
    BidiMultiMap<Employee, Position> qualificationTable;

    /**
     * Known tasks.
     */
    Set<Task> tasks;

    /**
     * Create a new qualification table.
     * 
     * @param qualificationTable
     * @param tasks
     */
    public TaskQualificationTable(BidiMultiMap<Employee, Position> qualificationTable, Set<Task> tasks) {
        if (qualificationTable == null || tasks == null) {
            throw new NullPointerException();
        }
        this.qualificationTable = qualificationTable;
        this.tasks = Collections.unmodifiableSet(tasks);
    }

    /**
     * This implementation check if the entry exists using the qualification table.
     */
    @Override
    public boolean containsEntry(Object key, Object value) {
        if (key instanceof Employee && value instanceof Task) {
            return this.qualificationTable.containsEntry(key, ((Task) value).getPosition());
        }
        return false;
    }

    /**
     * This implementation check if the key exists using the qualification table.
     */
    @Override
    public boolean containsKey(Object key) {
        return key instanceof Employee && this.qualificationTable.containsKey(key);
    }

    /**
     * This implementation check if the tasks is known.
     */
    @Override
    public boolean containsValue(Object value) {
        return value instanceof Task && this.tasks.contains(value);
    }

    @Override
    protected Iterator<Employee> createKeySetIterator() {
        // Since keySet() function is override, this function is never
        // called.
        return null;
    }

    @Override
    protected Iterator<Task> createValueIterator() {
        // Since valueSet function is override, this function is never
        // called.
        return null;
    }

    /**
     * This implementation create a view on the qualification table.
     */
    @Override
    public Set<Entry<Employee, Task>> entrySet() {
        if (this.entries == null) {
            this.entries = new EntrySet();
        }
        return this.entries;
    }

    /**
     * Return the qualification table key set.
     */
    @Override
    public Set<Employee> keySet() {
        // BidiMultiMap keySet is immutable by definition.
        return this.qualificationTable.keySet();
    }

    /**
     * Return the known tasks.
     */
    @Override
    public Set<Task> valueSet() {
        return this.tasks;
    }

}