#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "debug.h"
#include "lib.h"
#include "tcplib.h"
#include "lprintf.h"
#include "push.h"
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE (!FALSE)
#endif

#ifndef uint8_t
#define uint8_t unsigned char 
#endif
#ifndef uint16_t 
#define uint16_t unsigned short
#endif
#ifndef uint32_t 
#define uint32_t unsigned int
#endif

static int (*imsg)(const char *, ...);
void push_set_imsg(int (*fn)(const char *, ...)) 
{
	imsg = fn;
}
int push_notify_sendone(Chan *c , char *deviceTokenBinary, char *payloadBuff, uint16_t payloadLength, char *reason);

int push_feedback(int sandbox, char *appid, char *reason, int (*fb)(time_t t, char *devto))
{
	Chan *c=NULL;
	char *host=NULL;
	int nrec=0;
	uint32_t f_time;
	char f_token_hex[BFSZ];
	uint16_t f_token_len;
	char f_token[33];
	int port=2196;
	char bf[BFSZ],result[BFSZ], cert[BFSZ];
	static int count=42;
	strcpy(reason,"");

	if (sandbox){
		host="feedback.sandbox.push.apple.com";
		lprintf(cert,BFSZ,"apns_%s_sandbox.pem", appid);
	}else{
		host="feedback.push.apple.com";
		lprintf(cert,BFSZ,"apns_%s.pem", appid);
	}

	tcp_new(&c);
	tcp_ssl_clientcert(c,cert);
	imsg("push_feedback: Open (%s) port %d\n",host,port);
	if (!tcp_open(c,host,port,"")) {
		imsg("push_feedback: open failed\n");
		lprintf(reason,BFSZ-1,"push_notify: Unable to open port %d, %s \n",port,tcp_error(c,bf));
		tcp_free(&c);
		return FALSE;
	}
	imsg("push_feedback: SSL_INIT\n");
	if(!tcp_ssl_init(c,FALSE,NULL,result)){	
		imsg("push_feedback: SSL_INIT-FAILED\n");
		lprintf(reason,BFSZ-1,"push_notify: SSL init failed: %s\n",result);
		tcp_close_nice(c);
		tcp_free(&c);
		return FALSE;
	}
	tcp_setblock(c,TRUE);
	imsg("push_feedback: Reading data\n");
	for (;TRUE;) {
		int r;
		r = tcp_read(c,bf,4+2+32);
		if (r<=0) break;
		nrec++;

		memcpy(&f_time,bf,4);
		memcpy(&f_token_len,bf+4,2);
		memcpy(&f_token,bf+6,32); f_token[32] = 0;
		f_time = ntohl(f_time);
		f_token_len = ntohs(f_token_len);
		lib_bin_to_hex(f_token,32,f_token_hex,BFSZ-1);
		fb(f_time,f_token_hex); // Send to callback function...
		imsg("push_feedback: t=%d len=%d token=%s\n",f_time,f_token_len,f_token_hex);
		// here we should use callback to 'deregister' the devince in question...
	}
	imsg("push_feedback: Reading data - finished %d recordsd, %s\n",nrec,tcp_error(c,bf));
	tcp_close_nice(c);
	tcp_free(&c);
	return TRUE;
}
int push_free(Push *p)
{
	tcp_close_nice(p->c);
	free(p);
	return TRUE;
}
Push *push_init(char *appid, int sandbox, char *reason)
{
	Push *p = calloc(1,sizeof(Push));
	ncpy(p->appid,appid,BFSZ-1);
	p->sandbox = sandbox;
	return p;
}
int push_open(Push *p, char *reason)
{
	char *host=NULL;
	int ret=TRUE;
	int port=2195;
	char bf[BFSZ],result[BFSZ], cert[BFSZ];

	if (tcp_isopen(p->c)) {
		if (tcp_wait_write(p->c,0)) {
			return TRUE;
		} else {
			imsg("push_open: reopen connection as wait_write failed\n");
			tcp_close_nice(p->c);
		}
	}
	strcpy(reason,"Success!");
	if(p->sandbox){
		host="gateway.sandbox.push.apple.com";
		lprintf(cert,BFSZ,"apns_%s_sandbox.pem", p->appid);
	}else{
		host="gateway.push.apple.com";
		lprintf(cert,BFSZ,"apns_%s.pem", p->appid);
	}

	if (p->c==NULL) tcp_new(&p->c);
	tcp_ssl_clientcert(p->c,cert);

	if (!tcp_open(p->c,host,port,"")) {
		lprintf(reason,BFSZ-1,"push_notify: Unable to open port %d, %s \n",port,tcp_error(p->c,bf));
		tcp_free(&p->c);
		return FALSE;
	}
	if(!tcp_ssl_init(p->c,FALSE,NULL,result)){	
		lprintf(reason,BFSZ-1,"push_notify: SSL init failed: %s\n",result);
		tcp_close_nice(p->c);
		tcp_free(&p->c);
		return FALSE;
	}
	return TRUE;
}
int push_notify(Push *p, char *text, int badge, char *sound, char *deviceid, int uid, char *reason)
{
	char xdeviceid[BFSZ];
	int outlen;
	int ret=TRUE;
	char bf[BFSZ];
	char myhex[BFSZ];
	char tmp[BFSZ];
	char msg[BFSZ];
	int trunc;
	if (!push_open(p,reason)) {
		imsg("push_notify failed, socket not open %s\n",reason);
		return FALSE;
	}
	
	// Make sure we have the device id in binary
	lib_hex_to_bin(deviceid,xdeviceid,&outlen);
	imsg("push_notify: deviceid converted to binary  %s\n",lib_bin_to_hex(xdeviceid,outlen,myhex,BFSZ-1));
	
	// Format the payload message using basic JSON notation
	strcpy(msg,"");
	for (trunc = 200;(strlen(msg)==0) || strlen(msg)>252 && trunc>20;) {
		char text2[BFSZ];
		trunc -= 10;
		ncpy(bf,"",BFSZ-1);
		ncpy(text2,text,BFSZ-1); text2[trunc] = 0;
		if (strlen(text)!=0) {
			lprintf(bf,BFSZ,"\"alert\":\"%s\"",lib_web_encode_js(tmp,text2,BFSZ-1)); 
		}

		lprintf(tmp,BFSZ,"\"badge\":%d",badge);
		ncat_sep(bf,",",tmp,BFSZ-1);

		if(sound && strlen(sound)>0){
			lprintf(tmp,BFSZ,"\"sound\":\"%s\"",sound);
			ncat_sep(bf,",",tmp,BFSZ-1);
		}
		lprintf(msg,BFSZ,"{\"aps\":{%s},\"surge\":{\"uid\":%d}}",bf,uid);	
		imsg("push_notify: truncating loop (%d)\n",strlen(msg));
	}
	if(!push_notify_sendone(p->c,xdeviceid,msg,(uint16_t)strlen(msg),reason)){
		// Catch error 
		imsg("push_notify: Send failed %s\n",reason);
		ret = FALSE;
	}else{
		imsg("push_notify: Successfully sent %s\n",msg);
	}
	return ret;
}
// Actual sending of single push notification on open tcp channel
// message format is: |COMMAND|ID|EXPIRY|TOKENLEN|TOKEN|PAYLOADLEN|PAYLOAD|
// for more info see: http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW1
#define DEVICE_BINARY_SIZE 32
#define MAXPAYLOAD_SIZE 256
char *status_text[]={
	"No error"
	,"Processing Error"
	,"Missing Token"
	,"Mising topic"
	,"Missing payload"
	,"Invalid token size"
	,"Invalid topic size"
	,"Invalid payload size"
	,"Invalid token" 
};

int push_notify_sendone(Chan *c, char *deviceTokenBinary, char *payloadBuff, uint16_t payloadLength, char *reason)
{
	char binary_bf[BFSZ];
	char tmp3[BFSZ];
	char *p = binary_bf;
	int r;
	int cmd,status;
	char response[BFSZ];
	uint8_t command = 1; /* command number 1 = Advanced syntax with expiry*/
	uint32_t id, expire;
	uint16_t token_length, payload_length;

	imsg("push_notify: Chan %p, socket %d\n",c,c->sk);

	if (c==NULL || deviceTokenBinary==NULL || payloadBuff==NULL || payloadLength==0) return FALSE;

	if(sizeof(uint8_t)+sizeof(uint16_t)+sizeof(uint32_t)!=1+2+4){
		imsg("push_notify: warning data type sizes wrong!! (notify aborted)");
		return FALSE;
	}

	id = htons(1234);									// id for error code handling
	expire = 0; // htonl(time(NULL)+86400);				// expire message if not delivered in 1 day (0 = do not store at all)
	token_length = htons(DEVICE_BINARY_SIZE);
	payload_length = htons(payloadLength);
	if (payloadLength>254) {
		lprintf(reason,BFSZ-1,"Payload Too Long (%d)",payloadLength);
		return FALSE;
	}

	*p++ = command;										// command
	memcpy(p, &id, sizeof(uint32_t));					// provider preference ordered ID
	p += sizeof(uint32_t);
	memcpy(p, &expire, sizeof(uint32_t));				// expiry date network order
	p += sizeof(uint32_t);
	memcpy(p, &token_length, sizeof(uint16_t));			// token length network order
	p += sizeof(uint16_t);
	memcpy(p, deviceTokenBinary, DEVICE_BINARY_SIZE);	// device token
	p += DEVICE_BINARY_SIZE;

	memcpy(p, &payload_length, sizeof(uint16_t));		// payload length network order
	p += sizeof(uint16_t);
	memcpy(p, payloadBuff, payloadLength);				// payload
	p += payloadLength;

	tcp_setblock(c,TRUE);
	r=tcp_write(c,binary_bf, (p - binary_bf));
	imsg("push_notify: tcp writing n=%d r=%d c->sk=%ld, payloadlen=%d\n",(p - binary_bf), r, c->sk,payloadLength);
	if(r<=0){
		return FALSE;
	}
	imsg("push_notify: Sent %d bytes to socket?\n",r);
	// Ok now read response...
	memset(response,0,sizeof(response));
	if (tcp_wait_read(c,500)) { // while debugging lets wait a fraction of a second for a response...
		r = tcp_read_timed(c,response,6,10);
		if (r!=6) imsg("push_notify response was %d bytes %s\n",r,tcp_error(c,tmp3));
		if (r!=6) printf("push_notify response was %d bytes %s\n",r,tcp_error(c,tmp3));

		if (r>0) {
			cmd = response[0];
			status = response[1]; 
			imsg("Response from apple (cmd=%d) (status=%d)\n",cmd,status);
			if (status<=8) imsg("Response said (%s)\n",status_text[status]);
			if (status<0) status = 0;
			if (status>8) status = 8;
			lprintf(reason,BFSZ-1,"Response cmd=%d status=%d %s",cmd,status,status_text[status]);
			if (status!=0) return FALSE;
		} else {
			lprintf(reason,BFSZ-1,"No response from apple so hopefully ok");
		}
	} else {
		imsg("push_notify: No response from apple, a good sign?\n");
		lprintf(reason,BFSZ-1,"No response from apple so hopefully ok");
	}
	return TRUE;
}
