/*jslint browser: true, white: false */

function dbg(msg)
{
	if (console && console.log) {
		console.log(msg);
	}
}

function ASSERT(condicao, descricao)
{
	if (! condicao) {
		alert("Problema no aplicativo:\r\n\r\n" + 
			  descricao +
			  "\r\n\r\nPor favor avise o mantenedor do site.");
		console.trace();
		throw("Assert failed");
	}
	return condicao;
}

function AUDIT(condicao, descricao)
{
	if (! condicao) {
		alert("Erro de processamento:\r\n\r\n" + 
			  descricao +
			  "\r\n\r\nPor favor avise o mantenedor do site, se possível\r\n" +
			  "mandando um exemplo dos lançamentos para teste.");
	}
	return condicao;
}

function tzoffset(d)
{
	// returns the time zone offset, expressed as "hours *behind* UTC".
	// that would be 180 minutes for Brazil (-0300) and -60 minutes for Germany (+0100)
	return d.getTimezoneOffset() * 60000;
}

function zeropad(s, n)
{
	s = "" + s;
	while (s.length < n) {
		s = "0" + s;
	}
	return s;
}

function dtoD(data)
{
	ASSERT(typeof data == "number", "parametro de dtoD");
	var a = new Date();
	a.setTime(data);
	return a;
}

function jDtod(data)
{
	return data.getTime();
}

function ymdtoD(y, m, d)
{
	// at noon, to prevent a daylight saving timezone to change the date.
	return new Date(y, m, d, 12, 0, 0);
}

function ymdtod(y, m, d)
{
	return jDtod(ymdtoD(y, m, d));
}

function agora()
{
	var a = new Date();
	return ymdtod(a.getFullYear(), a.getMonth(), a.getDate());
}

function ctod(s)
{
	if (! s) {
		return null;
	}

	var tks = s.split('/');
	if (tks.length != 3) {
		return null;
	}

	for(var i = 0; i < 3; ++i) {
		tks[i] = parseInt(tks[i], 10);
		if (isNaN(tks[i])) {
			return null;
		}
	}

	// pt-br
	var tmp = tks[0];
	tks[0] = tks[2];
	tks[2] = tmp;

	if (tks[0] < 100) {
		tks[0] += 2000;
	}

	if (tks[0] < 0 || tks[0] > 2099 || tks[1] < 1 || tks[1] > 12 || tks[2] < 1 || tks[2] > 31) {
		return null;
	}

	tks[1]--;

	var y = ctod.normal_year;
	if (((tks[0] % 4) === 0) && (((tks[0] % 100) !== 0) || ((tks[0] % 400) === 0))) {
		y = ctod.leap_year;
	}

	if (tks[2] > y[tks[1]]) {
		return null;
	}

	return ymdtod(tks[0], tks[1], tks[2]);
}

ctod.normal_year = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
ctod.leap_year = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

function dtoc(t)
{
	ASSERT(typeof t == "number", "parametro de dtoc " + typeof t);
	// t comes from cookie memory; since it came straight from getTime(),
	// setTime() will remove the timezone offset added by getTime(),
	// and we get the original date anyway.
	var exp = dtoD(t);
	// return ""+zeropad(exp.getFullYear().toFixed(0), 4)+"/"+zeropad((exp.getMonth()+1).toFixed(0), 2)+"/"+zeropad(exp.getDate().toFixed(0), 2);
	return ""+zeropad(exp.getDate().toFixed(0), 2)+"/"+zeropad((exp.getMonth()+1).toFixed(0), 2)+"/"+zeropad(exp.getFullYear().toFixed(0), 4);
}

function sgn(n)
{
	return (n > 0 ? 1 : (n < 0 ? -1 : 0));
}

function _mesmo_sinal(a, b)
{
	return sgn(a) == sgn(b);
}

function mesmo_sinal(a, b) {
	return (a === 0 || b === 0 || _mesmo_sinal(a, b));
}

function sinal_oposto(a, b) {
	return ! mesmo_sinal(a, b);
}

function a_procura(a, criteria, arg_adicional)
{
	for(var i = 0; i < a.length; ++i) {
		if (criteria(a[i], arg_adicional)) {
			return i;
		}
	}
	return -1;
}

function a_existe(a, criteria, arg_adicional)
{
	return (a_procura(a, criteria, arg_adicional) != -1);
}

function a_remove(a, i)
{
	a.splice(i, 1);
}

function htoc(hora)
{
	return zeropad(Math.floor(hora / 100).toFixed(0), 2) + ":" + 
		   zeropad((hora % 100).toFixed(0), 2);

}

function ctoh(chora)
{
	var hora = parseInt(chora.replace(/[^0-9]/g, ''), 10);

	if (isNaN(hora)) {
		hora = 0;
	} else {
		hora = Math.round(hora);
		if (hora <= 0 || hora > 2359 || (hora % 100) > 59) {
			hora = 0;
		}
	}
	return hora;
}

function max_abs(a, b)
{
	return (Math.abs(a) > Math.abs(b)) ? a : b;
}

function min_abs(a, b)
{
	return (Math.abs(a) < Math.abs(b)) ? a : b;
}

function ultimo_dia_mes(data)
{
	AUDIT(typeof data == "number", "parametro de ultimo_dia_mes");
	var a = dtoD(data);
	return ymdtod(a.getFullYear(), a.getMonth() + 1, 0);
}

function ultimo_dia_proximo_mes(data)
{
	AUDIT(typeof data == "number", "parametro de ultimo_dia_proximo_mes");
	var a = dtoD(data);
	return ymdtod(a.getFullYear(), a.getMonth() + 2, 0);
}

function ultimo_dia_util_proximo_mes(data)
{
	AUDIT(typeof data == "number", "parametro de ultimo_dia_util_proximo_mes");
	var a = dtoD(data);
	a = ymdtoD(a.getFullYear(), a.getMonth() + 2, 0);

	while (a.getDay() < 1 || a.getDay() > 5) {
		a = ymdtoD(a.getFullYear(), a.getMonth(), a.getDate() - 1);
	}

	return jDtod(a);
}

/* retorna dia da semana normalizado com 0 = segunda-feira,
   em vez de 0 = domingo como o Javascript retornaria */

function data_dow(data)
{
	ASSERT(typeof data == "number", "parametro de data_dow");
	return (dtoD(data).getDay() + 6) % 7;
}

function data_ano(data)
{
	ASSERT(typeof data == "number", "parametro de data_ano");
	return dtoD(data).getFullYear();
}

function data_mes(data)
{
	ASSERT(typeof data == "number", "parametro de data_mes");
	return dtoD(data).getMonth();
}

function tipo_papel(papel)
{
	var caracteres = 0;
	var digitos = 0;
	var resto = 0;
	var valor = 0;
	var tipo = tipo_papel.DESCONHECIDO;
	var c, i;

	for (i = 0; i < papel.length; ++i) {
		c = papel.charAt(i);
		if (c >= 'A' && c <= 'Z') {
			++caracteres;
		} else {
			break;
		}
	}

	for (i = caracteres; i < papel.length; ++i) {
		c = papel.charAt(i);
		if (c >= '0' && c <= '9') {
			++digitos;
		} else {
			break;
		}
	}

	resto = papel.length - caracteres - digitos;

	if (digitos === 0) {
		return tipo;
	}

	valor = parseInt(papel.substr(caracteres, digitos), 10);

	if (caracteres === 5 && digitos == 2 && resto === 0 && papel.charAt(4) <= 'X') {
		tipo = tipo_papel.OPCAO;
	} else if (caracteres == 4 && digitos >= 1 && digitos <= 2 && resto === 0) {
		tipo = tipo_papel.ACAO;
	} else if (caracteres == 3 && digitos >= 1 && digitos <= 2 && resto === 0) {
		tipo = tipo_papel.FUTURO;
	}

	return tipo;
}

tipo_papel.DESCONHECIDO = 0;
tipo_papel.ACAO = 1;
tipo_papel.OPCAO = 5;
tipo_papel.FUTURO = 6;

/* Retorna a data do vencimento de uma opção (3a segunda do mês)
   mais 7 dias, depois do que é certo que a opção já não existe */

function vencimento_opcao(papel, data_compra)
{
	AUDIT(typeof data_compra == "number", "parametro data_compra de vencimento_opcao");
	AUDIT(papel.length > 5, "parametro papel de vencimento_opcao");
	var ano = data_ano(data_compra);
	var mes = (papel.charCodeAt(4) - 'A'.charCodeAt(0)) % 12;
	AUDIT((mes >= 0 && mes < 12), "vencimento_opcao: mes opcao");
	if (data_mes(data_compra) > mes) {
		++ano;
	}
	// 22 é o menor dia que pode cair a 4a segunda do mês!
	var tentative = ymdtod(ano, mes, 22);
	var venc7 = ymdtod(ano, mes, 22 + ((7 - data_dow(tentative)) % 7));
	return venc7;
}

function formatFinancial(val, decs)
{
	if (typeof(decs) == "undefined") {
		decs = 2;
	}

	var cval = "" + Math.round(Math.abs(val));

	while (cval.length <= decs) {
		cval = "0" + cval;
	}

	var cents = cval.substr(cval.length - decs, decs);
	var intg = cval.substr(0, cval.length - decs);
	var sign = val < 0 ? "-" : "";
	var intgsep = "";
	
	while (intg.length > 0) {
		if (intg.length > 3) {
			var blk = intg.substr(intg.length - 3, 3);
			intg = intg.substr(0, intg.length - 3);
			intgsep = "." + blk + intgsep;
		} else {
			intgsep = intg + intgsep;
			intg = "";
		}
	}
	
	return sign + intgsep + (decs > 0 ? ("," + cents) : "");
}

function addMinutes(t, d)
{
	var m = t % 100;
	var h = (t - m) / 100;
	m += d;
	while (m >= 60) {
		++h;
		m -= 60;
	}
	while (m < 0) {
		--h;
		m += 60;
	}
	while (h < 0) {
		h += 24;
	}
	h = h % 24;
	return h * 100 + m;
}
/*jslint browser: true, white: false */
/*global tela, main*/

ANIM_ITEM = 900;

function Lancto() {
	this.versao = Lancto._VERSAO_REGISTRO;
	this.ok = 0;
	this.htmlnode = null;
	this.err = "Lançamento não verificado";
}

Lancto._VERSAO_REGISTRO = 14;

var Lancto_class = Lancto.prototype;

Lancto_class.verif_taxa = function (key, name)
{
	key = "taxa_" + key;

	if (typeof this[key]!= "number") {
		this[key] = this[key].replace(/[^0-9]/g, '');
		this[key] = parseInt("0"+this[key], 10);
	}

	if (isNaN(this[key])) {
		this[key] = 0;
	}

	this[key] = Math.round(this[key]);

	if (this[key] < 0) {
		this.err = name + " não pode ser menor que zero";
		return 0;
	}
};

Lancto_class.verificar = function (default_id)
{
	if ((typeof this.id != "number") || isNaN(this.id)) {
		this.id = default_id;
	}

	if (typeof this.qtde != "number") {
		this.qtde = this.qtde.replace(/[^\-0-9]/g, '');
		this.qtde = parseInt(this.qtde, 10);
	}

	if (isNaN(this.qtde)) {
		this.qtde = 0;
	}

	this.qtde = Math.round(this.qtde, 0);

	if (this.qtde === 0) {
		this.err = "Quantidade não pode ser igual a zero";
		return 0;
	}

	this.verif_taxa('cblc_liq', 'Taxa de liquidação CBLC');
	this.verif_taxa('cblc_reg', 'Taxa de registro CBLC');
	this.verif_taxa('corretagem', 'Corretagem');
	this.verif_taxa('bovespa', 'Taxas Bovespa');
	this.verif_taxa('iss', 'ISS');
	this.verif_taxa('irrf', 'IRRF');
	this.verif_taxa('outras', 'Outras despesas');

	this.taxas = this.taxa_cblc_liq + this.taxa_cblc_reg + this.taxa_corretagem +
			this.taxa_bovespa + this.taxa_iss + this.taxa_outras;
	// IRRF nao eh contado como despesa de aquisicao

	if (typeof this.vlunit != "number") {
		this.vlunit = this.vlunit.replace(/[^0-9]/g, '');
		this.vlunit = parseInt("0" + this.vlunit, 10);
	}

	if (isNaN(this.vlunit)) {
		this.vlunit = 0;
	}

	this.vlunit = Math.round(this.vlunit);

	if (this.vlunit < 0) {
		this.err = "Valor unitário não pode ser menor que zero";
		return 0;
	}

	if (typeof this.vltotal != "number") {
		this.vltotal = this.vltotal.replace(/[^0-9]/g, '');
		this.vltotal = parseInt("0" + this.vltotal, 10);
	}

	if (isNaN(this.vltotal)) {
		this.vltotal = 0;
	}

	this.vltotal = Math.round(this.vltotal);

	if (this.vltotal < 0) {
		this.err = "Valor total deve ser positivo (valor absoluto)";
		return 0;
	}

	if (this.vlunit > 0 && this.vltotal > 0) {
		var vteste = this.vlunit * this.qtde;
		this.valor_total = this.vltotal * sgn(this.qtde);
		if (vteste != this.valor_total) {
			this.err = "Valor total não bate com valor unitário; corrija ou preencha apenas um deles";
			return 0;
		}
		this.valor_unitario = this.vlunit;
	} else if (this.vlunit > 0) {
		this.valor_unitario = this.vlunit;
		this.valor_total = this.valor_unitario * this.qtde;
	} else {
		this.valor_total = this.vltotal * sgn(this.qtde);
		this.valor_unitario = this.valor_total / this.qtde;
	}

	this.papel = jQuery.trim(this.papel).toUpperCase();
	this.papel = this.papel.replace(/[^A-Z0-9]/g, '');

	if (this.papel.length < 1) {
		this.err = "Preencha o código do papel";
		return 0;
	}

	if (typeof this.data != "number") {
		this.data = ctod(this.data);
	}

	if (! this.data || isNaN(this.data)) {
		this.err = "Data inválida";
		return 0;
	}

	if (typeof this.hora != "number") {
		this.hora = ctoh(this.hora);
	}

	if (! this.hora || isNaN(this.hora)) {
		this.err = "Hora inválida";
		return 0;
	}

	this.ok = 1;
	this.err = null;

	return this.ok;
};

Lancto_class.remover_provisorios = function () {
	delete this.ok;
	delete this.err;
};

Lancto_class.copia_para_exportacao = function () {
	var copia = jQuery.extend({}, this);
	copia.remover_provisorios();
	delete copia.htmlnode;
	delete copia.valor_unitario;
	delete copia.valor_total;
	for (item in copia) {
		if (typeof copia[item] === "function" || typeof copia[item] === "object") {
			delete copia[item];
		}
	}
	return copia;
};

Lancto.criar = function (default_id, id, data, hora, papel, qtde, vlunit, vltotal,
			taxa_cblc_liq, taxa_cblc_reg, taxa_bovespa, taxa_corretagem,
			taxa_iss, taxa_irrf, taxa_outras)
{
	l = new Lancto();

	l.id = id;
	l.data = data;
	l.hora = hora;
	l.papel = papel;
	l.qtde = qtde;
	l.vlunit = vlunit;
	l.vltotal = vltotal;

	l.taxa_cblc_liq = taxa_cblc_liq;
	l.taxa_cblc_reg = taxa_cblc_reg;
	l.taxa_bovespa = taxa_bovespa;
	l.taxa_corretagem = taxa_corretagem;
	l.taxa_iss = taxa_iss;
	l.taxa_irrf = taxa_irrf;
	l.taxa_outras = taxa_outras;

	l.verificar(default_id);

	return l;
};

Lancto.incluir_linha_vazia = function ()
{
	bgclasse = "zebra1";
	var nova_linha = $("<tr></tr>");
	nova_linha.attr('class', bgclasse);
	var nova_coluna = $("<td></td>");
	nova_coluna.attr("colspan", "10");
	nova_coluna_span = $("<span></span>");
	nova_coluna.append(nova_coluna_span);
	nova_linha.append(nova_coluna);
	tela.table_lanctos.append(nova_linha);
};

Lancto_class.incluir_na_tela = function(animar, bgclasse, prox_data)
{
	prox_data = -1;

	bgclasse = ["zebra0", "zebra1", "novo0", "novo1"][bgclasse];

	this.htmlnode = $("<tr style='display: none'></tr>");
	var ID = "rowlanc" + this.id;
	this.htmlnode.attr('id', ID);
	this.htmlnode.attr('class', bgclasse);

	var nova_coluna;
	var items = [];
	var classes = [];
	var td_classes = [];
	var classe = (this.qtde < 0 ? "dispendio" : "");

	items.push((this.data != prox_data) ? dtoc(this.data) : "");
	classes.push("");
	td_classes.push("numero");

	items.push(htoc(this.hora));
	classes.push("");
	td_classes.push("numero");

	items.push(this.papel);
	classes.push("");
	td_classes.push("");

	items.push(formatFinancial(this.qtde, 0));
	classes.push(classe);
	td_classes.push("numero");

	items.push("×");
	classes.push(classe);
	td_classes.push("numero");

	items.push(formatFinancial(this.valor_unitario));
	classes.push(classe);
	td_classes.push("numero");

	items.push("=");
	classes.push(classe);
	td_classes.push("numero");

	items.push(formatFinancial(this.valor_total));
	classes.push(classe);
	td_classes.push("numero");

	items.push(this.taxas !== 0 ? "+" + formatFinancial(this.taxas) : "");
	classes.push("");
	td_classes.push("numero");

	var item;
	for (var i = 0; i < items.length; ++i) {
		item = items[i];
		nova_coluna = $("<td></td>");
		nova_coluna.addClass(td_classes[i]);
		nova_coluna_span = $("<span></span>");
		nova_coluna_span.html(item);
		nova_coluna_span.addClass(classes[i]);
		nova_coluna.append(nova_coluna_span);
		this.htmlnode.append(nova_coluna);
	}

	var link_remover = $("<a href='#'></a>");
	// link_remover.attr("href", "javascript:main.remover(" + this.id + ")");
	var closure_id = this.id;
	link_remover.click(function () { main.remover(closure_id); return false; });
	link_remover.html("remover");
	// nova_coluna_span = document.createElement("span");
	// nova_coluna_span.append(link_remover);
	nova_coluna = $("<td></td>");
	// nova_coluna.append(nova_coluna_span);
	nova_coluna.append(link_remover);
	this.htmlnode.append(nova_coluna);

	tela.table_lanctos.prepend(this.htmlnode);
	
	this.htmlnode.fadeIn(ANIM_ITEM);
};

Lancto_class.remover_da_tela = function(animar) {
	if (this.htmlnode) {
		this.deleted = true;
		// closure to avoid the changing meaning of 'this'
		var clos = this;
		clos.htmlnode.fadeOut(animar ? ANIM_ITEM : 0, function() {
			clos.htmlnode.remove();
			clos.htmlnode = null;
		});
	}
};

/*jslint browser: true, white: false */
/*global exibir, main, tela*/

function Main() {
	this.lanctos = [];
	this.last_id = 1;
	this.bgnovo = 0;
	this.arquivo_salvo = "taxman.txt";

	this.lucro_normal = 0;
	this.lucro_daytrade = 0;
	this.imposto = 0;

	this.storage = null;
}

Main.DIARIO = 1;
Main.MENSAL = 2;
Main.ANUAL = 4;
Main.TUDO  = 8;
Main.POR_PAPEL = 16;

var Main_class = Main.prototype;

Main_class.vazio = function ()
{
	return this.lanctos.length <= 0;
};

Main_class.ler_storage = function (engine)
{
	if (this.storage) {
		// Bug esquisitíssimo do IE7
		return;
	}
	this.storage = engine;

	var lanctos_raw = this.storage.get("lanctos");
	var arquivo_raw = this.storage.get("lanctos_arquivo");

	if (! lanctos_raw) {
		lanctos_raw = [];
	}
	if (! arquivo_raw) {
		arquivo_raw = [ this.arquivo_salvo ];
	}
	if (! arquivo_raw[0]) {
		arquivo_raw = [ this.arquivo_salvo ];
	}
	if (arquivo_raw[0] == "undefined") {
		arquivo_raw = [ this.arquivo_salvo ];
	}
	
	this.arquivo_salvo = arquivo_raw[0];
	this.ler_storage2(lanctos_raw);
};

Main_class.ler_storage2 = function (lanctos_raw)
{
	var lancto, l;
	var teste_existe = function (arg1, arg2) { return arg1.id == arg2.id; };
	this._remover_tudo();

	for (var i = 0; i < lanctos_raw.length; ++i) {
		l = lanctos_raw[i];

		if (a_existe(this.lanctos, teste_existe, l)) {
			continue;
		}

		if (! l.versao) {
			continue;
		}

		if (l.versao != Lancto._VERSAO_REGISTRO) {
			if (l.versao < 13) {
				// upgrade para 13
				l.vltotal = 0;
			} else if (l.versao < 14) {
				// upgrade para 14
				l.taxa_corretagem = l.corretagem;
				l.corretagem = undefined;
				l.taxa_cblc_liq = l.taxa_cblc_reg = l.taxa_bovespa =
					l.taxa_iss = l.taxa_irrf = l.taxa_outras = 0;
			} else {
				continue;
			}
		}

		if (! l.id) {
			continue;
		}

		if (! l.papel) {
			continue;
		}

		if (! l.qtde) {
			continue;
		}

		if (l.vlunit === undefined) {
			continue;
		}

		if (l.vltotal === undefined) {
			continue;
		}

		if (l.taxa_cblc_liq === undefined) {
			continue;
		}

		if (l.taxa_cblc_reg === undefined) {
			continue;
		}

		if (l.taxa_bovespa === undefined) {
			continue;
		}

		if (l.taxa_corretagem === undefined) {
			continue;
		}

		if (l.taxa_iss === undefined) {
			continue;
		}

		if (l.taxa_irrf === undefined) {
			continue;
		}

		if (l.taxa_outras === undefined) {
			continue;
		}

		if (! l.data) {
			continue;
		}

		if (! l.hora) {
			continue;
		}

		lancto = Lancto.criar(this.last_id, l.id, l.data, l.hora, l.papel, 
					l.qtde, l.vlunit, l.vltotal, l.taxa_cblc_liq,
					l.taxa_cblc_reg, l.taxa_bovespa, l.taxa_corretagem,
					l.taxa_iss, l.taxa_irrf, l.taxa_outras);

		if (! lancto.ok) {
			continue;
		}
		lancto.remover_provisorios();

		this.lanctos.push(lancto);
		if (lancto.id > this.last_id) {
			this.last_id = lancto.id + 1;
		}
	}

	this.consolidar();
	this.mostrar_tudo_na_tela();
	exibir("lanctos", true);
};


Main_class.mostrar_tudo_na_tela = function () {
	var zebra = this.lanctos.length % 2;
	var prox_data = -1;
	var lancto, lancto2;

	// this.lanctos.reverse();
	for (var j = 0; j < this.lanctos.length; ++j) {
		lancto = this.lanctos[j];
		lancto2 = this.lanctos[parseInt(j, 10)+1];
		if (lancto2) {
			prox_data = lancto2.data;
		} else {
			prox_data = -1;
		}
		lancto.incluir_na_tela(0, zebra, prox_data);
		zebra = zebra ? 0 : 1;
	}
	// this.lanctos.reverse();

	if ((this.lanctos.length % 2) === 0) {
		Lancto.incluir_linha_vazia();
	}
};

Main_class.gravar_storage = function ()
{
	var lista_exp = [];

	for (var k = 0; k < this.lanctos.length; ++k) {
		lista_exp.push(this.lanctos[k].copia_para_exportacao());
   }

   try {
	   this.storage.set("lanctos", $.toJSON(lista_exp));
	   this.storage.set("lanctos_arquivo", $.toJSON([ this.arquivo_salvo ]));
   } catch (exp) {
	   alert("Armazenamento local do lançamento falhou. Provavelmente seu navegador não é compativel com este recurso. Tente usar outro navegador, como Firefox ou Chrome.");
	}
};

Main_class.ordenar = function() 
{
	this.lanctos.sort( function (a, b) {
		return (a.data * 10000 + a.hora) - (b.data * 10000 + b.hora);
	});
};

Main_class.is_lancto_novo = function (lancto)
{
	var i = a_procura(this.lanctos, function(a) { return a.data == lancto.data && a.hora == lancto.hora; });
	return i <= -1;
};

Main_class.erro = function(serr)
{
	tela.err.html(serr);
};

Main_class.adicionar = function () 
{
	this.erro("");

	var data = $("#form_data").val();
	var hora = $("#form_hora").val();
	var papel = $("#form_papel").val();
	var qtde = $("#form_qtde").val();
	var vlunit = $("#form_vlunit").val();
	var vltotal = $("#form_vltotal").val();
	var taxa_cblc_liq = $("#form_taxa_cblc_liq").val();
	var taxa_cblc_reg = $("#form_taxa_cblc_reg").val();
	var taxa_bovespa = $("#form_taxa_bovespa").val();
	var taxa_corretagem = $("#form_taxa_corretagem").val();
	var taxa_iss = $("#form_taxa_iss").val();
	var taxa_irrf = $("#form_taxa_irrf").val();
	var taxa_outras = $("#form_taxa_outras").val();

	hora = ctoh(hora);
	var hora_salva = hora;

	var lancto = Lancto.criar(null, ++this.last_id, data, hora, papel, qtde, vlunit, vltotal,
				taxa_cblc_liq, taxa_cblc_reg, taxa_bovespa, taxa_corretagem,
				taxa_iss, taxa_irrf, taxa_outras);

	if (lancto.ok) {
		lancto.remover_provisorios();
		if (! this.is_lancto_novo(lancto)) {
			this.erro("Já existe lançamento com esta data/hora");
		} else {
			this.lanctos.push(lancto);
			lancto.incluir_na_tela(1, 2 + this.bgnovo, -1);
			this.consolidar();
			if (this.lanctos.length == 1) { // portanto estava vazia até há pouco
				exibir("lanctos", false);
			}
			this.bgnovo = (this.bgnovo ? 0 : 1);
			
			hora_salva = addMinutes(hora_salva, 15);
			$("#form_hora").val(htoc(hora_salva));
		}
	} else {
		this.erro(lancto.err);
	}
};

Main_class._remover = function(i, animar) 
{
	if (this.lanctos[i]) {
		this.lanctos[i].remover_da_tela(animar);
		a_remove(this.lanctos, i);
	}
};

Main_class._remover_tudo = function() 
{
	while (! this.vazio()) {
		this._remover(0, false);
	}
};

Main_class.remover_tudo = function ()
{
	if (prompt("Digite SIM se você realmente deseja remover todos os lançamentos armazenados nesta página", "Não") == "SIM") {
		this._remover_tudo();
		this.consolidar();
	} else {
		alert("Remoção cancelada.");
	}
	// lista ficou vazia, esconder tudo
	exibir("lanctos", false);
};

Main_class.remover = function (id)
{
	var i = a_procura(this.lanctos, function(a) { return a.id == id; });
	if (i > -1) {
		var lancto = this.lanctos[i];
		if (confirm("Confirma a remoção do lançamento " + formatFinancial(lancto.qtde, 0) + 
				" x " + lancto.papel + " do dia " + dtoc(lancto.data) + "?")) {
			this._remover(i, true);
			this.consolidar();
		}
	}
	if (this.vazio()) {
		// lista ficou vazia, esconder tudo
		exibir("lanctos", false);
	}
};

Main_class.consolidar = function ()
{
	this.gravar_storage();
	this.ordenar();
	this.processar();
	this.atualizar_saldos();
};

/*jslint browser: true, white: false */
 

Main_class.adicionar_saldo_tela = function (papel, qtde, valor, bgclasse)
{
	bgclasse = ["zebra0", "zebra1", "total"][bgclasse];

	var nova_linha = $("<tr></tr>");
	nova_linha.addClass(bgclasse);

	var nova_coluna;
	var items = [];
	var classes = [];
	var td_classes = [];
	var classe = (qtde < 0? "negativo" : "");
	

	if (papel) {
		items.push(papel);
		classes.push(classe);
		td_classes.push("");
	
		items.push(formatFinancial(qtde, 0));
		classes.push(classe);
		td_classes.push("numero");
	
		items.push(bgclasse == "total" ? "" : "×");
		classes.push(classe);
		td_classes.push("numero");
	
		items.push(bgclasse == "total" ? "" : formatFinancial(valor / qtde));
		classes.push(classe);
		td_classes.push("numero");
	
		items.push(bgclasse == "total" ? "" : "=");
		classes.push(classe);
		td_classes.push("numero");
	
		items.push(formatFinancial(valor));
		classes.push(classe);
		td_classes.push("numero");
	} else {
		for (var i = 1; i <= 6; ++i) {
			items.push("");
			classes.push("");
			td_classes.push("");
		}
	}
	
	var item;
	for (var j = 0; j < items.length; ++j) {
		item = items[j];
		nova_coluna = $("<td></td>");
		nova_coluna.addClass(td_classes[j]);
		nova_coluna_span = $("<span></span>");
		nova_coluna_span.addClass(classes[j]);
		nova_coluna_span.html(item);
		nova_coluna.append(nova_coluna_span);
		nova_linha.append(nova_coluna);
	}

	tela.table_saldos.append(nova_linha);
};


Main_class.adicionar_saldos_tela = function ()
{
	tela.table_saldos.children().each(function() {
		var t = $(this);
		t.remove();
	});

	var papeis = [];
	var total_qtde = 0, total_valor = 0;

	for (var k in this.saldos_qtde) {
		if (this.saldos_qtde[k] !== 0) {
			papeis.push(k);
		}
	}

	papeis.sort();
	var zebra = 1;

	for (var j = 0; j < papeis.length; ++j) {
		this.adicionar_saldo_tela(papeis[j], this.saldos_qtde[papeis[j]], this.saldos_valor[papeis[j]], zebra);
		total_qtde += this.saldos_qtde[papeis[j]];
		total_valor += this.saldos_valor[papeis[j]];
		zebra = zebra ? 0 : 1;
	}

	this.adicionar_saldo_tela("Total", total_qtde, total_valor, 2);

	/*
	if (zebra || papeis.length <= 0) {
		this.adicionar_saldo_tela(null, 1, 0, 1);
	}
	*/
};


Main_class.atualizar_saldos = function ()
{
	this.adicionar_saldos_tela();

	valores = [this.lucro_normal, this.lucro_daytrade, this.lucro_normal + this.lucro_daytrade, this.imposto];
	for (var k = 0; k < valores.length; ++k) {
		tela.saldos[k].html(formatFinancial(valores[k]));
		tela.saldos[k].attr('class', (valores[k] < 0 ? "negativo" : ""));
	}
};

/*jslint browser: true, white: false */

var rotinas = [];

function Ticket(gerador1, gerador2, data, hora, papel, valor, nivel, texto, cargo)
{
	this.gerador1 = gerador1;
	this.gerador2 = gerador2;
	this.id = ++main.last_ticket_id;
	this.ticket = 1;
	this.data = data;
	this.hora = hora;
	this.ordem = 5;
	this.ordem_offset = 1;
	this.ddata = dtoD(data);
	this.papel = papel;
	this.qtde = 1; /* dummy */
	this.valor = valor;
	this.nivel = nivel;
	this.texto = texto;
	this.cargo = cargo;
	this.saldo_qtde = 0;
	this.saldo_valor = 0;
	this.daytrade = 0;
	this.enfeite = nivel >= 4 ? '\u2716 ' : (nivel >= 3 ? '\u2702  ' : '\u2714  ');
	this.classe = ["nivel1", "nivel2", "nivel3", "nivel4", "nivel5"][nivel];
}

Main_class.ordenar_concentrado = function() 
{
	this.concentrado.sort( function (a, b) {
		if (a.data == b.data) {
			if (a.hora == b.hora) {
				if (a.ticket == b.ticket) {
					if (a.ticket) {
						// ordem de criação dos tickets, no mesmo dia
						return a.id - b.id;
					} else {
						return a.ordem - b.ordem;
					}
				} else {
					// tickets aparecem depois do lançamento
					return a.ticket - b.ticket;
				}
			} else {
				return a.hora - b.hora;
			}
		} else {
			return a.data - b.data;
		}
	});
};

Main_class.adicionar_proc_tela = function (plancto, bgclasse, data_ant)
{
	data_ant = -1;
	bgclasse = ["zebra0", "zebra1"][bgclasse];

	var nova_linha = $("<tr></tr>");
	var nova_coluna, nova_coluna_span;

	if (! plancto) {
		nova_linha.attr('class', bgclasse);
		nova_coluna = $("<td></td>");
		nova_coluna.attr("colspan", "7");
		nova_linha.append(nova_coluna);
		nova_coluna = $("<td></td>");
		nova_coluna_span = $("<span></span>");
		nova_coluna.append(nova_coluna_span);
		nova_linha.append(nova_coluna);
	} else {
		nova_linha.attr('class', plancto.ticket ? plancto.classe : bgclasse);

		var items = [];
		var classes = [];
		var td_classes = [];
		var classe = plancto.ticket ? 
				(plancto.valor < 0 ? "dispendio" : "") :
				(plancto.qtde < 0 ? "dispendio" : "");

		items.push(plancto.data == data_ant ? "" : dtoc(plancto.data));
		classes.push("");
		td_classes.push("numero");
	
		items.push(plancto.ticket ? "" : htoc(plancto.hora));
		classes.push("");
		td_classes.push("numero");
	
		items.push(plancto.papel);
		classes.push("");
		td_classes.push("");
	
		items.push(plancto.ticket ? "" : formatFinancial(plancto.qtde, 0));
		classes.push(classe);
		td_classes.push("numero");
	
		if (plancto.ticket) {
			items.push(plancto.valor ? formatFinancial(plancto.valor) : "");
		} else {
			items.push(formatFinancial((plancto.valor_total + plancto.taxas)));
		}
		classes.push(classe);
		td_classes.push("numero");

		var saldo_sufixo = plancto.daytrade ? " dt" : "";

		if (plancto.ticket) {
			items.push("");
		} else {
			items.push(formatFinancial(plancto.saldo_qtde, 0) + saldo_sufixo);
		}
		classes.push(plancto.saldo_qtde < 0 ? "dispendio" : "");
		td_classes.push("numero");

		if (plancto.ticket) {
			items.push("");
		} else {
			items.push(formatFinancial(plancto.saldo_valor) + saldo_sufixo);
		}
		classes.push(plancto.saldo_valor < 0 ? "dispendio" : "");
		td_classes.push("numero");

		items.push(plancto.ticket ? plancto.enfeite + plancto.texto : "");
		classes.push("");
		td_classes.push("");
	
		var item;
		for (var i = 0; i < items.length; ++i) {
			item = items[i];
			nova_coluna = $("<td></td>");
			nova_coluna.addClass(td_classes[i]);
			nova_coluna_span = $("<span></span>");
			nova_coluna_span.addClass(classes[i]);
			nova_coluna_span.html(item);
			nova_coluna.append(nova_coluna_span);
			nova_linha.append(nova_coluna);
		}
	}

	tela.table_proc.append(nova_linha);
};


Main_class.adicionar_procs_tela = function ()
{
	var zebra = 1;
	var data_ant = -1;
	var c;

	tela.table_proc.children().each(function() {
		var t = $(this);
		t.remove();
	});

	this.concentrado.reverse();
	for (var k = 0; k < this.concentrado.length; ++k) {
		c = this.concentrado[k];
		this.adicionar_proc_tela(c, zebra, data_ant);
		if (c.ticket) {
			zebra = 0;
		} else {
			zebra = zebra ? 0 : 1;
		}
		data_ant = c.data;
	}
	if (zebra) {
		this.adicionar_proc_tela(null, zebra);
	}
	this.concentrado.reverse();
};

Main_class.calcula_concentrado_total = function()
{
	this.concentrado = [];
	this.saldos_qtde = [];
	this.saldos_valor = [];

	for(var i = 0; i < this.lanctos.length; ++i) {
		var lancto = this.lanctos[i];
		var papel = lancto.papel;

		if (! this.saldos_qtde[papel]) {
			this.saldos_qtde[papel] = 0;
			this.saldos_valor[papel] = 0;
		}

		lancto_analise = jQuery.extend({}, lancto);
		lancto_analise.ticket = 0;

		lancto_analise.saldo_qtde = 0;
		lancto_analise.saldo_valor = 0;
		lancto_analise.daytrade = 0;

		lancto_analise.ddata = dtoD(lancto_analise.data);
		// serve para estabelecer ordem relativa entre versoes desmembradas do mesmo lancamento
		lancto_analise.ordem = 5;
		lancto_analise.ordem_offset = 1;

		this.concentrado.push(lancto_analise);
	}
};

Main_class.calcula_concentrados_parciais = function ()
{
	this.concentrado_papel = [];
	this.concentrado_papel_diario = [];
	this.concentrado_papel_mensal = [];
	this.concentrado_papel_anual = [];
	this.concentrado_diario = [];
	this.concentrado_mensal = [];
	this.concentrado_anual = [];

	this.ordenar_concentrado();

	for (var k = 0; k < this.concentrado.length; ++k) {
		var lancto = this.concentrado[k];
		var papel = lancto.papel;
		var ano = lancto.ddata.getFullYear();
		var mes = ano * 100 + lancto.ddata.getMonth();
		var dia = lancto.data;
		if (! this.concentrado_papel[papel]) {
			this.concentrado_papel[papel] = [];
			this.concentrado_papel_diario[papel] = [];
			this.concentrado_papel_mensal[papel] = [];
			this.concentrado_papel_anual[papel] = [];
		}

		if (! this.concentrado_papel_diario[papel][dia]) {
			this.concentrado_papel_diario[papel][dia] = [];
		}

		if (! this.concentrado_papel_mensal[papel][mes]) {
			this.concentrado_papel_mensal[papel][mes] = [];
		}

		if (! this.concentrado_papel_anual[papel][ano]) {
			this.concentrado_papel_anual[papel][ano] = [];
		}

		if (! this.concentrado_diario[dia]) {
			this.concentrado_diario[dia] = [];
		}

		if (! this.concentrado_mensal[mes]) {
			this.concentrado_mensal[mes] = [];
		}

		if (! this.concentrado_anual[ano]) {
			this.concentrado_anual[ano] = [];
		}

		this.concentrado_diario[dia].push(lancto);
		this.concentrado_mensal[mes].push(lancto);
		this.concentrado_anual[ano].push(lancto);
		this.concentrado_papel_diario[papel][dia].push(lancto);
		this.concentrado_papel_mensal[papel][mes].push(lancto);
		this.concentrado_papel_anual[papel][ano].push(lancto);
		this.concentrado_papel[papel].push(lancto);
	}
};

Main_class.processa1 = function(concentrado, rotina)
{
	var res = {tickets: [], desmembramentos: []};
	if (concentrado.length > 0) {
		// garante que rotina é chamada apenas quando há o que processar
		res = rotina(concentrado);
	}
	return res;
};

Main_class.processa2 = function(concentrado, rotina)
{
	var res = {tickets: [], desmembramentos: []};
	for (var k in concentrado) {
		if (true) {
			var cres = this.processa1(concentrado[k], rotina);
			res.tickets = res.tickets.concat(cres.tickets);
			res.desmembramentos = res.desmembramentos.concat(cres.desmembramentos);
		}
	}
	return res;
};

Main_class.processa3 = function(concentrado, rotina)
{
	var res = {tickets: [], desmembramentos: []};
	for (var k in concentrado) {
		if (true) {
			var cres = this.processa2(concentrado[k], rotina);
			res.tickets = res.tickets.concat(cres.tickets);
			res.desmembramentos = res.desmembramentos.concat(cres.desmembramentos);
		}
	}
	return res;
};

Main_class.processar = function ()
{
	var res;
	this.last_ticket_id = 0;
	var recalcular = 1;
	this.lucro_normal = this.lucro_daytrade = this.imposto = 0;

	this.calcula_concentrado_total();

	for (var k = 0; k < rotinas.length; ++k) {
		var rotina = rotinas[k];

		if (recalcular) {
			this.calcula_concentrados_parciais();
			recalcular = 0;
		}

		if (rotina.tipo & Main.POR_PAPEL) {
			if (rotina.tipo & Main.DIARIO) {
				res = this.processa3(this.concentrado_papel_diario, rotina);
			} else if (rotina.tipo & Main.MENSAL) {
				res = this.processa3(this.concentrado_papel_mensal, rotina);
			} else if (rotina.tipo & Main.ANUAL) {
				res = this.processa3(this.concentrado_papel_anual, rotina);
			} else if (rotina.tipo & Main.TUDO) {
				res = this.processa2(this.concentrado_papel, rotina);
			}
		} else {
			if (rotina.tipo & Main.DIARIO) {
				res = this.processa2(this.concentrado_diario, rotina);
			} else if (rotina.tipo & Main.MENSAL) {
				res = this.processa2(this.concentrado_mensal, rotina);
			} else if (rotina.tipo & Main.ANUAL) {
				res = this.processa2(this.concentrado_anual, rotina);
			} else if (rotina.tipo & Main.TUDO) {
				res = this.processa1(this.concentrado, rotina);
			}
		}

		if (res.desmembramentos.length > 0) {
			this.processar_desmembramentos(res.desmembramentos);
			recalcular = 1;
		}
		if (res.tickets.length > 0) {
			this.concentrado = this.concentrado.concat(res.tickets);
			recalcular = 1;
		}
	}

	this.ordenar_concentrado();
	this.adicionar_procs_tela();
};

Main_class.processar_desmembramentos = function(desmembramentos)
{
	var novos_lanctos = [];
	for(var k = 0; k < desmembramentos.length; ++k) {
		var D = desmembramentos[k];

		AUDIT(D.qtdes[0], "Desmembramento: lançamento original não pode ser zerado");
		AUDIT(D.original.qtde === (D.qtdes[0] + D.qtdes[1] + D.qtdes[2]), "Desmembramento: soma das partes deve ser igual ao original");

		var lancto0 = D.original;
		var totorig = D.original.valor_total;
		var vlu = 0;
		if (Math.abs(D.original.qtde) !== 0) {
		   vlu = D.original.valor_total / D.original.qtde;
		}
		lancto0.ordem_offset /= 2;
		var lancto1 = jQuery.extend({}, lancto0);
		var lancto2 = jQuery.extend({}, lancto0);

		lancto0.qtde = D.qtdes[0];
		lancto1.qtde = D.qtdes[1];
		lancto2.qtde = D.qtdes[2];

		lancto0.daytrade = D.dt[0];
		lancto1.daytrade = D.dt[1];
		lancto2.daytrade = D.dt[2];

		lancto0.valor_total = Math.round(vlu * lancto0.qtde);
		lancto1.valor_total = Math.round(vlu * lancto1.qtde);
		lancto2.valor_total = totorig - lancto0.valor_total - lancto1.valor_total;

		lancto0.ordem = lancto0.ordem - lancto0.ordem_offset;
		// lancto1 herda a ordem do lançamento original
		lancto2.ordem = lancto2.ordem + lancto2.ordem_offset;

		if (lancto1.qtde) {
			this.concentrado.push(lancto1);
		} 

		if (lancto2.qtde) {
			this.concentrado.push(lancto2);
		}
	}
};


function rotina(tipo, funcao) {
	var closure = function (concentrado) {
		AUDIT(concentrado.length > 0, "Rotina deve receber concentrado não-vazio");
		var t = [], d = [];
		funcao(concentrado, t, d);
		return {tickets: t, desmembramentos: d};
	};
	closure.tipo = tipo;
	rotinas.push(closure);
}

function percorre_lancamentos(concentrado, funcao)
{
	for(var k = 0; k < concentrado.length; ++k) {
		var lancto = concentrado[k];
		if (lancto.ticket === 0) {
			if (funcao(lancto)) {
				break;
			}
		}
	}
}

function percorre_tickets(concentrado, funcao)
{
	for(var k = 0; k < concentrado.length; ++k) {
		var lancto = concentrado[k];
		if (lancto.ticket === 1) {
			if (funcao(lancto)) {
				break;
			}
		}
	}
}

function percorre_tudo(concentrado, funcao)
{
	for(var k = 0; k < concentrado.length; ++k) {
		var lancto = concentrado[k];
		if (funcao(lancto)) {
			break;
		}
	}
}

/*jslint browser: true, white: false */

/* Rotinas de processamento */

// desmembra lançamentos que invertem o saldo total

rotina(Main.TUDO | Main.POR_PAPEL, function (concentrado, tickets, desmembramentos)
{
	var saldo_ant = 0;
	percorre_lancamentos(concentrado, function (L) {
		L.daytrade = 0; // indefinido
		if (sinal_oposto(L.qtde, saldo_ant) && Math.abs(L.qtde) > Math.abs(saldo_ant)) {
			// sinal diferente e maior que saldo anterior = inversão de sinal
			desmembramentos.push({original: L, 
						  qtdes: [-saldo_ant, L.qtde + saldo_ant, 0],
						  dt: [0, 0, 0]});
		}
		saldo_ant += L.qtde;
	});
});

// calcula saldo atual de cada lançamento, para fins de detecção de daytrade

rotina(Main.TUDO | Main.POR_PAPEL, function (concentrado, tickets, desmembramentos)
{
	var saldo = 0;
	percorre_lancamentos(concentrado, function (L) {
		L._sq_ant = saldo;
		saldo += L.qtde;
	});
});


// detecta lançamentos DT e desmembra lançamentos zerocross DT

rotina(Main.DIARIO | Main.POR_PAPEL, function (concentrado, tickets, desmembramentos)
{
	var posicao = null;
	var saldo_dt, saldo_geral;

	percorre_lancamentos(concentrado, function (L) {
		if (posicao === null) {
			saldo_geral = posicao = L._sq_ant;
			saldo_dt = 0;
			delete L._sq_ant;
		}

		saldo_geral += L.qtde;

		if (saldo_dt !== 0 && sinal_oposto(saldo_dt, L.qtde)) {
			// saldo DT + um lançamento de sinal contrário = realização DT
		
			if (Math.abs(L.qtde) > Math.abs(saldo_dt)) {
				// liquida & inverte saldo DT
		
				var liquida_dt = -saldo_dt;
				var liquida_pos = 0;
				var reconstroi_dt = L.qtde + saldo_dt;

				if (sinal_oposto(reconstroi_dt, posicao)) {
					// reconstroi_dt pode ser usado para queimar posição
					var transf = min_abs(reconstroi_dt, -posicao);
					liquida_pos += transf;
					reconstroi_dt -= transf;
				}

				desmembramentos.push({original: L, 
							  qtdes: [liquida_dt, liquida_pos, reconstroi_dt], 
							  dt: [1, -1, 1]});

				saldo_dt += liquida_dt;
				posicao += liquida_pos;
				saldo_dt += reconstroi_dt;

			} else {
				// apenas liquida parte do saldo DT
				saldo_dt += L.qtde;
				L.daytrade = 1;
			}

		} else if (posicao !== 0 && sinal_oposto(posicao, L.qtde)) {
			// liquida posição
			if (Math.abs(L.qtde) > Math.abs(posicao)) {
				// liquida posição e ainda cria novo saldo DT
				desmembramentos.push({original: L, 
							  qtdes: [-posicao, L.qtde + posicao, 0],
							  dt: [-1, 1, 0]});
				saldo_dt = L.qtde + posicao;
				posicao = 0;
			} else {
				// liquida apenas parte da posição
				posicao += L.qtde;
				L.daytrade = -1;
			}

		} else {
			// cria saldo DT
			saldo_dt += L.qtde;
			L.daytrade = 1;
		}

		// como só vai aproveitar o último de cada dia, não conflita com desmembramentos
		L._sqdt = saldo_dt;
	});

	AUDIT(saldo_dt + posicao === saldo_geral, "Saldo geral diferente da soma de posição + daytrade");
});


// desmembra lançamentos que seriam DT mas deixaram saldo para o dia seguinte

rotina(Main.DIARIO | Main.POR_PAPEL, function (concentrado, tickets, desmembramentos)
{
	var posicao = null;

	// percorreremos a lista ao contrário
	concentrado.reverse();

	percorre_lancamentos(concentrado, function (L) {
		if (posicao === null) {
			posicao = L._sqdt;
			delete L._sqdt;
		}

		if (posicao !== 0) {
			if (mesmo_sinal(posicao, L.qtde)) {
				// este lançamento ajuda a criar posição não-DT
				if (Math.abs(L.qtde) > Math.abs(posicao)) {
					// parte final do lançamento é posição: desmembrar
					L.daytrade = 0; // assegura que desmembramento vai setar
					desmembramentos.push({original: L,
							  qtdes: [L.qtde - posicao, posicao, 0],
							  dt: [1, -1, 0]});
					posicao = 0;
				} else {
					// este lancamento cria parte da posição
					L.daytrade = -1;
					// sinal negativo pois estamos percorrendo ao contrário
					posicao -= L.qtde;
				}
			}
		} else {
			// nada mais a fazer
			return 1;
		}
		return null;
	});

	// desfaz inversão para outros poderem reaproveitar o concentrado
	concentrado.reverse();
});


// normaliza variável daytrade

rotina(Main.TUDO, function (concentrado, tickets, desmembramentos)
{
	percorre_lancamentos(concentrado, function (L) {
		AUDIT(L.daytrade !== 0, "Lançamento com status daytrade ambíguo: " + 
						dtoc(L.data) + " " + htoc(L.hora));
		L.daytrade = L.daytrade > 0;
	});
});

// verifica que nenhum zero-cross ocorre no histórico do papel
// (sinal que os desmembramentos foram exatos)

rotina(Main.DIARIO | Main.POR_PAPEL, function (concentrado, tickets, desmembramentos)
{
	var saldo = 0, saldo_ant = 0;
	percorre_lancamentos(concentrado, function (L) {
		saldo += L.qtde;
		AUDIT(mesmo_sinal(saldo_ant, saldo), "Lançamento de posição cruza saldo zero no papel " + L.papel);
	});
});

// verifica que o saldo daytrade de cada dia é igual a zero
// e que nenhum zero-cross ocorre dentro do dia
// (sinal que os desmembramentos foram exatos)

rotina(Main.DIARIO | Main.POR_PAPEL, function (concentrado, tickets, desmembramentos)
{
	var saldo = 0, saldo_ant = 0, papel = "", data = ctod("01/01/1970");
	percorre_lancamentos(concentrado, function (L) {
		if (L.daytrade) {
			saldo += L.qtde;
			AUDIT(mesmo_sinal(saldo_ant, saldo), "Lançamento de daytrade cruza saldo zero no papel " + L.papel);
			saldo_ant = saldo;
			papel = L.papel;
			data = L.data;
		}
	});

	AUDIT(saldo === 0, "Saldo daytrade não zerado ao final do dia: " + papel + " " + dtoc(data));
});

// verifica que nenhum zero-cross ocorre para lançamentos normais
// (sinal que os desmembramentos foram exatos)

rotina(Main.DIARIO | Main.POR_PAPEL, function (concentrado, tickets, desmembramentos)
{
	var saldo = 0, saldo_ant = 0;
	percorre_lancamentos(concentrado, function (L) {
		if (! L.daytrade) {
			saldo += L.qtde;
			AUDIT(mesmo_sinal(saldo_ant, saldo), "Lançamento de posição cruza saldo zero no papel " + L.papel);
			saldo_ant = saldo;
		}
	});
});


// calcula saldos de valor e realizações normais (não DT)

rotina(Main.TUDO | Main.POR_PAPEL, function (concentrado, tickets, desmembramentos)
{
	var papel = null, acao, qtde_velha, saldo_velho, qtde_nova, preco_medio;
	var qtde_extinta, saldo_realizado, venda_acao;

	percorre_lancamentos(concentrado, function (L) {
		if (! L.daytrade) {
			if (papel === null) {
				papel = L.papel;
				acao = (tipo_papel(papel) != tipo_papel.OPCAO);
			}
	
			/* custos de aquisicao aumentam desde já o custo de aquisição */
			main.saldos_valor[papel] += L.taxas;
	
			saldo_velho = main.saldos_valor[papel];
			qtde_velha = main.saldos_qtde[papel];
			preco_medio = qtde_velha === 0 ? 0 : (saldo_velho / qtde_velha);
	
			qtde_nova = main.saldos_qtde[papel] += L.qtde;
	
			qtde_extinta = saldo_realizado = 0;
	
			if (Math.abs(qtde_nova) > Math.abs(qtde_velha)) {
				// aumento do saldo absoluto
				// custo totalmente adicionado ao saldo
				main.saldos_valor[papel] += L.valor_total;
			} else {
				// diminuição do saldo absoluto
				// saldo diminui cfe. preço médio de formação
				qtde_extinta = L.qtde;
				saldo_realizado = Math.round(L.qtde * preco_medio);
				main.saldos_valor[papel] += saldo_realizado;
			}

			L.saldo_qtde = main.saldos_qtde[papel];
			L.saldo_valor = main.saldos_valor[papel];
			L.qtde_extinta = qtde_extinta;
			L.saldo_realizado = saldo_realizado;
			L.saldo_realizado_custo = qtde_extinta * L.valor_unitario;
			L._va20k = acao && (qtde_extinta < 0); /* venda de ações */
		}
	});
});


// calcula saldos de valor e realizações DT

rotina(Main.TUDO | Main.POR_PAPEL, function (concentrado, tickets, desmembramentos)
{
	var papel, qtde_velha, saldo_velho, qtde_nova, preco_medio;
	var qtde_extinta, saldo_realizado;
	var saldo_valor = 0, saldo_qtde = 0;

	percorre_lancamentos(concentrado, function (L) {
		if (L.daytrade) {
			papel = L.papel;
	
			/* custo de operacao aumenta desde já o custo de aquisição */
			saldo_valor += L.taxas;
	
			saldo_velho = saldo_valor;
			qtde_velha = saldo_qtde;
			preco_medio = qtde_velha === 0 ? 0 : (saldo_velho / qtde_velha);
	
			qtde_nova = saldo_qtde += L.qtde;
	
			qtde_extinta = saldo_realizado = 0;
	
			if (Math.abs(qtde_nova) > Math.abs(qtde_velha)) {
				// aumento do saldo absoluto
				// custo totalmente adicionado ao saldo
				saldo_valor += L.valor_total;
			} else {
				// diminuição do saldo absoluto
				// saldo diminui cfe. preço médio de formação
				qtde_extinta = L.qtde;
				saldo_realizado = Math.round(L.qtde * preco_medio);
				saldo_valor += saldo_realizado;
			}
	
			L.saldo_qtde = saldo_qtde;
			L.saldo_valor = saldo_valor;
			L.qtde_extinta = qtde_extinta;
			L.saldo_realizado = saldo_realizado;
			L.saldo_realizado_custo = qtde_extinta * L.valor_unitario;
		}
	});
});

	
// Ganhos de capital, não-daytrade

rotina(Main.TUDO | Main.POR_PAPEL, function (concentrado, tickets, desmembramentos)
{
	percorre_lancamentos(concentrado, function (L) {
		if (! L.daytrade) {
			if (L.qtde_extinta !== 0) {
				// realização de lucros ou prejuízos
				// saldo_realizado: preço de custo que foi removido do saldo
				// saldo_realizado_custo: quanto custou remover a qtde extinta do saldo

				var lucro = L.saldo_realizado - L.saldo_realizado_custo;
				main.lucro_normal += lucro;

				if (L._va20k) {
					var t = new Ticket(L.id, 0, L.data, L.hora, "@RV", lucro, 
							0, "Realização "+L.papel+" venda ações", null);
					t._vbru = -L.saldo_realizado_custo;
					tickets.push(t);
				} else {
					tickets.push(new Ticket(L.id, 0, L.data, L.hora, "@R", lucro, 
							0, "Realização "+L.papel+" normal", null));
				}
				delete L._va20k;
			}
		}
	});
});


// Ganhos de capital, daytrade

rotina(Main.TUDO | Main.POR_PAPEL, function (concentrado, tickets, desmembramentos)
{
	percorre_lancamentos(concentrado, function (L) {
		if (L.daytrade) {
			if (L.qtde_extinta !== 0) {
				// realização de lucros ou prejuízos
				// saldo_realizado: preço de custo que foi removido do saldo
				// saldo_realizado_custo: quanto custou remover a qtde extinta do saldo

				var lucro = L.saldo_realizado - L.saldo_realizado_custo;
				main.lucro_daytrade += lucro;

				tickets.push(new Ticket(L.id, 0, L.data, L.hora, "@Rdt", lucro, 
						0, "Realização "+L.papel+" daytrade", null));
			}
		}
	});
});


var limite_isencao = 2000000; /* centavos */

// Compila ganhos de capital para o fim do mês

rotina(Main.MENSAL, function (concentrado, tickets, desmembramentos)
{
	var lucro = 0, lucrov = 0, venda_bruta = 0;
	var data;

	percorre_tickets(concentrado, function (L) {
		if (L.papel == "@R") {
			lucro += L.valor;
			data = L.data;
		} else if (L.papel == "@RV") {
			lucrov += L.valor;
			venda_bruta += L._vbru;
			data = L.data;
		}
	});

	if (! data) {
		return;
	}

	data = ultimo_dia_mes(data);

	if (venda_bruta > limite_isencao || lucrov < 0) {
		lucro += lucrov;
		lucrov = 0;
	}

	if (lucro !== 0) {
		tickets.push(new Ticket(0, 0, data, 2359, "@L", lucro, 1, (lucro > 0 ? "Lucro" : "Prejuízo") + " normal do mês", null));
	}

	if (lucrov > 0) {
		tickets.push(new Ticket(0, 0, data, 2359, "@LV", lucrov, 1, "Lucro não tributável do mês", null));
	}
});


// Compila ganhos de capital para o fim do mês DT

rotina(Main.MENSAL, function (concentrado, tickets, desmembramentos)
{
	var lucro = 0;
	var data;
	percorre_tickets(concentrado, function (L) {
		if (L.papel == "@Rdt") {
			lucro += L.valor;
			data = L.data;
		}
	});

	if (! data) {
		return;
	}

	data = ultimo_dia_mes(data);
	if (lucro !== 0) {
		tickets.push(new Ticket(0, 0, data, 2359, "@Ldt", lucro, 1, (lucro > 0 ? "Lucro" : "Prejuízo") + " daytrade do mês", null));
	}
});


// Compila lucro tributável normal
// TODO epoch

var ir_normal = 15;
var ir_daytrade = 20;
var darf_minimo = 1000; /* 1000 centavos */

rotina(Main.TUDO, function (concentrado, tickets, desmembramentos)
{
	var lucro = 0;
	var data = null;
	percorre_tickets(concentrado, function (L) {
		if (data) {
			if (L.data > data) {
				if (lucro > 0) {
					data = ultimo_dia_mes(data);
					tickets.push(new Ticket(0, 0, data, 2359,  "@LT", lucro, 2, 
						"Lucro tributável a " + ir_normal + "%", null));
					lucro = 0;
				}
				
			}
		}
		if (L.papel == "@L") {
			lucro += L.valor;
			data = ultimo_dia_mes(L.data);
		}
	});
	if (lucro > 0) {
		data = ultimo_dia_mes(data);
		tickets.push(new Ticket(0, 0, data, 2359, "@LT", lucro, 2, "Lucro tributável a " + ir_normal + "%", null));
		lucro = 0;
	}
});

// Compila lucro tributável daytrade

rotina(Main.TUDO, function (concentrado, tickets, desmembramentos)
{
	var lucro = 0;
	var data = null;
	var ano = null;
	percorre_tickets(concentrado, function (L) {
		if (data) {
			if (L.data > data) {
				if (lucro > 0) {
					data = ultimo_dia_mes(data);
					tickets.push(new Ticket(0, 0, data, 2359, "@LTdt", lucro, 2, 
						"Lucro tributável a " + ir_daytrade + "%", null));
					lucro = 0;
				}
				
			}
			if (data_ano(L.data) != ano && lucro < 0) {
				// daytrade não leva prejuízo para próximo ano
				tickets.push(new Ticket(0, 0, data, 2359, "@Wdt", lucro, 2, 
							"Prejuízo daytrade não mais compensável (virada ano)", null));
				lucro = 0;
			}
		}
		if (L.papel == "@Ldt") {
			lucro += L.valor;
			data = ultimo_dia_mes(L.data);
			ano = data_ano(L.data);
		}
	});
	if (lucro > 0) {
		data = ultimo_dia_mes(data);
		tickets.push(new Ticket(0, 0, data, 2359, "@LTdt", lucro, 2, "Lucro tributável a " + ir_daytrade + "%", null));
		lucro = 0;
	}
});

// Calcula imposto normal

rotina(Main.TUDO, function (concentrado, tickets, desmembramentos)
{
	var imposto = 0;
	percorre_tickets(concentrado, function (L) {
		if (L.papel == "@LT") {
			imposto += Math.round(L.valor * ir_normal / 100);
			main.imposto += imposto;
			if (imposto >= darf_minimo) {
				tickets.push(new Ticket(0, 0, ultimo_dia_util_proximo_mes(L.data), 2359, "@DARF", imposto, 3, "Imposto " + ir_normal + "% a pagar", null));
				imposto = 0;
			}
		}
	});
});

// Calcula imposto daytrade a pagar

rotina(Main.TUDO, function (concentrado, tickets, desmembramentos)
{
	var imposto = 0;
	percorre_tickets(concentrado, function (L) {
		if (L.papel == "@LTdt") {
			imposto += Math.round(L.valor * ir_normal / 100);
			main.imposto += imposto;
			if (imposto >= darf_minimo) {
				tickets.push(new Ticket(0, 0, ultimo_dia_util_proximo_mes(L.data), 2359, "@DARFdt", imposto, 3, "Imposto " + ir_daytrade + "% a pagar", null));
				imposto = 0;
			}
		}
	});
});

// Detecta posições em aberto de opções que já foram exintas

rotina(Main.TUDO | Main.POR_PAPEL, function (concentrado, tickets, desmembramentos)
{
	var vencimento = null;
	var saldo = 0;
	var papel = null;
	var now = agora();
	percorre_lancamentos(concentrado, function (L) {
		if (vencimento) {
			if (vencimento < L.data) {
				// movimentação após o vencimento e com saldo
				tickets.push(new Ticket(0, 0, now, 2359, "@EXP", 0, 4, 
					"Opção "+papel+" com saldo expirado após "+dtoc(vencimento), null));
				saldo = 0;
				vencimento = null;
			}
		}
		
		if (papel === null) {
			papel = L.papel;
		}

		if (vencimento === null) {
			if (tipo_papel(papel) != tipo_papel.OPCAO) {
				return 1;
			}
			vencimento = vencimento_opcao(papel, L.data);
		}

		saldo += L.qtde;
		if (saldo === 0) {
			// posição zerada, esquece o vencimento
			vencimento = null;
		}

		return null;
	});

	if (vencimento !== null) {
		if (vencimento < now) {
			tickets.push(new Ticket(0, 0, now, 2359, "@EXP", 0, 4, 
				"Opção "+papel+" com saldo expirado após "+dtoc(vencimento), null));
		}
	}
});


/*jslint browser: true, white: false */

function importar_fase2(txt)
{
	if (! main.vazio()) {
		if (prompt("A importação eliminará os lançamentos velhos.\nDigite SIM se você realmente deseja fazer isto", "Não") !== "SIM") {
			alert("Importação cancelada.");
		$('#importando').hide();
			return;
		}
	}

	var res;

	try {
		res = $.secureEvalJSON(txt);
	} catch (exp) {
		res = null;
	}

	if (! res) {
		alert("Conteúdo inválido. Verifique se você colou o conteúdo completo do arquivo de cópia.");
	} else {
		main.ler_storage2(res);
		alert("Conteúdo importado com sucesso.");
	}
	$('#importando').hide();
}

function importar_fileapi_fase2(evt)
{
	var txt = evt.target.result;
	if (! txt) {
		$('#importando').hide();
		alert("Nenhum dado lido do arquivo.");
		return;
	}

	importar_fase2(txt);
}

/*
function importar_legacy()
{
	var arquivo = document.fimport.arquivo;
	var ok = 0;
	if (! arquivo.files) {
		alert("Este navegador não suporta leitura direta de arquivos. Siga a instrução genérica para qualquer browser.");
		return;
	}

	if (! arquivo.files.item(0)) {
		alert("Escolha o arquivo a ser importado.");
		return;
	}

	if (! arquivo.files.item(0).getAsBinary) {
		alert("Este navegador não suporta leitura direta de arquivos. Siga a instrução genérica para qualquer browser, mais abaixo.");
		return;
	}

	var txt = arquivo.files.item(0).getAsBinary();
	if (! txt) {
		alert("Este navegador não suporta leitura direta de arquivos. Siga a instrução genérica para qualquer browser.");
		return;
	}

	$('#importando').show();
	importar_fase2(txt);
}
*/

function importar_fileapi(evt)
{
	if (! window.File || ! window.FileReader) {
		// importar_legacy();
		alert("Este navegador não suporta leitura direta de arquivos. Siga a instrução genérica para qualquer browser.");
		return;
	}

	var f = evt.target.files;

	if (f.length != 1) {
		return;
	}

		f = f[0];

	var reader = new FileReader();
	reader.onload = importar_fileapi_fase2;
	reader.readAsText(f);

	$('#importando').show();
}


Main_class.exportar_fase1 = function ()
{
	var lista_exp = [];
	var elancto;

	for (var k = 0; k < this.lanctos.length; ++k) {
		lista_exp.push(this.lanctos[k].copia_para_exportacao());
	}

	var txt = $.toJSON(lista_exp, 1) + "\r\n\r\n"; 
	document.fexport.txt.value = txt;

	main.gravar_storage();

};

Main_class.exportar_fase2 = function ()
{
	var lista_exp = [];
	var elancto;

	for (var k = 0; k < this.lanctos.length; ++k) {
		lista_exp.push(this.lanctos[k].copia_para_exportacao());
	}

	var txt = $.toJSON(lista_exp, 1) + "\r\n\r\n"; 
	var uri = "data:application/octet-stream," + encodeURIComponent(txt);
	// var newWindow = window.open(uri, 'TaxMan: dados salvos');
	window.open(uri);

	main.gravar_storage();
};
/*jslint browser: true, white: false */

var main;

var divlanctos, divposicao, divprocessamento;
var divnovidades;
var divinstrucoes;

var ANIM = 300;

var tela = {
		em_exibicao:    "Nenhum",
		em_modo:    "Nenhum", 
		err:        null,
		table_lanctos:  null,
		table_saldos:   null,
		saldos:     null,
		table_proc: null,
		addlanc:    null,
		addlanc_link:   null,
		divs:       [],
		links:      []
	   };

function mostrar_addlanc()
{
	tela.addlanc.show(ANIM);
	tela.addlanc_link.hide(ANIM);
}

function esconder_addlanc()
{
	tela.addlanc.hide(ANIM);
	tela.addlanc_link.show(ANIM);
}

var links_iniciais = ["instrucoes", "importar", "novidades"];

function exibir(elemento_exibicao, carga)
{
	var modo = "normal";

	if (carga) {
		if (main.vazio()) {
			mostrar_addlanc();
		} else {
			esconder_addlanc();
		}
	}

	if (main.vazio()) {
		modo = "vazio";
		if (links_iniciais.indexOf(elemento_exibicao) < 0) {
			elemento_exibicao = links_iniciais[0];
		}
	}

	if (elemento_exibicao == tela.em_exibicao && modo == tela.em_modo) {
		return;
	}

	// links

	for(var k in tela.links) {
		if (k == elemento_exibicao) {
			tela.links[elemento_exibicao].hide(ANIM);
		} else if (links_iniciais.indexOf(k) >= 0) {
			tela.links[k].show(ANIM);
		} else if (! main.vazio()) {
			tela.links[k].show(ANIM);
		} else {
			tela.links[k].hide();
		}
	}

	// tabs

	var div_esconder = tela.divs[tela.em_exibicao];
	var div_mostrar = tela.divs[elemento_exibicao];

	if (div_esconder) {
		div_esconder.hide(ANIM);
	}
	div_mostrar.show(ANIM);

	tela.em_exibicao = elemento_exibicao;
	tela.em_modo = modo;

	if (elemento_exibicao == "exportar") {
		// joga dados para dentro do formulário de exportação
		main.exportar_fase1();
	}
}

function exportar_plugin_instalado(e)
{
	// $("#firefox_semplugin").css('display', "none");
	// $("#firefox_complugin").css('display', "block");
}

function instalar_validador(campo, decs, signal)
{
	var fformat = function(txt) {
		if (txt.length <= 0) {
			return "";
		}
		var sig = "0";
		if (signal) {
			if (txt.indexOf("-") >= 0) {
				if (txt.lastIndexOf("-") != txt.indexOf("-")) {
					sig = "0";
				} else {
					sig = "-0";
				}
			}
			if (txt.indexOf("+") >= 0) {
				sig = "0";
			}
		}
		var v = parseInt(sig + txt.replace(/[^0-9]/g, ''), 10);
		return formatFinancial(v, decs);
	};

	var ffinal = function () {
		var txt = $(this).val();
		$(this).val(fformat(txt));
	};

	var fpartial = function () {
		var txt = $(this).val();
		if (signal && (txt == "-" || txt == "+")) {
			return;
		}
		$(this).val(fformat(txt));
	};

	campo.change(ffinal);
	campo.keyup(fpartial);
}

function init_tela()
{
	tela.table_lanctos = $("#lanctos > tbody:first");
	tela.table_saldos = $("#saldos > tbody:first");
	tela.saldos = [
			$("#saldo_l1"), 
			$("#saldo_l2"), 
			$("#saldo_l3"),
			$("#saldo_l4") 
			  ];
	tela.table_proc = $("#proc > tbody:first");
	tela.addlanc = $("#addlanc");
	tela.addlanc_link = $("#linkmostraaddlanc");

	// tela.notebook = $("#notebook");
	// tela.blanket = $("#blanket");

	var ids = ["lanctos", "posicao", "processamento", "novidades", "instrucoes", "importar", "exportar"];
	for(var k = 0; k < ids.length; ++k) {
		tela.links[ids[k]] = $("#link" + ids[k]);
		tela.divs[ids[k]] = $("#div" + ids[k]);
	}

	tela.err = $("#err");
}

function init_formulario()
{
	$('#form_data').datepicker({dateFormat: 'dd/mm/yy',
			 monthNames: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 
					  'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro',
					  'Novembro', 'Dezembro']});

	var h = new Date();
	h.setHours(10);
	h.setMinutes(0);
	$("#form_data").val(dtoc(jDtod(h)));
	$("#form_hora").val("10:00");
	$("#form_papel").val("VALE5");
	// $("#form_qtde").setValue(200);
	// $("#form_vlunit").setValue(30.05);
	// $("#form_crretagem").setValue(20);

	instalar_validador($("#form_qtde"), 0, true);
	instalar_validador($("#form_vlunit"), 2, false);
	instalar_validador($("#form_vltotal"), 2, false);

	instalar_validador($("#form_taxa_cblc_liq"), 2, false);
	instalar_validador($("#form_taxa_cblc_reg"), 2, false);
	instalar_validador($("#form_taxa_bovespa"), 2, false);
	instalar_validador($("#form_taxa_corretagem"), 2, false);
	instalar_validador($("#form_taxa_iss"), 2, false);
	instalar_validador($("#form_taxa_irrf"), 2, false);
	instalar_validador($("#form_taxa_outras"), 2, false);
}

function init_fileapi()
{
	$("#arquivo").change( function (evt) { importar_fileapi(evt); } );
}
/*jslint browser: true, white: false */

jQuery.jStore.ready(function(engine) {
	jQuery.jStore.flashReady(function(){
		engine.ready(function() {
			main.ler_storage(this);
		});
	});
	engine.ready(function() {
		main.ler_storage(this);
	});
}); 

$(document).ready(function() {
	init_tela();
	init_formulario();
	init_fileapi();
	main = new Main(); // singleton
	jQuery.extend(jQuery.jStore.defaults,
			  {project: 'taxman',
			   flash: 'jStore.Flash.html'});
	jQuery.jStore.load();
});

