/*
 * ctf.js
 *
 * Understand and parse all of the different JSON formats of CTF data and
 * translate that into a series of node-ctype friendly pieces. The reason for
 * the abstraction is to handle different changes in the file format.
 *
 * We have to be careful here that we don't end up using a name that is already
 * a built in type.
 */
var mod_assert = require('assert');
var ASSERT = mod_assert.ok;

var ctf_versions = [ '1.0' ];
var ctf_entries = [ 'integer', 'float', 'typedef', 'struct' ];
var ctf_deftypes = [ 'int8_t', 'uint8_t', 'int16_t', 'uint16_t', 'int32_t',
    'uint32_t', 'float', 'double' ];

function ctfParseInteger(entry, ctype)
{
	var name, sign, len, type;

	name = entry['name'];
	if (!('signed' in entry['integer']))
		throw (new Error('Malformed CTF JSON: integer missing ' +
		    'signed value'));


	if (!('length' in entry['integer']))
		throw (new Error('Malformed CTF JSON: integer missing ' +
		    'length value'));

	sign = entry['integer']['signed'];
	len = entry['integer']['length'];
	type = null;

	if (sign && len == 1)
		type = 'int8_t';
	else if (len == 1)
		type = 'uint8_t';
	else if (sign && len == 2)
		type = 'int16_t';
	else if (len == 2)
		type = 'uint16_t';
	else if (sign && len == 4)
		type = 'int32_t';
	else if (len == 4)
		type = 'uint32_t';
	else if (sign && len == 8)
		type = 'int64_t';
	else if (len == 8)
		type = 'uint64_t';

	if (type === null)
		throw (new Error('Malformed CTF JSON: integer has ' +
		    'unsupported length and sign - ' + len + '/' + sign));

	/*
	 * This means that this is the same as one of our built in types. If
	 * that's the case defining it would be an error. So instead of trying
	 * to typedef it, we'll return here.
	 */
	if (name == type)
		return;

	if (name == 'char') {
		ASSERT(type == 'int8_t');
		return;
	}

	ctype.typedef(name, type);
}

function ctfParseFloat(entry, ctype)
{
	var name, len;

	name = entry['name'];
	if (!('length' in entry['float']))
		throw (new Error('Malformed CTF JSON: float missing ' +
		    'length value'));

	len = entry['float']['length'];
	if (len != 4 && len != 8)
		throw (new Error('Malformed CTF JSON: float has invalid ' +
		    'length value'));

	if (len == 4) {
		if (name == 'float')
			return;
		ctype.typedef(name, 'float');
	} else if (len == 8) {
		if (name == 'double')
			return;
		ctype.typedef(name, 'double');
	}
}

function ctfParseTypedef(entry, ctype)
{
	var name, type, ii;

	name = entry['name'];
	if (typeof (entry['typedef']) != 'string')
		throw (new Error('Malformed CTF JSON: typedef value in not ' +
		    'a string'));

	type = entry['typedef'];

	/*
	 * We need to ensure that we're not looking at type that's one of our
	 * built in types. Traditionally in C a uint32_t would be a typedef to
	 * some kind of integer. However, those size types are built ins.
	 */
	for (ii = 0; ii < ctf_deftypes.length; ii++) {
		if (name == ctf_deftypes[ii])
			return;
	}

	ctype.typedef(name, type);
}

function ctfParseStruct(entry, ctype)
{
	var name, type, ii, val, index, member, push;

	member = [];
	if (!Array.isArray(entry['struct']))
		throw (new Error('Malformed CTF JSON: struct value is not ' +
		    'an array'));

	for (ii = 0; ii < entry['struct'].length; ii++) {
		val = entry['struct'][ii];
		if (!('name' in val))
			throw (new Error('Malformed CTF JSON: struct member ' +
			    'missing name'));

		if (!('type' in val))
			throw (new Error('Malformed CTF JSON: struct member ' +
			    'missing type'));

		if (typeof (val['name']) != 'string')
			throw (new Error('Malformed CTF JSON: struct member ' +
			    'name isn\'t a string'));

		if (typeof (val['type']) != 'string')
			throw (new Error('Malformed CTF JSON: struct member ' +
			    'type isn\'t a string'));

		/*
		 * CTF version 2 specifies array names as <type> [<num>] where
		 * as node-ctype does this as <type>[<num>].
		 */
		name = val['name'];
		type = val['type'];
		index = type.indexOf(' [');
		if (index != -1) {
			type = type.substring(0, index) +
			    type.substring(index + 1, type.length);
		}
		push = {};
		push[name] = { 'type': type };
		member.push(push);
	}

	name = entry['name'];
	ctype.typedef(name, member);
}

function ctfParseEntry(entry, ctype)
{
	var ii, found;

	if (!('name' in entry))
		throw (new Error('Malformed CTF JSON: entry missing "name" ' +
		    'section'));

	for (ii = 0; ii < ctf_entries.length; ii++) {
		if (ctf_entries[ii] in entry)
			found++;
	}

	if (found === 0)
		throw (new Error('Malformed CTF JSON: found no entries'));

	if (found >= 2)
		throw (new Error('Malformed CTF JSON: found more than one ' +
		    'entry'));

	if ('integer' in entry) {
		ctfParseInteger(entry, ctype);
		return;
	}

	if ('float' in entry) {
		ctfParseFloat(entry, ctype);
		return;
	}

	if ('typedef' in entry) {
		ctfParseTypedef(entry, ctype);
		return;
	}

	if ('struct' in entry) {
		ctfParseStruct(entry, ctype);
		return;
	}

	ASSERT(false, 'shouldn\'t reach here');
}

function ctfParseJson(json, ctype)
{
	var version, ii;

	ASSERT(json);
	ASSERT(ctype);
	if (!('metadata' in json))
		throw (new Error('Invalid CTF JSON: missing metadata section'));

	if (!('ctf2json_version' in json['metadata']))
		throw (new Error('Invalid CTF JSON: missing ctf2json_version'));

	version = json['metadata']['ctf2json_version'];
	for (ii = 0; ii < ctf_versions.length; ii++) {
		if (ctf_versions[ii] == version)
			break;
	}

	if (ii == ctf_versions.length)
		throw (new Error('Unsuported ctf2json_version: ' + version));

	if (!('data' in json))
		throw (new Error('Invalid CTF JSON: missing data section'));

	if (!Array.isArray(json['data']))
		throw (new Error('Malformed CTF JSON: data section is not ' +
		    'an array'));

	for (ii = 0; ii < json['data'].length; ii++)
		ctfParseEntry(json['data'][ii], ctype);
}

exports.ctfParseJson = ctfParseJson;
