/**
 * Copyright (c) 2012 Todoroo Inc
 *
 * See the file "LICENSE" for the full license governing this code.
 */
package com.todoroo.astrid.reminders;

import java.util.Date;

import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.reminders.ReminderService.AlarmScheduler;
import com.todoroo.astrid.test.DatabaseTestCase;
import com.todoroo.astrid.utility.AstridPreferences;

public class ReminderServiceTests extends DatabaseTestCase {

    ReminderService service;
    AlarmScheduler original;

    @Autowired
    TaskDao taskDao;

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        service = ReminderService.getInstance();
        original = service.getScheduler();
        AstridPreferences.setPreferenceDefaults();
    }

    @Override
    protected void tearDown() throws Exception {
        super.tearDown();
        service.setScheduler(original);
    }

    /** tests with no alarms */
    public void testNoReminders() {
        service.setScheduler(new NoAlarmExpected());

        Task task = new Task();
        task.setValue(Task.TITLE, "water");
        task.setValue(Task.REMINDER_FLAGS, 0);
        task.setValue(Task.REMINDER_PERIOD, 0L);
        taskDao.save(task);
        service.scheduleAlarm(task);
    }

    /** tests with due date */
    public void testDueDates() {
        service.setScheduler(new AlarmExpected() {
            @Override
            public void createAlarm(Task task, long time, int type) {
                if (time == ReminderService.NO_ALARM)
                    return;
                super.createAlarm(task, time, type);
                assertEquals((long)task.getValue(Task.DUE_DATE), time);
                assertEquals(type, ReminderService.TYPE_DUE);
            }
        });

        // test due date in the past
        final Task task = new Task();
        task.setValue(Task.TITLE, "water");
        task.setValue(Task.DUE_DATE, DateUtilities.now() - DateUtilities.ONE_DAY);
        task.setValue(Task.REMINDER_FLAGS, Task.NOTIFY_AT_DEADLINE);
        taskDao.save(task);

        // test due date in the future
        task.setValue(Task.DUE_DATE, DateUtilities.now() + DateUtilities.ONE_DAY);
        taskDao.save(task);
        assertTrue(((AlarmExpected)service.getScheduler()).alarmCreated);
    }

    /** tests with random */
    public void testRandom() {
        // test random
        final Task task = new Task();
        task.setValue(Task.TITLE, "water");
        task.setValue(Task.REMINDER_PERIOD, DateUtilities.ONE_WEEK);
        service.setScheduler(new AlarmExpected() {
            @Override
            public void createAlarm(Task task, long time, int type) {
                if (time == ReminderService.NO_ALARM)
                    return;
                super.createAlarm(task, time, type);
                assertTrue(time > DateUtilities.now());
                assertTrue(time < DateUtilities.now() + 1.2 * DateUtilities.ONE_WEEK);
                assertEquals(type, ReminderService.TYPE_RANDOM);
            }
        });
        taskDao.save(task);
        assertTrue(((AlarmExpected)service.getScheduler()).alarmCreated);
    }

    /** tests with overdue */
    public void testOverdue() {
        // test due date in the future
        service.setScheduler(new AlarmExpected() {
            @Override
            public void createAlarm(Task task, long time, int type) {
                if (time == ReminderService.NO_ALARM)
                    return;
                super.createAlarm(task, time, type);
                assertTrue(time > task.getValue(Task.DUE_DATE));
                assertTrue(time < task.getValue(Task.DUE_DATE) + DateUtilities.ONE_DAY);
                assertEquals(type, ReminderService.TYPE_OVERDUE);
            }
        });
        final Task task = new Task();
        task.setValue(Task.TITLE, "water");
        task.setValue(Task.DUE_DATE, DateUtilities.now() + DateUtilities.ONE_DAY);
        task.setValue(Task.REMINDER_FLAGS, Task.NOTIFY_AFTER_DEADLINE);
        taskDao.save(task);

        // test due date in the past
        task.setValue(Task.DUE_DATE, DateUtilities.now() - DateUtilities.ONE_DAY);
        service.setScheduler(new AlarmExpected() {
            @Override
            public void createAlarm(Task task, long time, int type) {
                if (time == ReminderService.NO_ALARM)
                    return;
                super.createAlarm(task, time, type);
                assertTrue(time > DateUtilities.now() - 1000L);
                assertTrue(time < DateUtilities.now() + 2 * DateUtilities.ONE_DAY);
                assertEquals(type, ReminderService.TYPE_OVERDUE);
            }
        });
        taskDao.save(task);
        assertTrue(((AlarmExpected)service.getScheduler()).alarmCreated);

        // test due date in the past, but recently notified
        task.setValue(Task.REMINDER_LAST, DateUtilities.now());
        service.setScheduler(new AlarmExpected() {
            @Override
            public void createAlarm(Task task, long time, int type) {
                if (time == ReminderService.NO_ALARM)
                    return;
                super.createAlarm(task, time, type);
                assertTrue(time > DateUtilities.now() + DateUtilities.ONE_HOUR);
                assertTrue(time < DateUtilities.now() + DateUtilities.ONE_DAY);
                assertEquals(type, ReminderService.TYPE_OVERDUE);
            }
        });
        taskDao.save(task);
        assertTrue(((AlarmExpected)service.getScheduler()).alarmCreated);
    }

    /** tests with multiple */
    public void testMultipleReminders() {
        // test due date in the future, enable random
        final Task task = new Task();
        task.setValue(Task.TITLE, "water");
        task.setValue(Task.DUE_DATE, DateUtilities.now() + DateUtilities.ONE_WEEK);
        task.setValue(Task.REMINDER_FLAGS, Task.NOTIFY_AT_DEADLINE);
        task.setValue(Task.REMINDER_PERIOD, DateUtilities.ONE_HOUR);
        service.setScheduler(new AlarmExpected() {
            @Override
            public void createAlarm(Task task, long time, int type) {
                if (time == ReminderService.NO_ALARM)
                    return;
                super.createAlarm(task, time, type);
                assertTrue(time > DateUtilities.now());
                assertTrue(time < DateUtilities.now() + DateUtilities.ONE_DAY);
                assertEquals(type, ReminderService.TYPE_RANDOM);
            }
        });
        taskDao.save(task);
        assertTrue(((AlarmExpected)service.getScheduler()).alarmCreated);

        // now set the due date in the past
        task.setValue(Task.DUE_DATE, DateUtilities.now() - DateUtilities.ONE_WEEK);
        ((AlarmExpected)service.getScheduler()).alarmCreated = false;
        service.scheduleAlarm(task);
        assertTrue(((AlarmExpected)service.getScheduler()).alarmCreated);

        // now set the due date before the random
        task.setValue(Task.DUE_DATE, DateUtilities.now() + DateUtilities.ONE_HOUR);
        service.setScheduler(new AlarmExpected() {
            @Override
            public void createAlarm(Task task, long time, int type) {
                if (time == ReminderService.NO_ALARM)
                    return;
                super.createAlarm(task, time, type);
                assertEquals((long)task.getValue(Task.DUE_DATE), time);
                assertEquals(type, ReminderService.TYPE_DUE);
            }
        });
        taskDao.save(task);
        assertTrue(((AlarmExpected)service.getScheduler()).alarmCreated);
    }


    /** tests with snooze */
    public void testSnoozeReminders() {
        // test due date and snooze in the future
        final Task task = new Task();
        task.setValue(Task.TITLE, "spacemen");
        task.setValue(Task.DUE_DATE, DateUtilities.now() + 5000L);
        task.setValue(Task.REMINDER_FLAGS, Task.NOTIFY_AT_DEADLINE);
        task.setValue(Task.REMINDER_SNOOZE, DateUtilities.now() + DateUtilities.ONE_WEEK);
        service.setScheduler(new AlarmExpected() {
            @Override
            public void createAlarm(Task task, long time, int type) {
                if (time == ReminderService.NO_ALARM)
                    return;
                super.createAlarm(task, time, type);
                assertTrue(time > DateUtilities.now() + DateUtilities.ONE_WEEK - 1000L);
                assertTrue(time < DateUtilities.now() + DateUtilities.ONE_WEEK + 1000L);
                assertEquals(type, ReminderService.TYPE_SNOOZE);
            }
        });
        taskDao.save(task);
        assertTrue(((AlarmExpected)service.getScheduler()).alarmCreated);

        // snooze in the past
        task.setValue(Task.REMINDER_SNOOZE, DateUtilities.now() - DateUtilities.ONE_WEEK);
        service.setScheduler(new AlarmExpected() {
            @Override
            public void createAlarm(Task task, long time, int type) {
                if (time == ReminderService.NO_ALARM)
                    return;
                super.createAlarm(task, time, type);
                assertTrue(time > DateUtilities.now() - 1000L);
                assertTrue(time < DateUtilities.now() + 5000L);
                assertEquals(type, ReminderService.TYPE_DUE);
            }
        });
        taskDao.save(task);
        assertTrue(((AlarmExpected)service.getScheduler()).alarmCreated);
    }


    // --- helper classes

    public class NoAlarmExpected implements AlarmScheduler {
        public void createAlarm(Task task, long time, int type) {
            if(time == 0 || time == Long.MAX_VALUE)
                return;
            fail("created alarm, no alarm expected (" + type + ": " + new Date(time));
        }
    }

    public class AlarmExpected implements AlarmScheduler {
        public boolean alarmCreated = false;
        public void createAlarm(Task task, long time, int type) {
            alarmCreated = true;
        }
    }

}
