/**
 * 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 static com.planimod.core.TimeUtils.*;
import static org.junit.Assert.*;

import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.junit.Assert;
import org.junit.Test;

import com.patrikdufresne.managers.ManagerException;
import com.planimod.core.AbstractCalendarEvent;
import com.planimod.core.Product;
import com.planimod.core.ProductionEvent;
import com.planimod.core.ProductionEventManager;
import com.planimod.core.Team;

/**
 * This class intend to test the {@link ProductionEventManager}.
 * 
 * @author Patrik Dufresne
 * 
 */
public class ProductionEventManagerTest extends ManagerTestCase {

    public static void assertProductionEvent(
            int expected,
            List<ProductionEvent> events,
            final Product product,
            final Date start,
            final Date end,
            final AbstractCalendarEvent shiftEvent) {

        int count = CollectionUtils.countMatches(events, new Predicate() {
            @Override
            public boolean evaluate(Object object) {
                ProductionEvent event = (ProductionEvent) object;
                return event.getProduct().equals(product)
                        && (shiftEvent == null && event.getShift().getStartDate().getTime() == start.getTime() && event.getShift().getEndDate().getTime() == end
                                .getTime())
                        || (shiftEvent != null && event.getShift().equals(shiftEvent));
            }
        });
        if (expected != count) {
            StringBuilder buf = new StringBuilder();
            if (events.size() > 1) {
                buf.append(events);
            } else {
                buf.append(events.get(0));
            }
            buf.append(" expected=");
            buf.append(expected);
            buf.append(" actual=");
            buf.append(count);
            Assert.fail(buf.toString());
        }
    }

    public static void assertProductionEvent(List<ProductionEvent> events, final Product product, final AbstractCalendarEvent shiftEvent) {
        assertProductionEvent(1, events, product, null, null, shiftEvent);
    }

    public static void assertProductionEvent(List<ProductionEvent> events, final Product product, final Date start, final Date end) {
        assertProductionEvent(1, events, product, start, end, null);
    }

    public static void assertShiftEvent(int expected, List<Shift> events, final Date start, final Date end) {
        int count = CollectionUtils.countMatches(events, new Predicate() {
            @Override
            public boolean evaluate(Object object) {
                Shift event = (Shift) object;
                return event.getStartDate().getTime() == start.getTime() && event.getEndDate().getTime() == end.getTime();
            }
        });
        if (expected != count) {
            StringBuilder buf = new StringBuilder();
            if (events.size() > 1) {
                buf.append(events);
            } else {
                buf.append(events.get(0));
            }
            buf.append(" expected=");
            buf.append(expected);
            buf.append(" actual=");
            buf.append(count);
            Assert.fail(buf.toString());
        }

    }

    public static void assertShiftEvent(List<Shift> events, final Date start, final Date end) {
        assertShiftEvent(1, events, start, end);
    }

    @Test
    public void Copy_WithDaylightSaving() throws ManagerException {
        // Check to copy production event when one shift event already exists at
        // the destination.
        //
        // Source:
        // shift1 - shift1event1 - productionEvent1 - product#1
        // shift1 - shift1event2 - productionEvent1 - product#1
        // shift2 - shift2event2 - productionEvent1 - product#1
        //
        // Destination:
        // empty

        /*
         * Create shifts & production event
         */
        Product pro1 = addProduct(managers, "product1");
        Team shift1 = addTeam(managers, "Shift1");
        Team shift2 = addTeam(managers, "Shift2");
        Shift shift1Event1 = addShift(managers, shift1, dateTime("2012-10-30 Tue 6:00"), dateTime("2012-10-30 Tue 16:00"));
        Shift shift1Event2 = addShift(managers, shift1, dateTime("2012-10-31 Wed 6:00"), dateTime("2012-10-31 Wed 16:00"));
        Shift shift2Event2 = addShift(managers, shift2, dateTime("2012-10-30 Tue 8:00"), dateTime("2012-10-30 Tue 15:00"));
        ProductionEvent e1 = addProductionEvent(managers, pro1, shift1Event1);
        ProductionEvent e2 = addProductionEvent(managers, pro1, shift1Event2);
        ProductionEvent e3 = addProductionEvent(managers, pro1, shift2Event2);

        /*
         * Run Copy()
         */
        List<ProductionEvent> events;
        List<Shift> shifts;
        managers.getProductionEventManager().copy(Arrays.asList(e1, e2, e3), date("2012-11-07"), false);
        shifts = managers.getShiftManager().list(date("2012-11-04"), date("2012-11-11"));
        events = managers.getProductionEventManager().list(date("2012-11-04"), date("2012-11-11"));

        /*
         * Check results
         */
        assertEquals("Wrong number of production events.", 3, events.size());
        assertEquals("Wrong number of shift events.", 3, shifts.size());

        assertShiftEvent(shifts, dateTime("2012-11-06 Tue 6:00"), dateTime("2012-11-06 Tue 16:00"));
        assertShiftEvent(shifts, dateTime("2012-11-07 Wed 6:00"), dateTime("2012-11-07 Wed 16:00"));
        assertShiftEvent(shifts, dateTime("2012-11-06 Tue 8:00"), dateTime("2012-11-06 Tue 15:00"));

        assertProductionEvent(events, pro1, dateTime("2012-11-06 Tue 6:00"), dateTime("2012-11-06 Tue 16:00"));
        assertProductionEvent(events, pro1, dateTime("2012-11-07 Wed 6:00"), dateTime("2012-11-07 Wed 16:00"));
        assertProductionEvent(events, pro1, dateTime("2012-11-06 Tue 8:00"), dateTime("2012-11-06 Tue 15:00"));

    }

    @Test
    public void Copy_WithOneProductionEventAlreadyExists() throws ManagerException {
        // Check to copy production event when one production event already
        // exists at the destination.
        //
        // Source:
        // shift1 - shift1event1 - productionEvent1 - product#1
        // shift2 - shift2event2 - productionEvent1 - product#1
        //
        // Destination:
        // shift1 - shift1event2 -productionEvent1 - product#1

        Team shift1 = addTeam(managers, "Shift1");
        Team shift2 = addTeam(managers, "Shift2");
        Shift shift1Event1 = addShift(managers, shift1, dateTime("2012-02-7 Mon 6:00"), dateTime("2012-02-7 Mon 16:00"));
        Shift shift1Event2 = addShift(managers, shift1, dateTime("2012-02-14 Mon 6:00"), dateTime("2012-02-14 Mon 16:00"));
        Shift shift2Event2 = addShift(managers, shift2, dateTime("2012-02-7 Mon 8:00"), dateTime("2012-02-7 Mon 15:00"));
        Product pro1 = addProduct(managers, "product1");
        ProductionEvent e1 = addProductionEvent(managers, pro1, shift1Event1);
        ProductionEvent e2 = addProductionEvent(managers, pro1, shift2Event2);
        ProductionEvent existingEvent = addProductionEvent(managers, pro1, shift1Event2);

        /*
         * Run copy
         */
        List<ProductionEvent> copiedEvents;
        List<Shift> shiftEvents;
        managers.getProductionEventManager().copy(Arrays.asList(e1, e2), date("2012-02-12"), true);
        shiftEvents = managers.getShiftManager().list(date("2012-02-12"), date("2012-02-18"));
        copiedEvents = managers.getProductionEventManager().list(date("2012-02-12"), date("2012-02-18"));

        /*
         * Check results
         */
        assertEquals("Wrong number of production events.", 2, copiedEvents.size());
        assertEquals("Wrong number of shift events.", 2, shiftEvents.size());
        assertShiftEvent(shiftEvents, dateTime("2012-02-14 Mon 8:00"), dateTime("2012-02-14 Mon 15:00"));
        assertProductionEvent(copiedEvents, pro1, dateTime("2012-02-14 Mon 8:00"), dateTime("2012-02-14 Mon 15:00"));

    }

    @Test
    public void Copy_WithOneShiftEventAlreadyExists() throws ManagerException {
        // Check to copy production event when one shift event already exists at
        // the destination.
        //
        // Source:
        // shift1 - shift1event1 - productionEvent1 - product#1
        // shift2 - shift2event2 - productionEvent1 - product#1
        //
        // Destination:
        // shift1 - shift1event2

        Team shift1 = addTeam(managers, "Shift1");
        Team shift2 = addTeam(managers, "Shift2");
        Shift shift1Event1 = addShift(managers, shift1, dateTime("2012-02-7 Mon 6:00"), dateTime("2012-02-7 Mon 16:00"));
        Shift shift2Event2 = addShift(managers, shift2, dateTime("2012-02-7 Mon 8:00"), dateTime("2012-02-7 Mon 15:00"));
        Shift shift1Event2 = addShift(managers, shift1, dateTime("2012-02-14 Mon 6:00"), dateTime("2012-02-14 Mon 16:00"));
        Product pro1 = addProduct(managers, "product1");
        ProductionEvent e1 = addProductionEvent(managers, pro1, shift1Event1);
        ProductionEvent e2 = addProductionEvent(managers, pro1, shift2Event2);

        /*
         * run copy
         */
        List<ProductionEvent> copiedEvents;
        List<Shift> shiftEvents;
        managers.getProductionEventManager().copy(Arrays.asList(e1, e2), date("2012-02-12"), true);
        shiftEvents = managers.getShiftManager().list(date("2012-02-12"), date("2012-02-18"));
        copiedEvents = managers.getProductionEventManager().list(date("2012-02-12"), date("2012-02-18"));

        /*
         * Check results
         */
        assertEquals("Wrong number of production events.", 2, copiedEvents.size());
        assertEquals("Wrong number of shift events.", 2, shiftEvents.size());
        assertShiftEvent(shiftEvents, dateTime("2012-02-14 Mon 8:00"), dateTime("2012-02-14 Mon 15:00"));
        assertProductionEvent(copiedEvents, pro1, dateTime("2012-02-14 Mon 6:00"), dateTime("2012-02-14 Mon 16:00"));
        assertProductionEvent(copiedEvents, pro1, dateTime("2012-02-14 Mon 8:00"), dateTime("2012-02-14 Mon 15:00"));

    }

    @Test
    public void Copy_WithProductionEvents_WillSendManagerEvent() throws ManagerException {
        // Check a simple copy of production event

        Team shift1 = addTeam(managers, "Shift1");
        Shift shiftEvent1 = addShift(managers, shift1, dateTime("2012-02-7 Mon 6:00"), dateTime("2012-02-7 Mon 16:00"));
        Team shift2 = addTeam(managers, "Shift2");
        Shift shiftEvent2 = addShift(managers, shift2, dateTime("2012-02-7 Mon 8:00"), dateTime("2012-02-7 Mon 15:00"));

        Product pro1 = addProduct(managers, "product1");
        ProductionEvent e1 = addProductionEvent(managers, pro1, shiftEvent1);
        ProductionEvent e2 = addProductionEvent(managers, pro1, shiftEvent2);

        /*
         * Run copy
         */
        List<ProductionEvent> copiedEvents;
        List<Shift> shiftEvents;
        managers.getProductionEventManager().copy(Arrays.asList(e1, e2), date("2012-02-12"), true);
        shiftEvents = managers.getShiftManager().list(date("2012-02-12"), date("2012-02-18"));
        copiedEvents = managers.getProductionEventManager().list(date("2012-02-12"), date("2012-02-18"));

        /*
         * Check results
         */
        assertEquals("Wrong number of production events.", 2, copiedEvents.size());
        assertEquals("Wrong number of shift events.", 2, shiftEvents.size());
        assertShiftEvent(shiftEvents, dateTime("2012-02-14 Mon 6:00"), dateTime("2012-02-14 Mon 16:00"));
        assertShiftEvent(shiftEvents, dateTime("2012-02-14 Mon 8:00"), dateTime("2012-02-14 Mon 15:00"));
        assertProductionEvent(copiedEvents, pro1, dateTime("2012-02-14 Mon 6:00"), dateTime("2012-02-14 Mon 16:00"));
        assertProductionEvent(copiedEvents, pro1, dateTime("2012-02-14 Mon 8:00"), dateTime("2012-02-14 Mon 15:00"));

    }

    /**
     * Test listing production events
     */
    @Test
    public void ListProductionEvents() {

        Team shift = addTeam(managers, "shift");
        Shift monDay = addShift(managers, shift, time("mon 6:00"), time("mon 16:00"));
        Shift tueDay = addShift(managers, shift, time("tue 6:00"), time("tue 16:00"));
        Shift wedDay = addShift(managers, shift, time("wed 6:00"), time("wed 16:00"));
        Shift thuDay = addShift(managers, shift, time("thu 6:00"), time("thu 16:00"));

        Product product = addProduct(managers, "product");
        ProductionEvent prod1 = addProductionEvent(managers, product, monDay);
        ProductionEvent prod2 = addProductionEvent(managers, product, tueDay);
        ProductionEvent prod3 = addProductionEvent(managers, product, wedDay);
        ProductionEvent prod4 = addProductionEvent(managers, product, thuDay);

        // Add other calendar entry to make sure they are not listed.
        addNonAvailability(managers, addEmployee(managers, "employee"), time("mon 8:00"), time("mon 16:00"));
        addNonAvailability(managers, addEmployee(managers, "employee"), time("mon 8:00"), time("mon 16:00"));

        List<ProductionEvent> events;
        try {
            events = managers.getProductionEventManager().list(time("mon 6:00"), time("thu 16:00"));
        } catch (ManagerException e) {
            fail("Error listing the production events", e);
            return;
        }

        assertEquals("Wrong number of events", 4, events.size());

    }

    @Test
    public void Remove_WithAssociatedTasks_RemoveAllObjects() throws ManagerException {

        Team shift = addTeam(managers, "shift");
        Shift monDay = addShift(managers, shift, time("mon 6:00"), time("mon 16:00"));

        Product product = addProduct(managers, "product");
        Position position = addPosition(managers, null, "position", false);
        ProductionEvent e1 = addProductionEvent(managers, product, monDay);
        assertEquals(1, managers.getProductionEventManager().list().size());

        Task t1 = addTask(managers, e1, position, null, null);
        Task t2 = addTask(managers, e1, position, null, null);
        assertEquals(2, managers.getTaskManager().list().size());

        managers.getProductionEventManager().remove(Arrays.asList(e1));

        assertEquals(0, managers.getProductionEventManager().list().size());
        assertEquals(0, managers.getTaskManager().list().size());

    }

}
