const { LSB, Image } = require('../fs');
const AES = require('./aes');
const Serializer = require('./serializer');
const Serialize  = require('./serialize');

class ImageDB {

	constructor (path, key, bitrate) {
		this.path = path;
		this.lsb = new LSB(key, bitrate);
		this.key = key;
		this.bitrate = bitrate;
		this.data = {};
	}

	async set (path, val) {
		Serializer.insertToObject(this.data, path, val);
		this.free_size = this.freeSize();
		await this.save();
	}

	get (path) {
		let keys = Serializer.getKeys(path);
		let o = this.data;

		for(let i=0, l=keys.length;i<l;i++) {
			let k = keys[i];
			if(k === "") continue;
			if(o[k] === undefined) return undefined;

			o = o[k];
		}

		return o;
	}

	remove (path) {
		let keys = Serializer.getKeys(path);
		let o = this.data;

		for(let i=0, l=keys.length - 1;i<l;i++) {
			let k = keys[i];
			if(k === "") continue;
			if(o[k] === undefined) return false;

			o = o[k];
		}

		delete o[keys[keys.length - 1]];
		this.free_size = this.freeSize();

		return true;
	}

	async init (def) {
		let img  = await Image.read(this.path);
		let data = img.data;
		let { key, lsb } = this;


		let tag_buf 	= AES.encrypt(new Buffer(this.tag = def.tag), key);
		let next_buffer = Buffer.concat([AES.encrypt(Serialize.Int32(def.next.length), key), AES.encrypt(new Buffer(this.next = def.next), key)]);

		this.available_length = lsb.sizeOf(data) - next_buffer.length - 7;

		let raw_data     = Serializer.stringify("", this.data = def.data);
		let data_to_save = Serializer.shift(raw_data, this.available_length);
		let resting_data = raw_data.slice(data_to_save.length);
		let data_buffer  = Buffer.concat([AES.encrypt(Serialize.Int32(raw_data.length), key), AES.encrypt(new Buffer(data_to_save), key)]);

		let ToWrite = Buffer.concat([tag_buf, next_buffer, data_buffer]);

		lsb.hide(data, ToWrite);

		this.free_size = this.available_length - raw_data.length;

		await Image.write(this.path, img);

		return resting_data;
	}

	async load () {
		let img  = await Image.read(this.path);
		let data = img.data;
		let { key, lsb } = this;
		let buf  = lsb.recover(data, Infinity);

		let length = lsb.sizeOf(buf);
		let tag    = AES.decrypt(buf.slice(0, 3), key).toString();

		let next_end = Serialize.getInt32(AES.decrypt(buf.slice(3, 7), key)) + 7;
		let next = AES.decrypt(buf.slice(7, next_end), key).toString();



		let data_end   = Serialize.getInt32(AES.decrypt(buf.slice(next_end, next_end + 4), key)) + next_end + 4;
		let raw_data   = buf.slice(next_end + 4, data_end);
		let Data       = AES.decrypt(raw_data, key);

		this.data = Serializer.parse(Data);
		this.next = next;

		this.available_length = length - next_end - 4;
		this.tag = tag;

		this.free_size = this.available_length - raw_data.length;
	}

	async save () {
		let img  = await Image.read(this.path);
		let data = img.data;
		let key = this.key;
		let lsb = this.lsb;

		let tag_buf 	= AES.encrypt(new Buffer(this.tag), key);
		let next_buffer = Buffer.concat([AES.encrypt(Serialize.Int32(this.next.length), key), AES.encrypt(new Buffer(this.next), key)]);

		let raw_data     = Serializer.stringify("", this.data);
		let data_to_save = Serializer.shift(raw_data, this.available_length);
		let resting_data = raw_data.slice(data_to_save.length);
		let data_buffer  = Buffer.concat([AES.encrypt(Serialize.Int32(data.length), key), AES.encrypt(new Buffer(data_to_save), key)]);

		let ToWrite = Buffer.concat([tag_buf, next_buffer, data_buffer]);

		lsb.hide(data, ToWrite);

		await Image.write(this.path, img);

		return Serializer.parse(resting_data);
	}

	freeSize () {
		let raw_data = Serializer.stringify("", this.data);
		return this.free_size = this.available_length - raw_data.length;
	}
}

module.exports = ImageDB;
module.exports.load = async (path, key, bitrate, def) => {
	let img = new ImageDB(path, key, bitrate);

	await img.load();

	return img;
}