#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <Ecore.h>

#include <math.h>

#include "ecore_suite.h"

#define TIMEOUT_1 0.01 // interval for timer1
#define TIMEOUT_2 0.02 // timer2
#define TIMEOUT_3 0.06 // timer3
#define TIMEOUT_4 0.1  // timer4
#define TIMEOUT_5 0.5  // timer5 - end ecore_main_loop_begin()
#define SIZE 3

static int freeze_thaw = 0;   // 1 - freeze timer, 0 - thaw

struct _timers           // timer struct
{
   Ecore_Timer *timer1; // timer 1
   Ecore_Timer *timer2;
   Ecore_Timer *timer3;
   Ecore_Timer *timer4;
   Ecore_Timer *timer5;
   int count_timer1;
   int count_timer3;    // check count timer 3
   int add_timer2;      // flag add timer 2
   int check_freeze_thaw_timer3;
   int num_elem;
   double delay_1[3];
   double interval_1[3];
   double precision[3];
};

static Eina_Bool
_timer1_cb(void *data)
{
   struct _timers *timer = data;
   timer->count_timer1++;

   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_timer2_cb(void *data)
{
   struct _timers *timer = (struct _timers*)data;

   // choose next settings (delay, interval, precision) for timer 1
   if (++timer->num_elem > 2)
     timer->num_elem = 0;

   // check add/delay timer 2
   fail_if(timer->add_timer2 > 1, "Error add/delay timer");

   // check set new delay for timer 1
   ecore_timer_delay(timer->timer1, timer->delay_1[timer->num_elem]);

   // check set new interval for timer 1
   ecore_timer_interval_set(timer->timer1, timer->interval_1[timer->num_elem]);
   fail_if(!EINA_DBL_EQ(timer->interval_1[timer->num_elem], ecore_timer_interval_get(timer->timer1)), "Error set new interval");

   // check set new precision
   ecore_timer_precision_set(timer->precision[timer->num_elem]);
   fail_if(!EINA_DBL_EQ(timer->precision[timer->num_elem], ecore_timer_precision_get()), "Error set new precision");

   // check removal timer 2
   if (ecore_timer_del(timer->timer2))
   { 
     timer->add_timer2 = 0;
     timer->timer2 = NULL;
   }
   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_timer3_cb(void *data)
{
   struct _timers *timer = (struct _timers*)data;

   timer->count_timer3++; // number of starts timer 3

   // check add timer 2
   if (!timer->timer2)
   {
     timer->add_timer2++;   // amount of added timer
     timer->timer2 = ecore_timer_add(TIMEOUT_2, _timer2_cb, timer);
   }

   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_timer4_cb(void *data)
{
   struct _timers *timer = (struct _timers*)data;

   // check frezze/thaw timer 3
   if (freeze_thaw)
   {
      fail_if(timer->check_freeze_thaw_timer3 != timer->count_timer3, "Error frezze/thaw timer");

      ecore_timer_thaw(timer->timer3);
      freeze_thaw = 0;
   }
   else
   {
      ecore_timer_freeze(timer->timer3);
      freeze_thaw = 1;
      timer->check_freeze_thaw_timer3 = timer->count_timer3;
   }

   return ECORE_CALLBACK_RENEW;
}

static Eina_Bool
_timer5_cb(void *data)
{
   struct _timers *timer = (struct _timers*)data;
   if (timer->timer2) timer->timer2 = NULL;
   ecore_main_loop_quit();
   return ECORE_CALLBACK_RENEW;
}

/*
  Timer2 sets new parameters (delay, interval and precision) for Timer1 and checks them.
  Timer3 creates and runs Timer2 and increases parameter add_timer2 to 1. Timer2 removes himself
  and sets parameter add_timer2 to 0. Inside callback of Timer2 parameter add_timer2 should be
  equal to 1 otherwise "Error add/delay timer". 
  Timer4 controls 'freezing' or 'thaw' for Timer3. Timer4 sets flag freeze_thaw to state: 'freeze' or 'thaw'.
  When Timer3 is frozen counter of Timer3 saved in the parameter check_freeze_thaw_timer3.
  Otherwise (thaw mode) check that parameter check_freeze_thaw_timer3 equals to counter Timer3.
  If not equal then "Error frezze/thaw timer".
  Timer 5 finishes testing.
*/

EFL_START_TEST(ecore_test_timers)
{
   struct _timers timer = \
   {
      .num_elem = 0,
      .delay_1 = {0.01, 0.05, 0.1},
      .interval_1 = {0.01, 0.05, 0.1},
      .precision = {0.01, 0.02, 0.03}
   };

   timer.timer1 = ecore_timer_add(TIMEOUT_1, _timer1_cb, &timer);
   timer.timer2 = ecore_timer_add(TIMEOUT_2, _timer2_cb, &timer);
   timer.add_timer2++;
   timer.timer3 = ecore_timer_add(TIMEOUT_3, _timer3_cb, &timer);
   timer.timer4 = ecore_timer_add(TIMEOUT_4, _timer4_cb, &timer);
   timer.timer5 = ecore_timer_add(TIMEOUT_5, _timer5_cb, &timer);

   fail_if((!timer.timer1 || !timer.timer2 || !timer.timer3 || !timer.timer4 || !timer.timer5), "Error add timer\n");

   ecore_main_loop_begin();

   if (timer.timer1)
     ecore_timer_del(timer.timer1);
   if (timer.timer2)
     ecore_timer_del(timer.timer2);
   if (timer.timer3)
     ecore_timer_del(timer.timer3);
   if (timer.timer4)
     ecore_timer_del(timer.timer4);
   if (timer.timer5)
     ecore_timer_del(timer.timer5);
}
EFL_END_TEST

typedef struct _Test_Inside_Call
{
   Ecore_Timer *t;
   int it;
} Test_Inside_Call;

static Eina_Bool
_timeri_cb(void *data)
{
   Test_Inside_Call *c = data;

   ecore_timer_reset(c->t);

   c->it--;

   if (c->it > 0) return EINA_TRUE;
   free(c);
   ecore_main_loop_quit();
   return EINA_FALSE;
}

static Eina_Bool
timeout_timer_cb()
{
   ck_abort();
   return EINA_FALSE;
}

EFL_START_TEST(ecore_test_timer_inside_call)
{
   Test_Inside_Call *c;

   c = malloc(sizeof(Test_Inside_Call));
   c->it = 5;
   c->t = ecore_timer_add(0.01, _timeri_cb, c);
   ecore_timer_add(1.0, timeout_timer_cb, NULL);

   fail_if(!c->t, "Error add timer\n");

   ecore_main_loop_begin();
}
EFL_END_TEST


EFL_START_TEST(ecore_test_timer_valid_callbackfunc)
{
   Ecore_Timer *t = NULL;
   fail_if((t = ecore_timer_add(0.5, NULL, NULL)), "ERROR: Invalid callback func!\n");
}
EFL_END_TEST

static Eina_Bool
_quit_cb(void *data)
{
   Eina_Bool *val = data;
   if (val) *val = EINA_TRUE;
   ecore_main_loop_quit();
   return EINA_FALSE;
}

EFL_START_TEST(ecore_test_ecore_main_loop_timer)
{
   Eina_Bool did = EINA_FALSE;
   Ecore_Timer *timer;
   double start, end, elapsed;


   timer = ecore_timer_add(0.1, _quit_cb, &did);
   fail_if(timer == NULL);

   start = ecore_time_get();
   ecore_main_loop_begin();
   end = ecore_time_get();
   elapsed = end - start;

   fail_if(did == EINA_FALSE);
   fail_if(elapsed < 0.05);
   fail_if(elapsed > 0.15); /* .05 second "error margin" */

}
EFL_END_TEST

static int count = 0;

static Eina_Bool
_timer_cb(void *data)
{
   count++;
   int num = (intptr_t) data;
   fail_if (num != count, "Error timer is called out of order");
   if (count == 8) ecore_main_loop_quit();
   return ECORE_CALLBACK_CANCEL;
}

EFL_START_TEST(ecore_test_timer_in_order)
{
   Ecore_Timer *timer;
   timer = ecore_timer_add(0.001, _timer_cb, (void *) 1);
   fail_if(timer == NULL);
   timer = ecore_timer_add(0.001, _timer_cb, (void *) 2);
   fail_if(timer == NULL);
   timer = ecore_timer_add(0.001, _timer_cb, (void *) 3);
   fail_if(timer == NULL);
   timer = ecore_timer_add(0.001, _timer_cb, (void *) 4);
   fail_if(timer == NULL);
   timer = ecore_timer_add(0.001, _timer_cb, (void *) 5);
   fail_if(timer == NULL);
   timer = ecore_timer_add(0.001, _timer_cb, (void *) 6);
   fail_if(timer == NULL);
   timer = ecore_timer_add(0.001, _timer_cb, (void *) 7);
   fail_if(timer == NULL);
   timer = ecore_timer_add(0.001, _timer_cb, (void *) 8);
   fail_if(timer == NULL);

   ecore_main_loop_begin();
}
EFL_END_TEST

EFL_START_TEST(ecore_test_timer_iteration)
{
   Ecore_Timer *timer;
   count = 0;
   /* verify that timers expire after exactly one loop iteration */
   ecore_timer_add(0, _timer_cb, (void *) 1);
   ecore_main_loop_iterate();
   /* timers should not expire for one loop iteration */
   ck_assert_int_eq(count, 0);
   ecore_main_loop_iterate();
   /* timers should expire after one loop iteration */
   ck_assert_int_eq(count, 1);

   /* verify multiple timer expiration in single mainloop iteration */
   ecore_timer_add(0, _timer_cb, (void *) 2);
   ecore_timer_add(0, _timer_cb, (void *) 3);
   ecore_main_loop_iterate();
   /* timers should not expire for one loop iteration */
   ck_assert_int_eq(count, 1);
   ecore_timer_add(0, _timer_cb, (void *) 4);
   ecore_main_loop_iterate();
   /* all pending and instantiated timers should expire after exactly one loop iteration */
   ck_assert_int_eq(count, 3);
   ecore_main_loop_iterate();
   /* this should not interfere with successive timer processing */
   ck_assert_int_eq(count, 4);

   /* verify out-of-order timer processing solely based on timer times */
   timer = ecore_timer_add(1, _timer_cb, (void *) 6);
   ecore_main_loop_iterate();
   ck_assert_int_eq(count, 4);
   ecore_timer_add(0, _timer_cb, (void *) 5);
   ecore_main_loop_iterate();
   ck_assert_int_eq(count, 4);
   /* timer should expire after exactly 2 iterations */
   ecore_main_loop_iterate();
   ck_assert_int_eq(count, 5);
   ecore_timer_interval_set(timer, 0);
   ecore_timer_reset(timer);
   /* timer should expire after exactly 2 iterations since it becomes un-instantiated now */
   ecore_main_loop_iterate();
   ecore_main_loop_iterate();
   ck_assert_int_eq(count, 6);
}
EFL_END_TEST

static Eina_Bool
_recursion()
{
   static unsigned int recurse = 0;
   static Ecore_Timer *timer;

   switch (recurse++)
     {
      case 0:
        /* verify multiple timer expiration in single mainloop iteration */
        ecore_timer_add(0, _timer_cb, (void *) 2);
        ecore_timer_add(0, _timer_cb, (void *) 3);
        ecore_main_loop_iterate();
        ecore_main_loop_iterate();
        ck_assert_int_eq(count, 6);
        ecore_main_loop_quit();
        break;
      case 1:
        /* timers should not expire for one loop iteration */
        ck_assert_int_eq(count, 1);
        ecore_timer_add(0, _timer_cb, (void *) 4);
        ecore_main_loop_iterate();
        ecore_main_loop_iterate();
        break;
      case 2:
        /* all pending and instantiated timers should expire after exactly one loop iteration */
        ck_assert_int_eq(count, 3);
        ecore_main_loop_iterate();
        ecore_main_loop_iterate();
        break;
      case 3:
        /* this should not interfere with successive timer processing */
        ck_assert_int_eq(count, 4);

        /* verify out-of-order timer processing solely based on timer times */
        timer = ecore_timer_add(1, _timer_cb, (void *) 6);
        ecore_main_loop_iterate();
        ecore_main_loop_iterate();
        break;
      case 4:
        ck_assert_int_eq(count, 4);
        ecore_timer_add(0, _timer_cb, (void *) 5);
        ecore_main_loop_iterate();
        ecore_main_loop_iterate();
        break;
      case 5:
        ck_assert_int_eq(count, 4);
        /* timer should expire after exactly 2 iterations */
        ecore_main_loop_iterate();
        ecore_main_loop_iterate();
        break;
      case 6:
        ck_assert_int_eq(count, 5);
        ecore_timer_interval_set(timer, 0);
        ecore_timer_reset(timer);
        /* timer should expire after exactly 2 iterations since it becomes un-instantiated now */
        ecore_main_loop_iterate();
        ecore_main_loop_iterate();
        break;
      case 7:
        ecore_main_loop_iterate();
        ecore_main_loop_iterate();
        break;
     }
   return EINA_FALSE;
}

EFL_START_TEST(ecore_test_timer_recursion)
{
   Eina_Bool crit = eina_log_abort_on_critical_get();
   int critlevel = eina_log_abort_on_critical_level_get();
   count = 1;
   eina_log_abort_on_critical_set(1);
   eina_log_abort_on_critical_level_set(1);
   ecore_timer_add(0, _recursion, NULL);
   ecore_main_loop_begin();
   ck_assert_int_eq(count, 6);
   eina_log_abort_on_critical_set(crit);
   eina_log_abort_on_critical_level_set(critlevel);
}
EFL_END_TEST

void ecore_test_timer(TCase *tc)
{
  tcase_add_test(tc, ecore_test_timers);
  tcase_add_test(tc, ecore_test_timer_inside_call);
  tcase_add_test(tc, ecore_test_timer_valid_callbackfunc);
  tcase_add_test(tc, ecore_test_ecore_main_loop_timer);
  tcase_add_test(tc, ecore_test_timer_in_order);
  tcase_add_test(tc, ecore_test_timer_iteration);
  tcase_add_test(tc, ecore_test_timer_recursion);
}
