#include "clock.h"

EINTERN Config *time_config = NULL;
EINTERN Eina_List *clock_instances = NULL;
static Ecore_Timer *clock_timer;

static void
_clock_calendar_month_update(Instance *inst)
{
   Evas_Object *od, *oi;
   int x, y;

   oi = elm_layout_edje_get(inst->o_cal);
   edje_object_part_text_set(oi, "e.text.month", inst->month);
   edje_object_part_text_set(oi, "e.text.year", inst->year);
   for (x = 0; x < 7; x++)
     {
        od = edje_object_part_table_child_get(oi, "e.table.daynames", x, 0);
        edje_object_part_text_set(od, "e.text.label", inst->daynames[x]);
        edje_object_message_signal_process(od);
        if (inst->dayweekends[x][0])
          edje_object_signal_emit(od, "e,state,weekend", "e");
        else
          edje_object_signal_emit(od, "e,state,weekday", "e");
     }

   for (y = 0; y < 6; y++)
     {
        for (x = 0; x < 7; x++)
          {
             char buf[32];

             od = edje_object_part_table_child_get(oi, "e.table.days", x, y);
             snprintf(buf, sizeof(buf), "%i", (int)inst->daynums[x][y]);
             edje_object_part_text_set(od, "e.text.label", buf);
             if (inst->dayweekends[x][y])
               edje_object_signal_emit(od, "e,state,weekend", "e");
             else
               edje_object_signal_emit(od, "e,state,weekday", "e");
             if (inst->dayvalids[x][y])
               edje_object_signal_emit(od, "e,state,visible", "e");
             else
               edje_object_signal_emit(od, "e,state,hidden", "e");
             if (inst->daytoday[x][y])
               edje_object_signal_emit(od, "e,state,today", "e");
             else
               edje_object_signal_emit(od, "e,state,someday", "e");
             edje_object_message_signal_process(od);
          }
     }
   edje_object_message_signal_process(oi);
}

static void
_clock_month_prev_cb(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   Instance *inst = data;
   inst->madj--;
   time_instance_update(inst);
   _clock_calendar_month_update(inst);
}

static void
_clock_month_next_cb(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   Instance *inst = data;
   inst->madj++;
   time_instance_update(inst);
   _clock_calendar_month_update(inst);
}

static void
_clock_mouse_wheel_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Instance *inst = data;
   Evas_Event_Mouse_Wheel *ev = event_info;

   if (ev->z < 0)
     _clock_month_prev_cb(inst, NULL, NULL, NULL);
   else if (ev->z > 0)
     _clock_month_next_cb(inst, NULL, NULL, NULL);
}

static void
_clock_popup_dismissed(void *data EINA_UNUSED, Evas_Object *obj, void *info EINA_UNUSED)
{
   evas_object_del(obj);
}

static void
_eval_instance_size(Instance *inst)
{
   Evas_Coord mw, mh;
   int sw = 0, sh = 0;
   Evas_Object *ed = elm_layout_edje_get(inst->o_clock);

   edje_object_size_min_get(ed, &mw, &mh);

   if ((mw < 1) || (mh < 1))
     {
        if (edje_object_part_exists(ed, "e.sizer"))
          {
             edje_object_part_geometry_get(ed, "e.sizer", NULL, NULL, &mw, &mh);
          }
        else
          {
             Evas_Object *owner;

             owner = e_gadget_site_get(inst->o_clock);
             switch (e_gadget_site_orient_get(owner))
               {
                case E_GADGET_SITE_ORIENT_HORIZONTAL:
                  evas_object_geometry_get(owner, NULL, NULL, NULL, &sh);
                  break;

                case E_GADGET_SITE_ORIENT_VERTICAL:
                  evas_object_geometry_get(owner, NULL, NULL, &sw, NULL);
                  break;

                default: break;
               }

             evas_object_resize(inst->o_clock, sw, sh);
             edje_object_message_signal_process(ed);

             edje_object_parts_extends_calc(ed, NULL, NULL, &mw, &mh);
          }
     }

   if (mw < 4) mw = 4;
   if (mh < 4) mh = 4;

   if (mw < sw) mw = sw;
   if (mh < sh) mh = sh;

   evas_object_size_hint_aspect_set(inst->o_clock, EVAS_ASPECT_CONTROL_BOTH, mw, mh);
}

static void
_clock_edje_init(Instance *inst, Evas_Object *o)
{
   char datestr[128];
   const char *digital[] =
   {
      "e/gadget/clock/digital",
      "e/gadget/clock/digital/advanced",
   };

   time_datestring_format(inst, datestr, sizeof(datestr) - 1);
   if (inst->cfg->digital_clock)
     e_theme_edje_object_set(o, NULL, digital[inst->cfg->advanced]);
   else
     e_theme_edje_object_set(o, NULL, "e/gadget/clock/analog");
   if (inst->cfg->show_date)
     elm_layout_signal_emit(o, "e,state,date,on", "e");
   else
     elm_layout_signal_emit(o, "e,state,date,off", "e");
   if (inst->cfg->digital_24h)
     elm_layout_signal_emit(o, "e,state,24h,on", "e");
   else
     elm_layout_signal_emit(o, "e,state,24h,off", "e");
   if (inst->cfg->show_seconds)
     elm_layout_signal_emit(o, "e,state,seconds,on", "e");
   else
     elm_layout_signal_emit(o, "e,state,seconds,off", "e");

   elm_object_part_text_set(o, "e.text.sub", datestr);
   if (inst->cfg->timezone)
     {
        Edje_Message_String msg;

        msg.str = (char*)inst->cfg->timezone;
        edje_object_message_send(elm_layout_edje_get(o), EDJE_MESSAGE_STRING, 1, &msg);
     }
   {
      Edje_Message_String_Int msg;
      msg.str = (char*)inst->cfg->colorclass[0] ?: "";
      msg.val = !!inst->cfg->colorclass[0];
      edje_object_message_send(elm_layout_edje_get(o), EDJE_MESSAGE_STRING_INT, 2, &msg);
      msg.str = (char*)inst->cfg->colorclass[1] ?: "";
      msg.val = !!inst->cfg->colorclass[1];
      edje_object_message_send(elm_layout_edje_get(o), EDJE_MESSAGE_STRING_INT, 3, &msg);
   }
   edje_object_message_signal_process(elm_layout_edje_get(o));
}

static Eina_Bool
_clock_timer(void *d EINA_UNUSED)
{
   Eina_List *l;
   Instance *inst;
   Eina_Bool seconds = EINA_FALSE;
   int sec = 0;
   char buf[128];

   EINA_LIST_FOREACH(clock_instances, l, inst)
     {
        if (!inst->cfg->advanced) continue;
        seconds |= inst->cfg->show_seconds;
        sec = time_string_format(inst, buf, sizeof(buf));
        elm_object_part_text_set(inst->o_clock, "e.text", buf);
        time_datestring_format(inst, buf, sizeof(buf) - 1);
        elm_object_part_text_set(inst->o_clock, "e.text.sub", buf);
        _eval_instance_size(inst);
     }
   sec = seconds ? 1 : (61 - sec);
   if (clock_timer)
     ecore_timer_interval_set(clock_timer, sec);
   else
     clock_timer = ecore_timer_loop_add(sec, _clock_timer, NULL);
   return EINA_TRUE;
}

static void
_clock_popup_del(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
  Instance *inst = data;

  if (obj != inst->popup) return;
  inst->popup = inst->o_table = inst->o_cal = NULL;
}

EINTERN void
clock_popup_new(Instance *inst)
{
   Evas_Object *oi;

   if (inst->popup) return;

   inst->madj = 0;

   time_instance_update(inst);

   inst->popup = elm_ctxpopup_add(inst->o_clock);
   elm_object_style_set(inst->popup, "noblock");
   evas_object_smart_callback_add(inst->popup, "dismissed", _clock_popup_dismissed, inst);
   evas_object_event_callback_add(inst->popup, EVAS_CALLBACK_DEL, _clock_popup_del, inst);
   evas_object_event_callback_add(inst->popup, EVAS_CALLBACK_MOUSE_WHEEL,
                                   _clock_mouse_wheel_cb, inst);

   inst->o_table = elm_table_add(inst->popup);

   oi = elm_layout_add(inst->o_table);
   inst->o_cal = oi;
   e_theme_edje_object_set(oi, "base/theme/gadget/clock",
                           "e/gadget/clock/calendar");
   _clock_calendar_month_update(inst);

   elm_object_signal_callback_add(oi, "e,action,prev", "*",
                                   _clock_month_prev_cb, inst);
   elm_object_signal_callback_add(oi, "e,action,next", "*",
                                   _clock_month_next_cb, inst);
   edje_object_message_signal_process(elm_layout_edje_get(oi));
   elm_layout_sizing_eval(oi);
   elm_table_pack(inst->o_table, oi, 0, 1, 1, 1);
   evas_object_show(oi);

   elm_object_content_set(inst->popup, inst->o_table);
   e_gadget_util_ctxpopup_place(inst->o_clock, inst->popup, NULL);
   evas_object_show(inst->popup);
}

void
clock_instances_redo(void)
{
   Eina_List *l;
   Instance *inst;

   EINA_LIST_FOREACH(clock_instances, l, inst)
     {
        _clock_edje_init(inst, inst->o_clock);
        _eval_instance_size(inst);
     }
}

static void
_clock_cb_mouse_up(void *data, Evas *evas EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event)
{
   Instance *inst = data;
   Evas_Event_Mouse_Up *ev = event;

   if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD) return;
   ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD;
   if (ev->button == 1)
     {
        if (inst->popup) elm_ctxpopup_dismiss(inst->popup);
        else clock_popup_new(inst);
     }
}

static void
_clock_sizing_changed_cb(void *data, Evas_Object *obj EINA_UNUSED, const char *emission EINA_UNUSED, const char *source EINA_UNUSED)
{
   _eval_instance_size(data);
}

static void
_clock_gadget_removed_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info)
{
   Instance *inst = data;

   if (inst->o_clock != event_info) return;
   if (!inst->cfg) return;
   time_config->items = eina_list_remove(time_config->items, inst->cfg);
   eina_stringshare_del(inst->cfg->timezone);
   eina_stringshare_del(inst->cfg->time_str[0]);
   eina_stringshare_del(inst->cfg->time_str[1]);
   E_FREE(inst->cfg);
}

static void
clock_del(void *data, Evas *e EINA_UNUSED, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   Instance *inst = data;
   Eina_List *l;
   Eina_Bool advanced = EINA_FALSE, seconds = EINA_FALSE;

   evas_object_smart_callback_del_full(e_gadget_site_get(obj), "gadget_removed", _clock_gadget_removed_cb, inst);
   clock_instances = eina_list_remove(clock_instances, inst);
   evas_object_del(inst->popup);
   time_daynames_clear(inst);
   if (inst->cfg)
     {
        advanced |= !!inst->cfg->advanced;
        seconds |= !!inst->cfg->show_seconds;
        if (inst->cfg->inst == inst) inst->cfg->inst = NULL;
     }
   free(inst);
   if ((!advanced) && (!seconds)) return;
   EINA_LIST_FOREACH(clock_instances, l, inst)
     {
        advanced |= !!inst->cfg->advanced;
        seconds |= !!inst->cfg->show_seconds;
        if (seconds) break;
     }
   if (seconds) return; //no change possible
   E_FREE_FUNC(clock_timer, ecore_timer_del);
   if (advanced)
     _clock_timer(NULL);
}

static Config_Item *
_conf_item_get(int *id, Eina_Bool digital)
{
   Config_Item *ci;
   Eina_List *l;

   if (*id > 0)
     {
        EINA_LIST_FOREACH(time_config->items, l, ci)
          if (*id == ci->id) return ci;
     }

   ci = E_NEW(Config_Item, 1);
   if (!*id)
     *id = ci->id = time_config->items ? eina_list_count(time_config->items) + 1 : 1;
   else if (*id < 0)
     ci->id = -1;
   else
     ci->id = *id;

   ci->weekend.start = 6;
   ci->weekend.len = 2;
   ci->week.start = 1;
   ci->digital_clock = digital;
   ci->digital_24h = 0;
   ci->show_seconds = 0;
   ci->show_date = 0;
   ci->time_str[0] = eina_stringshare_add("%I:%M");
   ci->time_str[1] = eina_stringshare_add("%F");

   if (ci->id < 1) return ci;
   time_config->items = eina_list_append(time_config->items, ci);
   e_config_save_queue();

   return ci;
}

static Evas_Object *
_clock_gadget_configure(Evas_Object *g)
{
   Instance *inst = evas_object_data_get(g, "clock");
   inst->cfg->inst = inst;
   return config_clock(inst->cfg, e_comp_object_util_zone_get(g));
}

static void
_clock_gadget_created_cb(void *data, Evas_Object *obj, void *event_info EINA_UNUSED)
{
   Instance *inst = data;

   e_gadget_configure_cb_set(inst->o_clock, _clock_gadget_configure);
   evas_object_smart_callback_del_full(obj, "gadget_created", _clock_gadget_created_cb, data);
   if (inst->cfg->advanced)
     {
        _clock_timer(NULL);
        ecore_timer_loop_reset(clock_timer);
     }
   _eval_instance_size(inst);
}

static Evas_Object *
clock_create(Evas_Object *parent, Instance *inst, E_Gadget_Site_Orient orient)
{
   Evas_Object *o;
   const char *sig = NULL;

   inst->o_clock = o = elm_layout_add(parent);
   elm_layout_signal_callback_add(o, "e,state,sizing,changed", "*",
                                   _clock_sizing_changed_cb, inst);

   _clock_edje_init(inst, o);

   switch (orient)
     {
      case E_GADGET_SITE_ORIENT_HORIZONTAL:
        sig = "e,state,horizontal";
        break;

      case E_GADGET_SITE_ORIENT_VERTICAL:
        sig = "e,state,vertical";
        break;

      default:
        sig = "e,state,float";
     }

   elm_layout_signal_emit(inst->o_clock, sig, "e");

   evas_object_event_callback_add(o, EVAS_CALLBACK_DEL, clock_del, inst);
   evas_object_smart_callback_add(parent, "gadget_created", _clock_gadget_created_cb, inst);
   evas_object_smart_callback_add(parent, "gadget_removed", _clock_gadget_removed_cb, inst);
   evas_object_data_set(o, "clock", inst);

   evas_object_event_callback_add(inst->o_clock,
                                  EVAS_CALLBACK_MOUSE_UP,
                                  _clock_cb_mouse_up,
                                  inst);

   if (inst->cfg->id < 0) return o;
   clock_instances = eina_list_append(clock_instances, inst);

   return o;
}

EINTERN Evas_Object *
digital_clock_create(Evas_Object *parent, int *id, E_Gadget_Site_Orient orient)
{
   Instance *inst;

   inst = E_NEW(Instance, 1);
   inst->cfg = _conf_item_get(id, 1);
   return clock_create(parent, inst, orient);
}

EINTERN Evas_Object *
analog_clock_create(Evas_Object *parent, int *id, E_Gadget_Site_Orient orient)
{
   Instance *inst;

   inst = E_NEW(Instance, 1);
   inst->cfg = _conf_item_get(id, 0);
   return clock_create(parent, inst, orient);
}

EINTERN void
time_config_update(Config_Item *ci)
{
   Eina_List *l;
   Instance *inst;
   Eina_Bool advanced = EINA_FALSE;

   ci->week.start = (ci->weekend.start + ci->weekend.len) % 7;
   EINA_LIST_FOREACH(clock_instances, l, inst)
     {
         if (inst->cfg != ci) continue;
         _clock_edje_init(inst, inst->o_clock);
         if (!advanced)
           {
              advanced |= inst->cfg->advanced;
              if (!inst->cfg->advanced) continue;
              _clock_timer(NULL);
              ecore_timer_loop_reset(clock_timer);
           }
        _eval_instance_size(inst);
     }
   if (!advanced)
     E_FREE_FUNC(clock_timer, ecore_timer_del);
   e_config_save_queue();
}

EINTERN void
clock_timer_set(Eina_Bool set)
{
   if (set)
     {
        if (clock_instances) _clock_timer(NULL);
     }
   else
     E_FREE_FUNC(clock_timer, ecore_timer_del);
}

EINTERN void
clock_date_update(void)
{
   Eina_List *l;
   Instance *inst;
   char buf[128];

   EINA_LIST_FOREACH(clock_instances, l, inst)
     {
        time_datestring_format(inst, buf, sizeof(buf) - 1);
        elm_object_part_text_set(inst->o_clock, "e.text.sub", buf);
        _eval_instance_size(inst);
     }
}
