//
// anyRemote
// a wi-fi or bluetooth remote for your PC.
//
// Copyright (C) 2006-2016 Mikhail Fedotov <anyremote@mail.ru>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "common.h"
#include "timer.h"
#include "utils.h"
#include "hash.h"
#include "lib_wrapper.h"

extern int macroCmdCallback(char *descr, char *exec, cmdParams* p);
extern char tmp[MAXMAXLEN];

//////////////////////////////////////////////////////////////////////////////////
//
// Functions related to timer commands
//
//////////////////////////////////////////////////////////////////////////////////

static int timerAllowed = RC_OK;
static HashTable* _timerHash = NULL;

static void timer_destroyed(void * data) 
{
    //printf("timer_destroyed\n");
    timerCmd * tm = (timerCmd*) data;
    if (tm) {
	//DEBUG2("timer_destroyed(): %s", tm->macro);
	free(tm->macro);
	free(tm);
    }
}

static timerCmd* findTimer(const char *id)
{
    return (id ? hashFind(_timerHash, id) : NULL);
}

HashTable* getTimers(void)
{
    return _timerHash;
}

void forgetTimers(void) // no cleanup
{
    _timerHash = NULL;
}


//
// will be fired first time only after timeout
//
static int createTimer(const char *id, const char *op)
{
    //printf("createTimer\n");
    if (!_timerHash) {
        _timerHash = hashNew((DestroyCallback) timer_destroyed);
    }

    // parse n,m
    char *dup = strdup(op);
    char* comma = strstr(dup,",");
    if (!comma) {
        logger(L_DBG, "createTimer(): wrong format");
	free(dup);
        return EXIT_NOK;
    }
    
    *comma = '\0';
    int tmo = atoi(dup);
    int tms = atoi(comma+1);
    free(dup);
    
    if (tmo <= 0 || tms < 0) {
        logger(L_DBG, "createTimer(): wrong timeout values");
        return EXIT_NOK;
    }

    timerCmd *tm = (timerCmd *) malloc(sizeof(timerCmd));

    tm->macro     = (char*) malloc(strlen(id)+1);
    strcpy(tm->macro,id);
    tm->timeout   = tmo;
    tm->max_times = tms;
    tm->ticks     = 0;
    tm->times     = 1;
    tm->status    = ID_TIMER_CONTINUE;

    hashReplace(_timerHash, id, tm);
    
    DEBUG2("createTimer(): timer created %s %d %d",tm->macro,tm->timeout,tm->max_times);

    return EXIT_OK;
}

static void deleteTimer(char *id)
{
    DEBUG2("deleteTimer() >%s<", id?id:"NULL");
    if (hashRemove(_timerHash, id) != RC_OK) {
        logger(L_DBG, "Can not find timer to delete !");
    }
}

/*static void testTimers(gpointer key, gpointer value, gpointer user_data)
{
    printf("testTimers\n");
    if (timerAllowed != RC_OK) return;
    
    timerCmd *tm = (timerCmd *) value;
    if (!value) return;
    
    printf("testTimers(): %s ", tm->macro);
}*/

void freeTimers(HashTable* hash)
{
    timerAllowed = RC_NOK;
    
    HashTable* h = (hash ?  hash : _timerHash);
    
    hashDestroy(h);

    if (!hash) {
        logger(L_DBG, "freeTimers()");
        _timerHash = NULL;
    }

    timerAllowed = RC_OK;
}

int timerCommand(int subtype, const char *id, const char* op, cmdParams* p)
{
    logger(L_INF, "Command: Timer");
    
    if (id == NULL || id[0] == '\0') {
        logger(L_DBG, "timerCommand(): wrong input - timer id not specified");
        return EXIT_NOK;
    }
    
    timerCmd *tm = findTimer(id);
    if (subtype == ID_TIMER_PAUSE) {
    	if (tm) {
    	    DEBUG2("pause timer() %s", id);
    	    tm->status = ID_TIMER_PAUSE;
    	}
    } else if (subtype == ID_TIMER_CANCEL) {
    	if (tm) {
    	    DEBUG2("cancel timer() >%s<", id);
    	    // postpone removal because in general timer can be canceled
    	    // inside itself
    	    tm->status = ID_TIMER_CANCEL;
    	}
    } else if (subtype == ID_TIMER_CONTINUE) {
    	if (tm) {
    	    DEBUG2("continue timer() %s", id);
    	    tm->status = ID_TIMER_CONTINUE;
    	}
    } else if (subtype == ID_TIMER_RESET) {
    	if (tm) {
    	    DEBUG2("reset timer() %s", id);
    	    tm->ticks  = 0;
    	}
    } else if (subtype == ID_TIMER_RESTART) {
    	if (tm) {
    	    DEBUG2("restart timer() %s", id);
    	    tm->ticks  = 0;
    	    tm->times  = 1;
    	}
    } else if (!tm && op && op[0] != '\0') {
    	return createTimer(id,op);
    } else {
    	DEBUG2("timer %s: incorrect parameter %d", id, subtype);
    	return EXIT_NOK;
    }
    
    return EXIT_OK;
}

static void verifyTimers(void* key, void* value, void* user_data)
{
    //printf("verifyTimers %s\n",key);
    
    if (timerAllowed != RC_OK) return;
    
    int ticksInSec = CAST_POINTER_TO_INT(user_data);
    
    timerCmd *tm = (timerCmd *) value;
    if (!value) return;
    
    if (tm->status == ID_TIMER_CONTINUE) {  // do not process paused timers
    	
	//DEBUG2("verifyTimers(): timer %p\n",tm);
    	//DEBUG2("verifyTimers(): timer descr %s ", tm->macro); 
    	//DEBUG2("verifyTimers(): timer ticks %d ", tm->ticks);
    	//DEBUG2("verifyTimers(): timer tmout %d ", tm->timeout);
    	
	if (tm->ticks >= (tm->timeout * ticksInSec)) {
    	    DEBUG2("verifyTimers(): it is time to execute %s", tm->macro);

    	    macroCmdCallback(tm->macro, NULL, NULL);

    	    tm->ticks = 0;

    	    if (tm->max_times > 0) {
    		tm->times++;
    		if (tm->times >= tm->max_times) {    // Cancel this timer
    		    DEBUG2("verifyTimers(): timer %s self-canceled", tm->macro);
 	            // postpone removal because in general timer can be canceled
	            // inside itself
		    tm->status = ID_TIMER_CANCEL;
    		}
    	    }
    	} else {
    	    tm->ticks++;
    	}
    } else if (tm->status == ID_TIMER_CANCEL) { // delete canceled timers
    	deleteTimer(tm->macro);
    }
}

void verifyTimerCfg(int ticksInSec)
{
    //logger(L_DBG, "verifyTimerCfg()");
     
    if (timerAllowed != RC_OK) return;
    
    hashForeach(_timerHash, verifyTimers, CAST_INT_TO_POINTER(ticksInSec));

}
