//
// 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 <errno.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "common.h"
#include "lib_wrapper.h"
#include "utils.h"
#include "conf.h"
#include "mode.h"
#include "var.h"
#include "alarm.h"
#include "queue.h"
#include "mutex.h"
#include "timer.h"

extern char         tmp[MAXMAXLEN];
extern CONF         conf;

// Some globals

char logfile [MAXLEN];

// used by Flush() command to temporary store old configuration
static mode       *flushModes   = NULL;
static SingleList *flushAlarms  = NULL;
static HashTable  *flushTimers  = NULL;

#ifdef USE_ICONV

#include <iconv.h>

iconv_t convertorTo   = (iconv_t) -1;
iconv_t convertorFrom = (iconv_t) -1;

#endif

//////////////////////////////////////////////////////////////////////////////////
//
// Functions related to logging
//
//////////////////////////////////////////////////////////////////////////////////

static void initFile(char* what, const char* name)
{
    // store data in first arg
   
    int useuser = 0;
    
    char *prefix = dupVarValue("TmpDir");
    char *h = getenv("HOME");  
    
    if (prefix) {
        strcat(what, prefix);
        if (h) {
            if (strncmp(prefix, h, strlen(h)) != 0) {
                useuser = 1;
            }
        } else {
            useuser = 1;
        }
    } else {
        if (h) {
            strcat(what, h);
            strcat(what, "/.anyRemote");
        } else {			// could it ever happen ?
            strcat(what, "/tmp");
            useuser = 1;
        }
    }
    strcat(what, name);
    
    if (useuser == 1) {
        char *u = getenv("USER");
        if (prefix && u) {
            strcat(what, ".");
            strcat(what, u);
        }
    }
    
    free(prefix);
    return;
}

void initLog()
{
    if (getLog()) {

        initFile(logfile, LOGFILE);

        printf("INFO: log file is %s\n",logfile);

        // Just  truncate file
        FILE *fplog = fopen(logfile, "w");
        if (fplog) {
            fclose(fplog);
        }
        if(getuid()==0 && conf.uid) { // do not open file as superuser
            #ifdef __cplusplus
	        int dummy = 
	        #endif
	        chown(logfile,conf.uid,conf.gid);
        }

        mutexNew(M_LOG);
        
	    printTime();
        sprintf(tmp, "anyRemote v%s", PACKAGE_VERSION);
	    logger(L_CFG, tmp);
    }
}

void releaseLog()
{
    mutexRemove(M_LOG);
}

static void logger2(const char *head, const char *str)
{
    //printf("logger ENTER\n");fflush(stdout);
    FILE *fplog;
    time_t logtime;
    
    //struct timeval  tv;
    //struct timezone tz;

    if (!getLog() ||
        logfile[0] == '\0' ||
        (head && strcmp(head, "DBG") == 0 && getDebug() == 0)) {
        return;
    }
    
    mutexLock(M_LOG);

    fplog = fopen(logfile, "a");
    if (fplog!=NULL) {
        if (head && strcmp(head, "CFG") != 0) {
            
	        time(&logtime);
            struct tm* timeinfo = localtime (&logtime );
           
	        //gettimeofday(&tv, &tz);

            char stime[32];
            stime[8] = '\0';

            sprintf(stime, "%2d:%2d:%2d", timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec);
            //sprintf(stime, "%2d:%2d", tv.tv_sec, tv.tv_usec/1000);
	    
            fprintf(fplog, "[%s] - ",stime);
        }
        if(head != NULL && strcmp(head, "CFG") != 0) {
            fprintf(fplog, "%s - ",head);
        }
        fprintf(fplog, "%s\n",str);

        fclose(fplog);
    } else {
        printf("Can not open log file >%s<\n", logfile);
	    logfile[0] = '\0';  // print this error only once
    }

    mutexUnlock(M_LOG);
    
    //printf("logger EXIT\n");fflush(stdout);
}

void logger(int head, const char *str)
{
    switch (head) {
        case L_CFG:
	    logger2("CFG", str);
	    break;
	case L_INF:
	    logger2("INF", str);
	    break;
	case L_WARN:
	    logger2("WARNING", str);
	    break;
        case L_ERR:
	    logger2("ERR", str);
	    break;
	//case L_DBG:
	default:
	    logger2("DBG", str);
	    break;
    }
}

void printTime()
{
    time_t logtime;
    time(&logtime);

    char *timestr = (char *) ctime(&logtime);
    timestr[strlen(timestr)-1]=0;
    logger(L_CFG, timestr);
}

//////////////////////////////////////////////////////////////////////////////////
//
// Functions related to encoding conversion
//
//////////////////////////////////////////////////////////////////////////////////
int needConvert() 
{
    int ret = 0;
    #ifdef USE_ICONV
    if (convertorTo != (iconv_t) -1) {
         ret = 1;
    }
    #endif
    return ret;
}

#ifdef USE_ICONV
void closeConvertor(void)
{
    if (convertorTo != (iconv_t) -1) {
        iconv_close(convertorTo);
	    convertorTo = (iconv_t) -1;
    }
    if (convertorFrom != (iconv_t) -1) {
        iconv_close(convertorFrom);
	    convertorFrom = (iconv_t) -1;
    }
}

void encodingHook() 
{
    closeConvertor();
    
    char* to = dupVarValue("ToEncoding");
    if (!to) return;
    
    char* from = dupVarValue("FromEncoding");
    if (!from) {
        free(to);
        return;
    }
    
    DEBUG2("encodingHook() start conversion from >%s< to >%s<", from, to);

    convertorTo = iconv_open(to, from);
    if (convertorTo == (iconv_t) - 1) {
        ERROR2("Can start conversion %s -> %s, not supported by iconv()", from, to);
    }
    
    convertorFrom = iconv_open(from, to);
    if (convertorFrom == (iconv_t) - 1) {
        ERROR2("Can start conversion %s -> %s, not supported by iconv()", to, from);
    }
    
    free(to);
    free(from);
}
#endif


//////////////////////////////////////////////////////////////////////////////////
//
// Functions related to Flush() command
//
//////////////////////////////////////////////////////////////////////////////////

extern int  flushConf;

int flushData()
{
    //logger(L_DBG,"flushData");

    flushConf = 1;

    flushModes = getModes();
    forgetModes();

    flushAlarms = getAlarms();
    forgetAlarms();

    flushTimers = getTimers();
    forgetTimers();

    // Now we ready to load new cfg.
    return EXIT_OK;
}

void flushOldConf()
{

    freeTimers(flushTimers);
    flushTimers = NULL;
    
    freeModes(flushModes);
    flushModes = NULL;
    
    freeAlarms(flushAlarms);
    flushAlarms = NULL;
}

void printHelp()
{
    printf("Usage: anyremote [-f conf.file] [-s peer[,peer ...]] [-log] [-a] \n");
    printf("                 [-cfgdir directory] [-tmpdir directory]\n");
    printf("                 [-fe port] [-u username] [-name SDP service name] [-password]\n");
    printf("   or: anyremote -h|-v\n\n");
    printf(" -h|--help  print this help\n");
    printf(" -v|--version  print version of anyRemote\n");
    printf(" -f /path/to/file  specify configuration file\n");
    printf(" -s peer[,peer ...], where peer possible values are\n");
    printf("    tcp:<port>          (Server mode - TCP/IP connection)\n");
    printf("    bluetooth:<channel 1-32> (Server mode - bluetooth connection)\n");
    printf("    web:<port>          (Server mode - Web interface)\n");
    printf("    cmxml:<port>        (Server mode - XML interface for Cisco IP phones)\n");
    printf("    local:/dev/ircommX  (Server mode - IR connection)\n");
    printf("    rfcomm:XX:XX:XX:XX:XX:XX:CC (AT mode - bluetooth connection,\n");
    printf("        where XX:XX:XX:XX:XX:XX is bluetooth device address,\n");
    printf("        and CC is channel number - integer from 1 to 32)\n");
    printf("    /dev/ttyACM#        (AT mode - cable connection)\n");
    printf("    /dev/ircomm#        (AT mode - IR connection)\n");
    printf("    ilirc:<AF_LOCAL socket file> (use with inputlircd)\n");
    printf("    stdin\n");
    printf("    avahi - register SDP service using Avahi\n");
    printf("  Default peer value is bluetooth,tcp:5197,web:5080,avahi\n");
    printf("  It is possible to specify several peers for Server mode configuration files only\n");
    printf("  It is possible to specify only single peer of web: or cmxml: type\n");
    printf(" -log print verbose logging information to $HOME/.anyRemote/anyremote.log\n");
    printf(" -cfgdir override default location of directory with configuration files\n");
    printf(" -tmpdir override default location of directory to store temporary files\n");
    printf(" -a	reconnect automatically in case of connection failure, used only in AT-mode\n");
    printf(" -fe <port> work as backend for GUI frontend. Use specified port to connect to frontend.\n");
    printf(" -name <SDP service name> if bluetooth or TCP/IP connection is used, allows one to specify SDP service name\n");
    printf(" -password ask password on connect\n");
    printf("           password should be stored in $HOME/.anyRemote/password file in a plain text\n");
    printf(" -u|--user <username> if started from root, allows one to set effective user ID to specified user\n\n");

}

int getUidGid(char *username, uid_t *uid, gid_t *gid)
{
    /* Set uid and gid to the preferred user (found in setuid.h). Can either be
    * numeric or a string, found in /etc/passwd.	*/
    struct passwd *pw;

    if ((pw = getpwnam(username))) {
        // Name exists
        *uid = pw->pw_uid;
        *gid = pw->pw_gid;
        return EXIT_OK;
    }
    /* something Bad happened, so send back an error */
    return EXIT_NOK;
}

void freeMMessage(void* ptr)
{
    int *mm = (int*) ptr;
    free(mm);
}

static void sendToMainThread(int *m)
{
    queuePush(Q_MAIN, (void*) m);
}

void sendAbort()
{
    int* i = (int*) malloc(sizeof(int));
    *i = M_ABORT;
    sendToMainThread(i);
}

void sendDisconnect()
{
    int* i = (int*) malloc(sizeof(int));
    *i = M_DISCONNECT;
    sendToMainThread(i);
}

void stripCommandEnding(char *s)
{
    if (s && strlen(s) >= 2) {
        s[strlen(s)-2] = '\0';   // strip );
    }
}

//////////////////////////////////////////////////////////////////////////////////

void errnoDebug(const char* tag, int err) 
{
   
    DEBUG2("[DS]: %s error %d (%s)", tag, err, strerror(err));
    /*switch (err) {
    	case EPERM:
            DEBUG2("[DS]: %s error EPERM (Operation not permitted)", tag);
            break;
     	case ENOENT:
            DEBUG2("[DS]: %s error ENOENT (No such file or directory)", tag);
            break;
    	case EINTR:
            DEBUG2("[DS]: %s error EINTR (Interrupted system call)", tag);
            break;
    	case EIO:
            DEBUG2("[DS]: %s error EIO (I/O error"), tag);
            break;
   	case EBADF:
            DEBUG2("[DS]: %s error EBADF (Bad file number)", tag);
            break;
    	case EAGAIN:
            DEBUG2("[DS]: %s error EAGAIN (Try again)", tag);
            break;
    	case ENOMEM:
            DEBUG2("[DS]: %s error ENOMEM (Out of memory)", tag);
            break;
    	case EBUSY:
            DEBUG2("[DS]: %s error EBUSY (Device or resource busy)", tag);
            break;
   	case EFAULT:
            DEBUG2("[DS]: %s error EFAULT (Bad address)", tag);
            break;
    	case EISDIR:
            DEBUG2("[DS]: %s error EISDIR (Is a directory)", tag);
            break;
    	case EINVAL:
            DEBUG2("[DS]: %s error EINVAL (Invalid argument)", tag);
            break;
    	case EFBIG:
            DEBUG2("[DS]: %s error EFBIG (File too large)", tag);
            break;
    	case ENOSPC:
            DEBUG2("[DS]: %s error ENOSPC (No space left on device)", tag);
            break;
    	case EPIPE:
            DEBUG2("[DS]: %s error EPIPE (Broken pipe)", tag);
            break;
     	case EDESTADDRREQ:
            DEBUG2("[DS]: %s error EDESTADDRREQ (Destination address required)", tag);
            break;
    	case ECONNRESET:
            DEBUG2("[DS]: %s error ECONNRESET (Connection reset by peer)", tag);
            break;
    	default :
            DEBUG2("[DS]: %s error %d", tag, err);
            break;
    }*/	 
}
//////////////////////////////////////////////////////////////////////////////////
//
// iconv() support
//
//////////////////////////////////////////////////////////////////////////////////

#ifdef USE_ICONV

char * convCharsetSimple(char *str, int direction)
{
    if (!str) return strdup("");
    size_t size = strlen(str);
    
    return convCharset(str, size, direction);
}

char * convCharset(char *str, size_t size, int direction)
{
    DEBUG2("convCharset >%s<", str);
    char *out;
    
    if (!str || size == 0 || 
        (direction == CNV_TO   && convertorTo   == (iconv_t) - 1) || 
	(direction == CNV_FROM && convertorFrom == (iconv_t) - 1)) {
	
	DEBUG2("convCharset skip conversion");
        out = strdup("");
	
    } else {
        
	char *buf, *holder;
        size_t insize, outsize, bufsize, r;

        insize = size;
        outsize = bufsize = insize * 4;

        buf = (char *) malloc(bufsize);
        holder = buf;
	
	    iconv_t cnv = (direction == CNV_TO ? convertorTo : convertorFrom);

        r = iconv(cnv, &str, &insize, &buf, &outsize);
        if (r < 0 || insize != 0) {
	    DEBUG2("convCharset error on conversion %d", (int) outsize);
            free(holder);
            return strdup("");
        }


        size = bufsize - outsize;

        buf -= size;
        out = (char *) malloc(size + 1);
        memcpy(out, buf, size);
	    out[size] = '\0';
	
        free(holder);
    }

    return out;
}

#endif

//////////////////////////////////////////////////////////////////////////////////
