package com.n4no.webpencoder.webp.io;

import android.util.Log;

import com.n4no.webpencoder.webp.stream.SeekableOutputStream;
import com.n4no.webpencoder.webp.utils.Logs;
import com.n4no.webpencoder.webp.utils.WebpChunk;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.BitSet;

public class WebpContainerWriter {

	private final SeekableOutputStream _outputStream;
	private int _offset;
	private final String TAG = "WebpDemo";

	public WebpContainerWriter(SeekableOutputStream outputStream) {
		_outputStream = outputStream;
	}

	public void writeHeader() throws IOException {
		write(new byte[] { 'R', 'I', 'F', 'F' });
		writeUInt32(0);
		write(new byte[] { 'W', 'E', 'B', 'P' });
	}

	public void close() throws IOException {
		int fileSize = _offset - 8;
		_outputStream.setPosition(4);
		writeUInt32(fileSize);
//		_outputStream.close();
	}

	public void write(WebpChunk chunk) throws IOException {
		Logs.d(this, "Writting type "+chunk.type);
		switch (chunk.type) {
			case VP8L:
				writePayloadChunk(chunk, new byte[] { 'V', 'P', '8', 'L' });
				break;
			case VP8X:
				writeVp8x(chunk);
				break;
			case ANIM:
				writeAnim(chunk);
				break;
			case ANMF:
				writeAnmf(chunk);
				break;
			default:
				throw new IOException("Not supported chunk type.");
		}
	}

	private void writePayloadChunk(WebpChunk chunk, byte[] fourCc) throws IOException {
		write(fourCc, 4);
		writeUInt32(chunk.payload.length);
		write(chunk.payload);
	}

	private void writeVp8x(WebpChunk chunk) throws IOException {
		write(new byte[] { 'V', 'P', '8', 'X' });
		writeUInt32(10);

		BitSet bs = new BitSet(32);
		// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		// |Rsv|I|L|E|X|A|R|                   Reserved                    |
		// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		// |7|6|5|4|3|2|1|0|

		Logs.i(this," writeVp8x() hasAnim "+chunk.hasAnim+", hasAlpha "+chunk.hasAlpha);
		// 0 R (reserved)
		bs.set(1, chunk.hasAnim); // A hasAnim
		bs.set(2, chunk.hasXmp); // X hasXmp
		bs.set(3, chunk.hasExif); // E hasExif
		bs.set(4, true);  // L hasalpha
		bs.set(5, chunk.hasIccp); // I hasIccp
		// 6 Rsv
		// 7 Rsv
		// TODO: canvas is 0 as not its not coming from encoder callee
		// so using height or width +1. +1 to maintain original scale.
		// // till now it worked with few animatedimages
		Logs.i(this,"chunk 🏁🏁🏁🏁::::::::: chunk.canvasHeight "+chunk.canvasHeight);
		Logs.i(this,"chunk 🏁🏁🏁🏁::::::::: chunk.canvasWidth "+chunk.canvasWidth);
		write(bitSetToBytes(bs, 4));
		writeUInt24(chunk.width);
		writeUInt24(chunk.height);
	}

	private void writeAnim(WebpChunk chunk) throws IOException {
		write(new byte[] { 'A', 'N', 'I', 'M' });
		writeUInt32(6);
//		Logs.e(this,"writeAnim "+chunk.background);
		writeUInt32(chunk.background);
		writeUInt16(chunk.loops);
	}

	private void writeAnmf(WebpChunk chunk) throws IOException {
		Logs.i(this,"writeAnmf() ");
		write(new byte[] { 'A', 'N', 'M', 'F' });

		int ALPHSize = 0;
		if(chunk.alphaData != null)
			ALPHSize = 8 + chunk.alphaData.length;
			// FourC + Size Holder + Payload Length

		writeUInt32(chunk.payload.length + 24 + ALPHSize);

		writeUInt24(chunk.x); // 3 bytes (3)
		writeUInt24(chunk.y); // 3 bytes (6)
		writeUInt24(chunk.width); // 3 bytes (9)
		writeUInt24(chunk.height); // 3 bytes (12)
		writeUInt24(chunk.duration); // 3 bytes (15)

		BitSet bs = new BitSet(8);
		bs.set(1, chunk.useAlphaBlending); // blend
		bs.set(0, chunk.disposeToBackgroundColor); // dispose
		write(bitSetToBytes(bs, 1)); // 1 byte (16)


		// Insert ALPH chunk
		if(chunk.alphaData != null){
			writeAlph(chunk.alphaData);
			Logs.v(this," alpha data "+chunk.alphaData.length);
		}else{
			Logs.w(this,"no alpha data");
		}

		if (chunk.isLossless)
			write(new byte[] { 'V', 'P', '8', 'L' }); // 4 bytes (20)
		else
			write(new byte[] { 'V', 'P', '8', ' ' });
		writeUInt32(chunk.payload.length); // 4 bytes (24)
		write(chunk.payload);
		addPaddingZero(chunk.payload);
	}


	public void writeAlph(byte[] alphaData) throws IOException {
		write(new byte[] { 'A', 'L', 'P', 'H' }); // 4
		writeUInt32(alphaData.length); // 4
		write(alphaData); // x
		addPaddingZero(alphaData);
	}

	private void addPaddingZero(byte[] payload) throws IOException {
		if(payload.length%2!=0){
			write(new byte[]{0});
		}
	}


	private void write(byte[] bytes) throws IOException {
		write(bytes, bytes.length);
	}

	private void write(byte[] bytes, int length) throws IOException {
		_outputStream.write(bytes, length);
		_offset += length;
	}

	private void writeUInt(int value, int bytes) throws IOException {
		byte[] b = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(value).array();
		write(b, bytes);
	}

	private void writeUInt16(int value) throws IOException {
		writeUInt(value, 2);
	}
	
	private void writeUInt24(int value) throws IOException {
		writeUInt(value, 3);
	}

	private void writeUInt32(int value) throws IOException {
		writeUInt(value, 4);
	}

	private byte[] bitSetToBytes(BitSet bs, int bytes) {
		byte[] b = new byte[bytes];
		byte[] a = bs.toByteArray();
		System.arraycopy(a, 0, b, 0, a.length);
		return b;
	}



}
