/*
 * Copyright (C) 2014 Andreas Steffen
 * HSR Hochschule fuer Technik Rapperswil
 *
 * 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.  See <http://www.fsf.org/copyleft/gpl.txt>.
 *
 * 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.
 */

#include "bliss_signature.h"
#include "bliss_bitpacker.h"
#include "bliss_huffman_coder.h"


typedef struct private_bliss_signature_t private_bliss_signature_t;

/**
 * Private data of a bliss_signature_t object.
 */
struct private_bliss_signature_t {
	/**
	 * Public interface for this signer.
	 */
	bliss_signature_t public;

	/**
	 * BLISS signature parameter set
	 */
	bliss_param_set_t *set;

	/**
	 * BLISS signature vector z1 of size n
	 */
	int32_t *z1;

	/**
	 * BLISS signature vector z2d of size n
	 */
	int16_t *z2d;

	/**
	 * Indices of sparse BLISS challenge vector c of size kappa
	 */
	uint16_t *c_indices;

};

METHOD(bliss_signature_t, get_encoding, chunk_t,
	private_bliss_signature_t *this)
{
	bliss_bitpacker_t *packer;
	bliss_huffman_coder_t *coder;
	bliss_huffman_code_t *code;
	int32_t z1;
	uint32_t z1_sign;
	uint16_t z2d_bits;
	chunk_t encoding = chunk_empty;
	int i;

	z2d_bits = this->set->z1_bits - this->set->d;

	/* Get Huffman code for this BLISS parameter set */
	code = bliss_huffman_code_get_by_id(this->set->id);
	if (!code)
	{
		DBG1(DBG_LIB, "no Huffman code found for parameter set %N",
			 bliss_param_set_id_names, this->set->id);
		return chunk_empty;
	}

	packer = bliss_bitpacker_create(this->set->n * this->set->z1_bits +
									this->set->n * z2d_bits +
									this->set->kappa * this->set->n_bits);
	coder = bliss_huffman_coder_create(code, packer);

	for (i = 0; i < this->set->n; i++)
	{
		/* determine and remove the sign of z1[i]*/
		z1_sign = this->z1[i] < 0;
		z1 = z1_sign ? -this->z1[i] : this->z1[i];

		if (!packer->write_bits(packer, z1_sign, 1) ||
			!packer->write_bits(packer, z1 & 0xff, 8) ||
			!coder->encode(coder, z1 >> 8, this->z2d[i]))
		{
			goto end;
		}
	}
	for (i = 0; i < this->set->kappa; i++)
	{
		if (!packer->write_bits(packer, this->c_indices[i], this->set->n_bits))
		{
			goto end;
		}
	}
	encoding = packer->extract_buf(packer);

	DBG2(DBG_LIB, "efficiency of Huffman coder is %6.4f bits/tuple (%u bits)",
				   coder->get_bits(coder)/(double)(this->set->n), 
				   coder->get_bits(coder));
	DBG2(DBG_LIB, "generated BLISS signature (%u bits encoded in %u bytes)",
				   packer->get_bits(packer), encoding.len);

	end:
	coder->destroy(coder);
	packer->destroy(packer);
	return encoding;
}

METHOD(bliss_signature_t, get_parameters, void,
	private_bliss_signature_t *this, int32_t **z1, int16_t **z2d,
	uint16_t **c_indices)
{
	*z1 = this->z1;
	*z2d = this->z2d;
	*c_indices = this->c_indices;
}

METHOD(bliss_signature_t, destroy, void,
	private_bliss_signature_t *this)
{
	free(this->z1);
	free(this->z2d);
	free(this->c_indices);
	free(this);
}

/**
 * See header.
 */
bliss_signature_t *bliss_signature_create(bliss_param_set_t *set)
{
	private_bliss_signature_t *this;

	INIT(this,
		.public = {
			.get_encoding = _get_encoding,
			.get_parameters = _get_parameters,
			.destroy = _destroy,
		},
		.set = set,
		.z1  = malloc(set->n * sizeof(int32_t)),
		.z2d = malloc(set->n * sizeof(int16_t)),
		.c_indices = malloc(set->n * sizeof(uint16_t)),
	);

	return &this->public;
}

/**
 * See header.
 */
bliss_signature_t *bliss_signature_create_from_data(bliss_param_set_t *set,
													chunk_t encoding)
{
	private_bliss_signature_t *this;
	bliss_bitpacker_t *packer;
	bliss_huffman_coder_t *coder;
	bliss_huffman_code_t *code;
	uint32_t z1_sign, z1_low, value;
	int32_t z1;
	int16_t z2;
	int i;

	/* Get Huffman code for this BLISS parameter set */
	code = bliss_huffman_code_get_by_id(set->id);
	if (!code)
	{
		DBG1(DBG_LIB, "no Huffman code found for parameter set %N",
			 bliss_param_set_id_names, set->id);
		return NULL;
	}

	INIT(this,
		.public = {
			.get_encoding = _get_encoding,
			.get_parameters = _get_parameters,
			.destroy = _destroy,
		},
		.set = set,
		.z1  = malloc(set->n * sizeof(int32_t)),
		.z2d = malloc(set->n * sizeof(int16_t)),
		.c_indices = malloc(set->n * sizeof(uint16_t)),
	);

	packer = bliss_bitpacker_create_from_data(encoding);
	coder = bliss_huffman_coder_create(code, packer);

	for (i = 0; i < set->n; i++)
	{
		if (!packer->read_bits(packer, &z1_sign, 1) ||
			!packer->read_bits(packer, &z1_low, 8) ||
			!coder->decode(coder, &z1, &z2))
		{
			coder->destroy(coder);
			packer->destroy(packer);
			destroy(this);
			return NULL;
		}
		z1 = (z1 << 8) + z1_low;
		this->z1[i] = z1_sign ? -z1 : z1;
		this->z2d[i] = z2;
	}
	coder->destroy(coder);

	for (i = 0; i < set->kappa; i++)
	{
		if (!packer->read_bits(packer, &value, set->n_bits))
		{
			packer->destroy(packer);
			destroy(this);
			return NULL;
		}
		this->c_indices[i] = value;
	}
	packer->destroy(packer);

	return &this->public;
}
