/* EINA - EFL data type library
 * Copyright (C) 2015 Carsten Haitzler
 *
 * This library 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 2.1 of the License, or (at your option) any later version.
 *
 * This library 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 library;
 * if not, see <http://www.gnu.org/licenses/>.
 */

# ifdef HAVE_CONFIG_H
#  include "config.h"
# endif

#include <Eina.h>
#include <Ecore.h>

#if __BYTE_ORDER == __LITTLE_ENDIAN
#define SWAP_64(x) x
#define SWAP_32(x) x
#define SWAP_16(x) x
#else
#define SWAP_64(x) eina_swap64(x)
#define SWAP_32(x) eina_swap32(x)
#define SWAP_16(x) eina_swap16(x)
#endif

#define EXTRACT(_buf, pval, sz) \
{ \
   memcpy(pval, _buf, sz); \
   _buf += sz; \
}
#define _EVLOG_INTERVAL 0.2

static int               _evlog_max_times = 0;
static Ecore_Timer      *_evlog_fetch_timer = NULL;
static FILE             *_evlog_file = NULL;

static int _cl_stat_reg_opcode = EINA_DEBUG_OPCODE_INVALID;
static int _cid_from_pid_opcode = EINA_DEBUG_OPCODE_INVALID;
static int _prof_on_opcode = EINA_DEBUG_OPCODE_INVALID;
static int _prof_off_opcode = EINA_DEBUG_OPCODE_INVALID;
static int _cpufreq_on_opcode = EINA_DEBUG_OPCODE_INVALID;
static int _cpufreq_off_opcode = EINA_DEBUG_OPCODE_INVALID;
static int _evlog_get_opcode = EINA_DEBUG_OPCODE_INVALID;

static Eina_Debug_Session *_session = NULL;

static int _cid = 0;

static int my_argc = 0;
static char **my_argv = NULL;

static Eina_Bool
_evlog_get_cb(Eina_Debug_Session *session EINA_UNUSED, int src EINA_UNUSED, void *buffer, int size)
{
   static int received_times = 0;
   unsigned char *d = buffer;
   unsigned int *overflow = (unsigned int *)(d + 0);
   unsigned char *p = d + 4;
   unsigned int blocksize = size - 4;

   if(++received_times <= _evlog_max_times)
     {
        if ((_evlog_file) && (blocksize > 0))
          {
             unsigned int header[3];

             header[0] = 0xffee211;
             header[1] = SWAP_32(blocksize);
             header[2] = *overflow;
             if (fwrite(header, 1, 12, _evlog_file) < 12 ||
                   fwrite(p, 1, blocksize, _evlog_file) < blocksize)
                printf("Error writing bytes to evlog file\n");
          }
     }

   if(received_times == _evlog_max_times)
     {
        printf("Received last evlog response\n");
        if (_evlog_file) fclose(_evlog_file);
        _evlog_file = NULL;
        ecore_main_loop_quit();
     }

   return EINA_TRUE;
}

static Eina_Bool
_cb_evlog(void *data EINA_UNUSED)
{
   static int sent_times = 0;
   Eina_Bool ret = ECORE_CALLBACK_RENEW;
   if(++sent_times <= _evlog_max_times)
         eina_debug_session_send(_session, _cid, _evlog_get_opcode, NULL, 0);

   if(sent_times == _evlog_max_times)
     {
        eina_debug_session_send(_session, _cid, _cpufreq_off_opcode, NULL, 0);
        ecore_timer_del(_evlog_fetch_timer);
        _evlog_fetch_timer = NULL;
        ret = ECORE_CALLBACK_CANCEL;
     }

   return ret;
}

static Eina_Bool
_cid_get_cb(Eina_Debug_Session *session EINA_UNUSED, int cid EINA_UNUSED, void *buffer, int size EINA_UNUSED)
{
   _cid = *(int *)buffer;

   const char *op_str = my_argv[1];
   Eina_Bool quit = EINA_TRUE;

   if ((!strcmp(op_str, "pon")) && (3 <= (my_argc - 1)))
     {
        int freq = SWAP_32(atoi(my_argv[3]));
        eina_debug_session_send(_session, _cid, _prof_on_opcode, &freq, sizeof(int));
     }
   else if (!strcmp(op_str, "poff"))
      eina_debug_session_send(_session, _cid, _prof_off_opcode,  NULL, 0);
   else if (!strcmp(op_str, "evlogon") && (3 <= (my_argc - 1)))
     {
        double max_time;
        sscanf(my_argv[3], "%lf", &max_time);
        _evlog_max_times = max_time > 0 ? (max_time/_EVLOG_INTERVAL+1) : 1;
        eina_debug_session_send(_session, 0, _cl_stat_reg_opcode, NULL, 0);
        printf("Evlog request will be sent %d times\n", _evlog_max_times);
        eina_debug_session_send(_session, _cid, _cpufreq_on_opcode,  NULL, 0);

        /* Creating the evlog file and setting the timer */
        char path[4096];
        int pid = atoi(my_argv[2]);
        snprintf(path, sizeof(path), "%s/efl_debug_evlog-%ld.log",
              getenv("HOME"), (long)pid);
        _evlog_file = fopen(path, "wb");
        _evlog_fetch_timer = ecore_timer_add(_EVLOG_INTERVAL, _cb_evlog, NULL);

        quit = EINA_FALSE;
     }
   else if (!strcmp(op_str, "evlogoff"))
        eina_debug_session_send(_session, _cid, _cpufreq_off_opcode,  NULL, 0);

   if(quit)
        ecore_main_loop_quit();

   return EINA_TRUE;
}

static Eina_Bool
_clients_info_added_cb(Eina_Debug_Session *session EINA_UNUSED, int src EINA_UNUSED, void *buffer, int size)
{
   char *buf = buffer;
   while(size)
     {
        int cid, pid, len;
        EXTRACT(buf, &cid, sizeof(int));
        EXTRACT(buf, &pid, sizeof(int));
        cid = SWAP_32(cid);
        pid = SWAP_32(pid);
        /* We dont need client notifications on evlog */
        if(!_evlog_fetch_timer)
           printf("Added: CID: %d - PID: %d - Name: %s\n", cid, pid, buf);
        len = strlen(buf) + 1;
        buf += len;
        size -= (2 * sizeof(int) + len);
     }
   return EINA_TRUE;
}

static Eina_Bool
_clients_info_deleted_cb(Eina_Debug_Session *session EINA_UNUSED, int src EINA_UNUSED, void *buffer, int size)
{
   char *buf = buffer;
   while(size)
     {
        int cid;
        EXTRACT(buf, &cid, sizeof(int));
        cid = SWAP_32(cid);
        size -= sizeof(int);

        /* If client deleted dont send anymore evlog requests */
        if(_evlog_fetch_timer)
          {
             if(_cid == cid)
               {
                  printf("Evlog debugged App closed (CID: %d), stopping evlog\n", cid);
                  ecore_timer_del(_evlog_fetch_timer);
                  _evlog_fetch_timer = NULL;
                  fclose(_evlog_file);
                  _evlog_file = NULL;
                  ecore_main_loop_quit();
               }
          }
        else
           printf("Deleted: CID: %d\n", cid);
     }
   return EINA_TRUE;
}

static void
_ecore_thread_dispatcher(void *data)
{
   eina_debug_dispatch(_session, data);
}

Eina_Bool
_disp_cb(Eina_Debug_Session *session EINA_UNUSED, void *buffer)
{
   ecore_main_loop_thread_safe_call_async(_ecore_thread_dispatcher, buffer);
   return EINA_TRUE;
}

static void
_args_handle(void *data EINA_UNUSED, Eina_Bool flag)
{
   if (!flag) exit(0);
   eina_debug_session_dispatch_override(_session, _disp_cb);;

   const char *op_str = my_argv[1];
   if (op_str && !strcmp(op_str, "list"))
     {
        eina_debug_session_send(_session, 0, _cl_stat_reg_opcode, NULL, 0);
     }
   else if (2 <= my_argc - 1)
     {
        int pid = atoi(my_argv[2]);
        eina_debug_session_send(_session, 0, _cid_from_pid_opcode, &pid, sizeof(int));
     }
}

EINA_DEBUG_OPCODES_ARRAY_DEFINE(ops,
      {"Daemon/Client/register_observer",  &_cl_stat_reg_opcode,   NULL},
      {"Daemon/Client/added",              NULL,                   &_clients_info_added_cb},
      {"Daemon/Client/deleted",            NULL,                   &_clients_info_deleted_cb},
      {"Daemon/Client/cid_from_pid",       &_cid_from_pid_opcode,  &_cid_get_cb},
      {"Profiler/on",                      &_prof_on_opcode,       NULL},
      {"Profiler/off",                     &_prof_off_opcode,      NULL},
      {"CPU/Freq/on",                      &_cpufreq_on_opcode,    NULL},
      {"CPU/Freq/off",                     &_cpufreq_off_opcode,   NULL},
      {"EvLog/get",                        &_evlog_get_opcode,     _evlog_get_cb},
      {NULL, NULL, NULL}
);

int
main(int argc EINA_UNUSED, char **argv EINA_UNUSED)
{
   ecore_init();

   my_argc = argc;
   my_argv = argv;

   _session = eina_debug_local_connect(EINA_TRUE);
   if (!_session)
     {
        fprintf(stderr, "ERROR: Cannot connect to debug daemon.\n");
        return -1;
     }
   eina_debug_opcodes_register(_session, ops(), _args_handle, NULL);

   ecore_main_loop_begin();

   ecore_shutdown();

   return 0;
}
