<!doctype html>

<html lang="cs">
<head>
<meta charset="utf-8">
<title>ces-pokus-mini</title>
<body>


<form>
<!--Input ze souboru <input type="file" onchange="CES.func.previewFile();">
<img src="" height="200" alt="Nahled obrazku, kdyz je soubor obrazek...">
<br>-->
Input<br> <textarea id="inp" cols="80" rows="4"></textarea>
<br><input type="button" value="compress" onclick="CES.func.compressEncode(this.form.inp);">
<br>Output<br> <textarea id="out" cols="80" rows="4"></textarea>
<div id="stat"></div>
</form>


<script>
// ces.pas - myslim si, ze to deli byte po 2 bitech, spocita pocet opakovani a podle toho vytvori mapy true/false. Ktere navic zasifruje huffmanem.
// 2022 - mysleni skodi :) nepocita opakovani, mapa je vlastne huffman

// jeste jsem to moc neupravoval!!! 13.10.2020
// 14.10. nedokonceno


var CES = {};
CES.func = {};
CES.vars = {};
CES.vars.el = {};
CES.vars.tab = {};
CES.vars.map_list = [];
CES.vars.huff = '';
CES.vars.out_info = [];

CES.func.log = function (str)
	{
	CES.vars.el['stat'].innerHTML += '<br>'+str;
	}

CES.func.tablesCreate = function ()
	{
	var i, tab;
	tab = {};
	tab.ord  = {};
	tab.chr  = [];
	tab.freq = {};
	for (i=0;i<256;i++)
		{
		chr = String.fromCharCode(i);
		tab.ord[chr] = i;	// byte = ord(char)
		tab.chr[i]   = chr;	// char = chr(byte)
		tab.freq[chr] = [0, 0, 0, 0];
		tab.freq[chr][(i)    & 0x03] += 1;	// frequency pair of bits in 8-bit char
		tab.freq[chr][(i>>2) & 0x03] += 1;
		tab.freq[chr][(i>>4) & 0x03] += 1;
		tab.freq[chr][(i>>6) & 0x03] += 1;
		}
	return tab;
	}

CES.func.ord = function (chr)
	{
	return CES.vars.tab.ord[chr];
	}

CES.func.chr = function (ord)
	{
	return CES.vars.tab.chr[ord];
	}

CES.func.computeFrequency = function (data)
	{
	var i, i_end, freq, freq_byte, chr;
	freq  = [0, 0, 0, 0];
	i_end = data.length;
	for (i=0;i<i_end;i++)
		{
		chr = data[i]; // chr = data.charAt(i);
		freq_byte = CES.vars.tab.freq[chr];
		freq[0] += freq_byte[0];
		freq[1] += freq_byte[1];
		freq[2] += freq_byte[2];
		freq[3] += freq_byte[3];
		}
	return freq;
	}

CES.func.computeOrder = function (freq)
	{
	var order = [];
	for (i=0; i<4; i++)
		{
		order[i] = {'order':i, 'freq': freq[i]};
		}
	order.sort(function(b,a){return a.freq-b.freq;});	// desc
	for (i=0; i<4; i++)
		{
		order[i] = order[i].order;
		}
	return order;
	}
// compress 3x


CES.func.maplistReset = function()
	{
	var list;
	list = [];
	list[0] = {map: '', size: 0, bites: 7, byte: 0, huff_failed: false};
	list[1] = {map: '', size: 0, bites: 7, byte: 0, huff_failed: false};
	list[2] = {map: '', size: 0, bites: 7, byte: 0, huff_failed: false};
	return list;
	}

CES.func.maplistExport = function()
	{
	var list = CES.vars.map_list;
	return list[0].map + list[1].map + list[2].map;
	}

CES.func.maplistAddReset = function(m)
	{
	CES.vars.map_list[m].byte  = 0;
	CES.vars.map_list[m].bites = 7;
	}

CES.func.maplistAddChr = function(m, byte)
	{
	CES.vars.map_list[m].map += CES.func.chr(byte);
	CES.vars.map_list[m].size++;
	CES.func.maplistAddReset(m);
	}

CES.func.maplistAddBite = function(m, bite)
	{
	var fn = CES.func;
	var list;
	list = CES.vars.map_list[m];
	list.byte = list.byte | (bite << list.bites);
	if (list.bites>0)
		{
		list.bites--;
		return;
		}
	CES.func.maplistAddChr(m, list.byte);
	}

CES.func.maplistAddPair = function(value, order_list)
	{
	var fn = CES.func;
	if (value==order_list[0])
		{
		fn.maplistAddBite(0, 1);
		return;
		}
	else { if (value==order_list[1])
		{
		fn.maplistAddBite(0, 0);
		fn.maplistAddBite(1, 1);
		return;
		}
	else { if (value==order_list[2])
		{
		fn.maplistAddBite(0, 0);
		fn.maplistAddBite(1, 0);
		fn.maplistAddBite(2, 1);
		return;
		}}}
	fn.maplistAddBite(0, 0);
	fn.maplistAddBite(1, 0);
	fn.maplistAddBite(2, 0);
	}

CES.func.huffReset = function()
	{
	return {map: '', size: 0, bites: 7, byte: 0, huff_failed: false};
	}

CES.func.huffAddReset = function(m)
	{
	CES.vars.huff.byte  = 0;
	CES.vars.huff.bites = 7;
	}

CES.func.huffAddChr = function(byte)
	{
	CES.vars.huff.map += CES.func.chr(byte);
	CES.vars.huff.size++;
	CES.func.huffAddReset();
	}

CES.func.huffAddBite = function(bite)
	{
	var fn = CES.func;
	var list;
	list = CES.vars.huff;
	list.byte = list.byte | (bite << list.bites);
	if (list.bites>0)
		{
		list.bites--;
		return;
		}
	CES.func.huffAddChr(list.byte);
	}

CES.func.huffEncodePair = function (value)
	{
	var fn = CES.func;
	if (value==0)
		{
		fn.huffAddBite(1);
		return;
		}
	else { if (value==1)
		{
		fn.huffAddBite(0);
		fn.huffAddBite(1);
		return;
		}
	else { if (value==2)
		{
		fn.huffAddBite(0);
		fn.huffAddBite(0);
		fn.huffAddBite(1);
		return;
		}}}
	fn.huffAddBite(0);
	fn.huffAddBite(0);
	fn.huffAddBite(0);
	}

CES.func.huffEncode = function (input)
	{
	var fn = CES.func;
	var freq, order, map,i,i_end,j,end_j, m,n;
//	freq  = CESX.func.frequencyCount(input);
//	order = [0, 1, 2, 3];
//	order.sort(function(b,a){return freq[a]-freq[b];});	//desc
	order = [0, 1, 2, 3];
	CES.vars.huff = fn.huffReset();
	list = CES.vars.huff;
	i_end = input.length;
	for (i=0;i<i_end;i++)
		{
		m = fn.ord(input[i]);
		n = m & 0x03; m >>= 2; fn.huffEncodePair(order[n]);
		n = m & 0x03; m >>= 2; fn.huffEncodePair(order[n]);
		n = m & 0x03; m >>= 2; fn.huffEncodePair(order[n]);
		n = m & 0x03;          fn.huffEncodePair(order[n]);
		}
	if (CES.vars.huff.bites!==7)
		{
		fn.huffAddChr(list.byte);
		}
	return list.map;
	}

CES.func.compressEncode = function (inp, base64)
	{
	var fn = CES.func;
	var map, i, i_end, m, m_end, value, n, new_data, value, arr, freq, out;
	var map_list;

	if (base64)
		{
		inp = fn.base64Decode(inp);
		}

	CES.vars.map_list = fn.maplistReset();
	map_list = CES.vars.map_list;
	freq  = fn.computeFrequency(inp);
	order = fn.computeOrder(freq);
fn.log('inp freq + order '+ JSON.stringify(freq)+' '+ JSON.stringify(order));
	// mapping (save single bit of huffman code to differend array)
	i_end = inp.length;
	for (i=0; i<i_end; i++)
		{
		value = fn.ord(inp[i]);
		n = value & 0x03; value >>= 2; fn.maplistAddPair(n, order);
		n = value & 0x03; value >>= 2; fn.maplistAddPair(n, order);
		n = value & 0x03; value >>= 2; fn.maplistAddPair(n, order);
		n = value & 0x03;              fn.maplistAddPair(n, order);
		}
	// add last byte
	m_end = map_list.length;
	for (m=0; m<m_end; m++)
		{
		if (map_list[m].bites!==7)
			{
			fn.maplistAddChr(m, map_list[m].byte);
			}
		}
console.log(fn.computeFrequency(map_list[0].map))
console.log(fn.computeFrequency(map_list[1].map))
console.log(fn.computeFrequency(map_list[2].map))

// nejsem si zatim jisty, zda to funguje spravne
console.log(fn.base2Encode(inp, 40, 60))
console.log(fn.base2Encode(map_list[0].map, 40, 60))
console.log(fn.base2Encode(map_list[1].map, 40, 60))
console.log(fn.base2Encode(map_list[2].map, 40, 60))
console.log(map_list[0].map.length, map_list[1].map.length, map_list[2].map.length)

	map_list[0].map = fn.huffEncode(map_list[0].map);
	map_list[1].map = fn.huffEncode(map_list[1].map);
console.log(map_list[0].map.length, map_list[1].map.length, map_list[2].map.length)

	out = fn.maplistExport();
fn.log('inp bytes '+ inp.length);
fn.log('out bytes '+ out.length);
	if (base64)
		{
//		out = fn.base64Encode(out);
		}
        }

CES.func.base64Encode = function (data)
	{
//	return window.btoa(data);	// mam pocit, ze s js + UTF-8 + diakritika si btoa neporadila, ale na soubory to snad bude ok
	var CHARS, out = "", i = 0, len = data.length, c1, c2, c3;
	var CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	while (i < len)
		{
		c1 = data.charCodeAt(i++) & 0xff;
		if (i == len)
			{
			out += CHARS.charAt(c1 >> 2);
                	out += CHARS.charAt((c1 & 0x3) << 4);
                	out += "==";
                	break;
			}
		c2 = data.charCodeAt(i++);
		if (i == len)
			{
			out += CHARS.charAt(c1 >> 2);
			out += CHARS.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));
			out += CHARS.charAt((c2 & 0xF) << 2);
			out += "=";
			break;
			}
		c3 = data.charCodeAt(i++);
		out += CHARS.charAt(c1 >> 2);
		out += CHARS.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
		out += CHARS.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6));
		out += CHARS.charAt(c3 & 0x3F);
		}
        return out;
	}

CES.func.base64Decode = function (data)
	{
	return window.atob(data);
	}

CES.func.base2Encode = function (data, start, end)	// to je sikovne, kdyz potrebujes videt, jak neco vypada binarne
	{
	var i, i_start, i_end, out;
	out   = '';
	i_end = data.length;
	i_end = end>0 && end<i_end ? end : i_end;
	i_start = start>=0 && start<i_end ? start : 0;
	for (i = i_start; i < i_end; i++)
		{
		out += ('0000000' + data[i].charCodeAt(0).toString(2)).slice(-8) + " ";
		}
	return out;
	}

CES.func.construct = function ()
	{
	CES.vars.tab = CES.func.tablesCreate();
	CES.vars.el = {
		'inp' : document.getElementById('inp'),
		'out' : document.getElementById('out'),
		'stat': document.getElementById('stat')
		};
	CES.vars.el['inp'].innerHTML = '[encoded data, 6012 characters]'; // nejaky soubor, 6012 encoded, 4508 rar nejakeho pseudorandom
//'
	CES.func.compressEncode(CES.vars.el['inp'].innerHTML, true);
	}
CES.func.construct();












</script>

