#define _XOPEN_SOURCE
#include <errno.h>
#include <iconv.h>
#include <unistd.h>
#include <nan.h>
#include <node.h>
#include <string.h>

using namespace v8;

static char SECURE_SALT[21] = "$5$";
#define TRIP_MAX 128

void set_salt(const Nan::FunctionCallbackInfo<Value>& info) {
	if (info.Length() != 1) {
		Nan::ThrowTypeError("setSalt takes one argument");
		return;
	}
	Nan::Utf8String saltVal(info[0]);
	if (saltVal.length() != 16) {
		Nan::ThrowTypeError("setSalt takes a string of length 16");
		return;
	}
	char *salt = *saltVal;
	if (!salt) {
		Nan::ThrowTypeError("setSalt read a null string?!");
		return;
	}
	memcpy(SECURE_SALT + 3, salt, 16);
	SECURE_SALT[19] = '$';
	SECURE_SALT[20] = 0;
	info.GetReturnValue().Set(true);
}

static void fix_char(char &c) {
	static const char *from = ":;<=>?@[\\]^_`", *to = "ABCDEFGabcdef";
	const char *p;
	if (c < '.' || c > 'z')
		c = '.';
	else if ((p = strchr(from, c)))
		c = to[p - from];
}

static void hash_trip(char *key, size_t len, char *dest) {
	char *digest, salt[3] = "..";
	if (len == 1)
		salt[0] = 'H';
	else if (len == 2) {
		salt[0] = key[1];
		salt[1] = 'H';
	}
	else if (len)
		strncpy(salt, key + 1, 2);
	fix_char(salt[0]);
	fix_char(salt[1]);
	digest = crypt(key, salt);
	if (!digest)
		return;
	len = strlen(digest);
	if (len < 11)
		return;
	digest += len - 11;
	digest[0] = '!';
	strncpy(dest, digest, 12);
}

static void hash_secure(char *key, size_t len, char *dest) {
	size_t i;
	char *digest;
	if (len > TRIP_MAX) {
		len = TRIP_MAX;
		key[TRIP_MAX] = 0;
	}
	for (i = 0; i < len; i++)
		fix_char(key[i]);
	digest = crypt(key, SECURE_SALT);
	if (!digest)
		return;
	len = strlen(digest);
	if (len < 12)
		return;
	digest += len - 12;
	digest[0] = digest[1] = '!';
	strncpy(dest, digest, 13);
}

static iconv_t conv_desc;

static int setup_conv() {
	conv_desc = iconv_open("SHIFT_JIS", "UTF-8");
	if (conv_desc == (iconv_t) -1) {
		fprintf(stderr, "Can't convert to SHIFT_JIS.\n");
		return 0;
	}
	return 1;
}

typedef void (*trip_f)(char *, size_t, char *);

static void with_SJIS(Nan::Utf8String &trip, trip_f func, char *ret) {
	char *src = *trip;
	if (!src)
		return;
	size_t src_left = trip.length(), dest_left = TRIP_MAX;
	if (!src_left)
		return;
	if (src_left > TRIP_MAX / 2)
		src_left = TRIP_MAX / 2;
	char sjis[TRIP_MAX+1];
	char *dest = sjis;
	size_t result = iconv(conv_desc, &src, &src_left, &dest, &dest_left);
	if (result == (size_t) -1 && errno != EILSEQ && errno != EINVAL) {
		perror("iconv");
		return;
	}
	ssize_t len = TRIP_MAX - dest_left;
	if (len > 0) {
		sjis[len] = 0;
		func(sjis, len, ret);
	}
}

void hash(const Nan::FunctionCallbackInfo<Value>& info) {
	if (info.Length() != 2) {
		Nan::ThrowTypeError("hash takes 2 arguments");
		return;
	}

	Nan::Utf8String trip(info[0]), secure(info[1]);
	char digest[24];
	digest[0] = 0;
	with_SJIS(trip, &hash_trip, digest);
	with_SJIS(secure, &hash_secure, digest + strlen(digest));

	Local<String> digest_str = Nan::New(digest).ToLocalChecked();
	info.GetReturnValue().Set(digest_str);
}

void init(Local<Object> exports) {
	if (!setup_conv())
		return;
	exports->Set(Nan::New("setSalt").ToLocalChecked(),
			Nan::New<FunctionTemplate>(set_salt)->GetFunction());
	exports->Set(Nan::New("hash").ToLocalChecked(),
		Nan::New<FunctionTemplate>(hash)->GetFunction());
}

NODE_MODULE(tripcode, init)
