/*
 * PDU common
 *
 * Copyright (C) 2010 by James Maki
 *
 * Author: James Maki <jamescmaki@gmail.com>
 *
 * 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 2 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */

#define _GNU_SOURCE

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <ctype.h>
#include <time.h>
#include <unistd.h>

#include "config.h"
#include "log.h"
#include "pdu.h"
#include "sms_utils.h"

int hex_nibble_scan(const char *buf, size_t len)
{
	static const char digits[] = "0123456789ABCDEF";

	int i;
	int result = 0;

	STRLEN_CHECK(buf, len, -1);

	for (i = 0; i < len; i++) {
		const char *where;

		where = strchr(digits, toupper(buf[i]));
		if (!where) {
			return -1;
		}

		result = (result << 4) + (where - digits);
	}

	return result;
}

int hex_byte_decode(const char *buf)
{
	return hex_nibble_scan(buf, HEX_BYTE_LEN);
}

int hex_byte_encode(char *buf, size_t len, int byte)
{
	if (len < HEX_BYTE_LEN) {
		log_error("buffer is not large enough");
	}

	return snprintf(buf, len, "%02X", byte & 0xFF);
}

int nibble_swap(char *buf, size_t len)
{
	int i;
	char c;

	if (len & 1) {
		BUG("odd buffer size found");
	}

	for (i = 0; i < len; i += HEX_BYTE_LEN) {
		c = buf[i];
		buf[i] = buf[i + 1];
		buf[i + 1] = c;
	}

	return i;
}

int strunpad(char *str, unsigned char c)
{
	char *cp = strchr(str, c);

	if (cp) {
		*cp = '\0';
	}

	return strlen(str);
}

int strpad(char *str, size_t len, unsigned char c)
{
	int i;
	int start = strlen(str);

	for (i = start; i < len; i++) {
		str[i] = c;
	}

	str[i] = '\0';

	return i - start;
}

int pdu_format_timestamp(struct pdu_info *pdu, char *str, size_t len, const char *fmt)
{
	if (len <= 0) {
		return 0;
	}

	switch (pdu->type.msg_type) {
	case PDU_MTI_DELIVER:
	case PDU_MTI_REPORT:
		return strftime(str, len, fmt, &pdu->timestamp);
	default:
		*str = '\0';
		return 0;
	}
}

int pdu_format_vp(struct pdu_info *pdu, char *str, size_t len)
{
	int val;

	if (len <= 0) {
		return 0;
	}

	*str = '\0';

	val = pdu->validity_period;

	switch (pdu->type.msg_type) {
	case PDU_MTI_SUBMIT:
		if (val >= 0 && val <= 143) {
			val = (val + 1) * 5;
		} else if (val >= 144 && val <= 167) {
			val = ((val - 143) * 30) + (12 * 60);
		} else if (val >= 168 && val <= 196) {
			val = (val - 166) * (24 * 60);
		} else {
			val = (val - 192) * (7 * 24 * 60);
		}

		return snprintf(str, len, "%d", val);
	default:
		return 0;
	}
}

int pdu_addr_check(const char *addr_str)
{
	if (addr_str[0] == '+') {
		addr_str++;
	}

	if (!*addr_str) {
		log_error("empty addr");
		return -1;
	}

	int len = strlen(addr_str);
	int bad = strcspn(addr_str, "1234567890");

	if (bad != len) {
		log_error("bad character in addr at offset %d", bad);
		return -1;
	}

	return 0;
}

#define GWRITTEN_SEP(c) ((c) == '-' || (c) == '.')
#define NUM_ADDR "1234567890-."
#define CMD_ADDR "1234567890*#+-."

int pdu_addr_type_infer(const char *str)
{
	if (*str == '+' && strlen(str + 1) == strspn(str + 1, NUM_ADDR)) {
		log_debug("SMS_ADDR_GLOBAL");
		return SMS_ADDR_GLOBAL;
	} else if (strlen(str) == strspn(str, NUM_ADDR)) {
		log_debug("SMS_ADDR_LOCAL");
		return SMS_ADDR_LOCAL;
	} else if (strlen(str) == strspn(str, CMD_ADDR)) {
		log_debug("SMS_ADDR_CMD");
		return SMS_ADDR_CMD;
	} else {
		log_debug("SMS_ADDR_TEXT");
		return SMS_ADDR_TEXT;
	}
}

static int pdu_addr_clean_copy(struct pdu_addr *addr, const char *src)
{
	char *dest = addr->addr;
	int count = 0;
	int c;

	while((c = *src++)) {
		if(isdigit(c)) {
			if(count >= PDU_ADDR_SIZE - 1) {
				log_error("addr exceeds max length");
				return -1;
			}
			dest[count++] = c;
		} else if(GWRITTEN_SEP(c)) {
			continue;
		} else if(c == '+' && !count) {
			continue;
		} else {
			log_error("addr contains invalid char %c at offset %d", c, count);
			return -1;
		}
	}

	dest[count] = '\0';

	if(!count) {
		log_error("empty addr");
		return -1;
	}

	return count;
}

int pdu_addr_fill(struct pdu_addr *addr, const char *addr_str, int type)
{
	int tmp;

	memset(addr, 0 , sizeof(*addr));

	addr->type = pdu_addr_type_infer(addr_str);

	tmp = pdu_addr_clean_copy(addr, addr_str);
	if (tmp < 0) {
		return tmp;
	}

	log_debug("addr: %s", addr->addr);

	if (type) {
		addr->type = type;
	}

	log_debug("addr-type: 0x%02X", addr->type);

	return tmp;
}

int pdu_user_data_read(int fd, struct pdu_info *pdu)
{
	int err;
	char c;
	int total = 0;
	int len;

	if (pdu->data_coding.general.alphabet == PDU_ALPHABET_DEFAULT) {
		len = PDU_UD_7BIT_MAX;
	} else if (pdu->data_coding.general.alphabet == PDU_ALPHABET_EIGHT) {
		len = PDU_UD_8BIT_MAX;
	} else {
		return -1;
	}

	while (1) {
		err = read(fd, &c, 1);
		if (err < 0) {
			log_error("read failed: %m");
			return err;
		} else if (err > 0) {
			if (total < len) {
				pdu->user_data[total++] = c;
			} else {
				log_debug("read max message length %d", total);
				goto done;
			}
		} else {
			log_debug("read eof at %d", total);
			goto done;
		}
	}

done:

	pdu->user_data[total] = '\0';
	pdu->user_data_len = total;

	debug_buffer("message is: ", pdu->user_data, total);

	return total;
}