/**
 * Created by tom on 06.09.17.
 */
function Ajax(props) {

	// props validation
	if(!props || !props.url) {
		console.error("Ajax Error: Undefined url property.");
		return false;
	}

	// set props
	this.url = props.url;
	this.method = props.method || "POST";
	this.data = props.data || {};
	this.cache = props.cache !== undefined ? props.cache : false;
	this.dataType = props.dataType || "JSON";
	this.contentType = (props.contentType !== undefined) ? props.contentType : "application/x-www-form-urlencoded; charset=UTF-8";
	this.processData = (props.processData !== undefined) ? props.processData : true;
	this.ok = props.ok || null;
	this.error = props.error || null;
	this.complete = props.complete || null;
	this.retryCount = props.retryCount || 5;
	this.retrySleep = props.retrySleep || 5;
	this.params = props.params || [];

	this.printReports = function(reports) {
		for(var i in reports) {
			var report = reports[i];
			switch(report.level) {
				case "fatal" :
					console.error(report.data);
					break;
				case "warn" :
					console.warn(report.data);
					break;
				default :
					console.log(report.data);
					break;
			}
		}
	};

	this.processResult = function(result) {
		switch(result.status) {
			case "OK" :
				if(this.ok) this.ok(result.response, this.params);
				if(result.printableReports) this.printReports(result.printableReports);
				if(this.complete) this.complete();
				return;
			case "ERROR" :
				if(this.error) this.error(result.message, result.code, this.params);
				if(result.printableReports) this.printReports(result.printableReports);
				if(this.complete) this.complete();
				return;
			case "SEND-AGAIN" :
				if(this.retryCount-- <= 0 && this.error) {
					PHO.dispatchEvent(this.error, {code: null, message: "Chyba serveru."});
				} else {
					setTimeout(function() {
						this.send();
					}.bind(this), this.retrySleep * 1000);
				}
				if(this.complete) this.complete();
				return;
			default :
				if(this.complete) this.complete();
				console.error("Ajax Error: Unknown status.");
		}
	};

	this.send = function() {

		$.ajax({
			url: this.url,
			method: this.method,
			data: this.data,
			async: true,
			cache: this.cache,
			dataType: this.dataType,
			contentType: this.contentType,
			processData: this.processData,
			success: function(result) {
				this.processResult(result);
			}.bind(this),
			error: function(xhr, status, err) {
				if(this.error) this.error("Chyba serveru.", null);
				if(this.complete) this.complete();
				console.log(xhr, status, err);
				// console.error("Ajax Error: "+status+" ("+err+").");
			}.bind(this)
		});
	};
}

/**
 * Created by tom on 12.06.17.
 */
(function () {
	if(typeof window.CustomEvent === "function") return false; //If not IE

	function CustomEvent(event, params) {
		params = params || {bubbles: false, cancelable: false, detail: undefined};
		var evt = document.createEvent("CustomEvent");
		evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
		return evt;
	}

	CustomEvent.prototype = window.Event.prototype;

	window.CustomEvent = CustomEvent;
})();
/**
 * Created by tom on 06.09.17.
 */
window.Modal = {

	stack: [],

	push: function(name) {
		Modal.stack.push(name);
	},

	pop: function() {
		if(Modal.stack.length <= 0) return;
		Modal.show(Modal.stack.pop());
	},

	show: function(name) {
		var modal = $("#modal .modal");
		var scrollPosition = modal.length ? modal.scrollTop() : null;

		$("body").addClass("modal-opened");

		PHOState.activeModal = name;
		render("modal");


		if(scrollPosition) $("#modal .modal").scrollTop(scrollPosition);
	},

	hide: function() {
		PHO.dispatchEvent("modal-hide", {name: PHOState.activeModal});
		PHOState.activeModal = null;
		$("body").removeClass("modal-opened");
		render("modal");
		Modal.pop();
	}
};
window.PHOTinyMCE = {
    load: function(selector, data) {
        $.getScript('/static/js/core/external/tinymce/js/tinymce/tinymce.min.js', function () {
            tinymce.baseURL = "/static/js/core/external/tinymce/js/tinymce";
            tinymce.suffix = '.min';
            if(selector){
                PHOTinyMCE.start(selector, data);
            }
        });
    },
    start: function(selector, data) {
        PHOState.editingField = {
            selector: selector,
            data: data
        };
        var settings = PHOTinyMCE.getSettings(selector);
        if(typeof tinymce == "undefined") {
            PHOTinyMCE.load(selector, data)
        } else {

            tinymce.EditorManager.execCommand('mceRemoveEditor',true, selector);
            tinymce.EditorManager.init(settings);
            tinymce.EditorManager.execCommand('mceAddEditor',true, selector);
        }
    },
    stop: function(selector) {
        PHOState.editingField = null;
        tinymce.EditorManager.execCommand('mceRemoveEditor',true, selector);
    },
    getSettings: function(selector, data) {
        var settings = {
            selector: "#" + selector,
            theme: "silver",
            inline: true,
            plugins: [
                "image imagetools lists link media"
            ],
            image_dimensions:false,
            menubar: false,
            style_formats: [
                {title: 'Nadpis 2', block: 'h2'},
                {title: 'Nadpis 3', block: 'h3'},
                {title: 'Odstavec', block: 'p'},
            ],
            extended_valid_elements: 'a[href|target=_blank]',
            block_formats: 'Odstavec=p;Nadpis 2=h2;Nadpis 3=h3',
            contextmenu: "link table",
            table_default_styles: {
                'width': "100%",
                'border': "0",
                'border-collapse' : "collapse",
            },
            table_cell_class_list: [
                {title: 'None', value: ''},
                {title: 'time', value: 'time'}
            ],
            toolbar: "styleselect | bold italic underline forecolor removeformat | alignleft aligncenter alignright | table tableCellProps | bullist numlist | code | link image media",
            entity_encoding: "raw",
            auto_focus: true,
            language: "cs",
            color_map: [
                "0098e4", "mainColor",
                "ff698f", "pink",
                "00bc9c", "green",
                "9673d1", "violet",
                "e3545c", "red",
                "e7faff", "lightBlue",
                "9f9f9f", "noticeText",
                "bfbfbf", "greyBorder",
                "E4E4E4", "greyBackground",
            ],
            custom_colors: false,

            images_upload_handler: function (blobInfo, success, failure) {
                // var itemId = PHOData[PHOData.productType].id;
                // FIXME zkusit ajaxovy obal - hazi to chybu Illegal invocation
                // var data = {
                //     file: [
                //         blobInfo.blob(),
                //         blobInfo.filename()
                //     ],
                //     itemType: PHOState.editingField.data.itemType,
                //     itemId: PHOState.editingField.data.itemId
                // };
                // formData = new FormData();
                // formData.append('file', blobInfo.blob(), blobInfo.filename());
                // formData.append('itemType', PHOState.editingField.data.type);
                // formData.append('itemId', PHOState.editingField.data.itemId);
                // var ajax = new Ajax({url: "/ajax//tinymce/image-upload",
                // 	data: formData,
                // 	ok: function(response) {
                //         alert("juch");
                // 	},
                // 	error: function(message){
                // 		failure(message);
                // 	}
                // });
                // ajax.send();
                var xhr, formData;

                xhr = new XMLHttpRequest();
                xhr.withCredentials = false;
                xhr.open('POST', '/ajax/tinymce/image-upload');

                xhr.onload = function () {
                    var json;

                    if (xhr.status != 200) {
                        failure('HTTP Error: ' + xhr.status);
                        return;
                    }

                    json = JSON.parse(xhr.responseText);

                    if (!json || typeof json.location != 'string') {
                        failure('Invalid JSON: ' + xhr.responseText);
                        return;
                    }

                    success(json.location);
                };

                formData = new FormData();
                formData.append('file', blobInfo.blob(), blobInfo.filename());
                formData.append('itemType', PHOState.editingField.data.itemType);
                formData.append('itemId', PHOState.editingField.data.itemId);
                formData.append('CSRFToken', PHOData.CSRFToken);

                xhr.send(formData);
            },
            // file_picker_types: 'image',
            /* and here's our custom image picker*/
            file_picker_callback: function (cb, value, meta) {
                var input = document.createElement('input');
                input.setAttribute('type', 'file');
                input.setAttribute('accept', '*');

                /*
                  Note: In modern browsers input[type="file"] is functional without
                  even adding it to the DOM, but that might not be the case in some older
                  or quirky browsers like IE, so you might want to add it to the DOM
                  just in case, and visually hide it. And do not forget do remove it
                  once you do not need it anymore.
                */

                input.onchange = function () {
                    var file = this.files[0];

                    var reader = new FileReader();

                    var itemId = PHOState.editingField;

                    reader.onload = function () {
                        /*
                          Note: Now we need to register the blob in TinyMCEs image blob
                          registry. In the next release this part hopefully won't be
                          necessary, as we are looking to handle it internally.
                        */
                        var id = 'blobid' + (new Date()).getTime();
                        var blobCache = tinymce.activeEditor.editorUpload.blobCache;
                        var base64 = reader.result.split(',')[1];
                        var blobInfo = blobCache.create(file.name, file, base64);

                        blobCache.add(blobInfo);

                        var xhr, formData;

                        xhr = new XMLHttpRequest();
                        xhr.withCredentials = false;
                        xhr.open('POST', '/ajax/tinymce/file-upload');

                        xhr.onload = function () {
                            var json;

                            if (xhr.status != 200) {
                                alert('HTTP Error: ' + xhr.status);
                                return;
                            }

                            json = JSON.parse(xhr.responseText);

                            if (!json || typeof json.location != 'string') {
                                alert('Invalid JSON: ' + xhr.responseText);
                                return;
                            }
                            /* call the callback and populate the Title field with the file name */
                            cb(json.location, {title: file.name});
                            // success(json.location);
                        };


                        formData = new FormData();
                        formData.append('file', blobInfo.blob(), file.name);
                        formData.append('itemGroup', "htmlField");
                        formData.append('itemId', itemId);

                        xhr.send(formData);

                    };
                    reader.readAsDataURL(file);
                };

                input.click();
            },
        };

        return settings;
    },
};
/**
 * Created by tom on 16.09.17.
 */
function FormItem(element) {
	this.element = $(element);
	this.tag = element.tagName;
	this.type = $(element).attr("type");
	this.id = $(element).attr("id");
	this.name = $(element).attr("name");
	this.hasFocus = $(element).is(":focus");
	this.isChecked = $(element).is(":checked");
	this.anchorOffset = null;
	this.focusOffset = null;

	if($(element).is("[contenteditable]")) {
		range = window.getSelection();
		if(range) {
			this.anchorOffset = range.anchorOffset;
			this.focusOffset = range.focusOffset;
		}
	}

}
/**
 * Created by tom on 28.08.17.
 */
Template = function(id, tplParams, concatenate) {

	this.id = id;
	this.tplParams = tplParams || [];
	this.concatenate = concatenate || false;

	var result = id.split(".");
	this.templateId = result[0];

	if(!PHOTemplates[this.templateId]) console.error("Unknown template ("+this.templateId+")");

	this.template = PHOTemplates[this.templateId];

	this.offset = 0;
	this.compiledFile = "";
	this.keywordStack = [];
	this.after = [];
	this.childOffset = null;
	this.keywords = [
		"if", "else", "elseif", "for", "while", "do", "foreach",
		"switch", "case", "default", "break", "continue", "set",
		"include", "parent", "child", "variables", "params", "load",
		"js"
	];
	this.ignoreSpaces = false;
	this.formItems = [];

	this.getToken = function(code) {
		if(code[0] == "/") return "/";
		var result = code.match(/^\s*([a-zA-Z0-9_]+)/);
		if(result && this.keywords.indexOf(result[1]) >= 0) return result[1];
		return null;
	};

	this.fixSyntax = function (code) {
		code = code.replace(/@\$/g, "\$")
			       .replace(/\-\>\$([a-zA-Z_][a-zA-Z0-9_]*)/g, "[$1]")
				   .replace(/\$/g, "")
				   .replace(/\-\>/g, ".")
			       .replace(/~/g, "+");
		return code;
	};

	this.translateForeach = function(code) {

		code = this.fixSyntax(code);

		var m = code.match(/\s*foreach\(([^\s]+)\s+as\s+(([^\s]+)\s+\=\>\s+)?([^\s]+)\)/);

		var items = m[1];
		var key = m[3];
		var value = m[4];

		code = "for(i in Object.keys("+items+")) {";
		code += "var keys = Object.keys("+items+");";
		if(key) code += "var "+key+" = keys[i];";
		code += "var "+value+" = "+items+"[keys[i]];";

		return code;
	};

	this.parseParams = function(string) {

		var pos = 0;
		var state = "s1";
		var params = [];
		var token = "";

		string = string.split("");
		for(i in string) {
			var ch = string[i];

			switch(state) {
				case "s1" :
					if(ch.match(/\s/)) break;
					token = ch;
					state = ch == "\"" ? "s2" : "s3";
					break;
				case "s2" :
					token += ch;
					if(ch == "\"") {
						params.push(token);
						token = "";
						state = "s1";
					}
					break;
				case "s3" :
					if(!ch.match(/\s/)) {
						token += ch;
					} else {
						params.push(token);
						token = "";
						state = "s1";
					}
					break;
			}
		}

		if(token.length) params.push(token);

		return params;
	};

	this.translate = function(code) {

		var token = this.getToken(code);

		switch(token) {
			case "/" : // block closing slash
				var keyword = this.keywordStack.pop();
				if(keyword == "parent") {
					var after = this.after.pop();
					return after;
				} else if(keyword == "js") {
					return "";
				} else {
					return "}";
				}
			case "if" :
			case "for" :
			case "while" :
				// pushing token at the top of keyword stack
				this.keywordStack.push(token);
				code = code+" {";
				break;
			case "foreach" :
				this.keywordStack.push(token);
				code = this.translateForeach(code);
				break;
			case "else" :
				// closing last and opening next statement
				code = "} "+code+" {";
				break;
			case "elseif" :
				code = "} "+code.replace("elseif", "else if")+" {";
				break;
			case "break" :
			case "continue" :
				// php break or continue statement
				return code + ";";
			case "set" :
				// special keyword for assinging variables in template
				var m = code.match(/^set\s+([^\s]+)\s+(.*)/);
				if(!m) {
					console.error("SYNTAX ERROR (set)");
					return "";
				}
				code = "var "+m[1]+" = "+m[2]+";";
				break;
			case "include" :
				// template include
				var m = code.match(/^include\s+[\"'](.*\/)?([^\"']*)[\"']((\s+[^\s]+)*)/);
				if(!m) {
					console.error("SYNTAX ERROR (include)");
					return "";
				}

				var compiledFile = 'var tplParams = [];';
				if(m[3]) {
					var params = this.parseParams(m[3]);

					for(i in params) {
						compiledFile += 'tplParams.push('+this.fixSyntax(params[i])+');';
					}
				}

				template = new Template(m[2], [], true);

				compiledFile += template.compile();
				compiledFile += 'delete tplParams;';
				return compiledFile;
			case "parent" :
				/*
				 *	Special keyword that is used when we need to put
				 *	current template into another (parent) template.
				 *	Current template is put to position of "child" keyword
				 *	that parent template must contain.
				 */
				var m = code.match(/^parent\s+[\"'](.*\/)?([^\"']*)[\"']((\s+[^\s]+)*)/);
				if(!m) {
					console.error("SYNTAX ERROR (parent)");
					return "";
				}

				var template = new Template(m[2], [], true);
				var result = template.compile();
				var before = result[0];
				var after = result[1];

				var compiledFile = 'var tplParams = [];';
				if(m[3]) {
					var params = this.parseParams(m[3]);
					for(i in params) {
						compiledFile += 'tplParams.push('+this.fixSyntax(params[i])+');';
					}

					before = compiledFile+before;
					after += 'delete tplParams;';
				}

				if(this.compiledFile == "var phoX = '';") {
					before = "";
					after= "";
				}

				this.after.push(after);
				this.keywordStack.push(token);
				return before;
			case "child" :
				// keyword that defines template position in parent template
				this.childOffset = this.compiledFile.length - 1;
				return "";
			case "variables" :
				return "";
			case "params" :
				var m = code.match(/^params(\s+.+)/);
				if(!m) {
					console.error("SYNTAX ERROR (parent)");
					return "";
				}
				var compiledFile = '';
				if(m[1]) {
					var params = m[1].trim().split(/\s+/);
					compiledFile += 'if(tplParams.length != '+params.length+') console.error("Insufficient parameters for template \\"'+this.templateId+'\\"");';
					for(i in params) {
						compiledFile += 'var '+params[i]+' = tplParams['+i+'];';
					}
				}
				compiledFile += 'delete tplParams;';
				return compiledFile;
			case "load" :
				code = this.fixSyntax(code);
				var m = code.match(/^load(\s+.+\s+.+)/);
				if(!m) {
					console.error("SYNTAX ERROR (load)");
					return "";
				}
				var js = '';
				if(m[1]) {
					var results = m[1].trim().split(/\s+/);
					var id = results[0];
					var tplName = results[1];

					var jsParams = '';

					js = jsParams+' loadTemplate(\"\'+'+id+'+\'\",\"\'+'+tplName+'+\'\"';

					for(i=2 ; i<results.length ; i++) {
						js += ',\'+JSON.stringify('+results[i]+')+\'';
					}


					js += ');';
				}
				js = "phoX +='<script>"+js+"</script>';";
				return js;
			case "js" :
				this.keywordStack.push(token);
				return "";
			default :
				// if any keyword is matched, the code is printed to stdout
				code = "phoX +=escapeString("+code+");";

		}

		return this.fixSyntax(code);
	};

	this.compile = function() {

		if(!this.concatenate) {
			this.compiledFile = "var phoX = '";
		} else {
			this.compiledFile += "phoX += '";
		}

		for(this.offset=0 ; this.offset<this.template.length ; this.offset++) {

			var ch = this.template[this.offset];
			var nextChar = null;
			if(this.offset + 1 < this.template.length) nextChar = this.template[this.offset+1];
			if(ch == "\\" && nextChar == "{") {
				this.compiledFile += "{";
				this.offset++;
			} else if(ch == "\\" && nextChar == "}") {
				this.compiledFile += "}";
				this.offset++;
			} else if(ch == "\n" || ch == "\r") {
				this.compiledFile += "";
			} else if(ch == "{") {

				this.compiledFile += "';";
				for(var j=this.offset+1 ; j<this.template.length ; j++) {
					if(this.template[j] == "}") {
						var code = this.template.substring(this.offset+1, j);
						var translation = this.translate(code);
						this.compiledFile += translation;
						this.offset = j;
						break;
					}
				}
				this.compiledFile += "phoX += '";

			} else {
				this.compiledFile += ch;
			}
		}

		if(this.childOffset !== null) {
			before = this.compiledFile.substring(0, this.childOffset + 1);
			after = this.compiledFile.substring(this.childOffset)+"';";
			return [before, after];
		}

		this.compiledFile += "';";

		return this.compiledFile;
	};

	this.exportFormItems = function() {
		this.formItems = [];

		$("input, textarea, select, div[contenteditable=true]").each(function(index, element) {
			this.formItems.push(new FormItem(element));
		}.bind(this));
	};

	this.importFormItems = function() {
		for(i in this.formItems) {
			var formItem = this.formItems[i];
			var selector = formItem.tag;
			if(formItem.id) selector += "[id='"+formItem.id+"']";
			if(formItem.name) selector += "[name='"+formItem.name+"']";
			if(formItem.type == "radio") {
				var value = $(formItem.element).val();
				if(value.length) selector += "[value='"+value+"']";
			}
			if($(selector).length != 1) continue;
			$(selector).replaceWith($(formItem.element));
			if(formItem.hasFocus) $(selector).focus();
			if(formItem.isChecked) $(selector).prop("checked", true);
			if(formItem.focusOffset && formItem.anchorOffset) {
				// FIXME - does not work with range (caret position only)
				PHO.setCaretPosition($(selector).get(0), formItem.focusOffset);
			}
		}
		this.formItems = [];
	};

	this.viewDidAppear = function() {
		if(!PHOControllers[this.id]) return;
		if(!PHOControllers[this.id].viewDidAppear) return;
		PHOControllers[this.id].viewDidAppear();
	};

	this.render = function() {
		this.compile();
		eval(this.compiledFile);

		if(PHOTemplates && PHOControllers && PHOControllers[this.id] && PHOControllers[this.id].viewWillAppear) {
			PHOControllers[this.id].viewWillAppear();
		}

		this.exportFormItems();

		// FIXME - čistý js nefungal
		var node = $("#"+this.id.replace(".", "\\."));
		if(node.length <= 0) return;

		// SCROLL POSITIONS
		scrolls = {};
		$(node).parent().find("[data-rsp]").each(function() {
			var rsp = $(this).attr("data-rsp");
			if(!rsp) return;
			scrolls[rsp] = {
				x: $(this).scrollLeft(),
				y: $(this).scrollTop()
			};
		});

		var newNode = $(phoX);
		node.replaceWith(newNode);

		for(rsp in scrolls) {
			var node = $("[data-rsp='" + rsp + "']");
			if (node.length <= 0) return;
			var scroll = scrolls[rsp];
			node.scrollLeft(scroll.x);
			node.scrollTop(scroll.y);
		}

		// RENDER EVENT

		$(node).parent().find("[render-event]").each(function() {
			PHO.dispatchEvent("component-rendered", $(this));
		});

		// FIXME nekdy to vykopat do funkce

		// var node = document.getElementById(id);
		// node.outerHTML = phoX;

		// view did appear
		this.viewDidAppear();

		this.importFormItems();
	};
}

Template.escapeStrings = true;

/**
 * Created by tom on 11.09.17.
 */
window.Templates = {

	loaded: function(response, params) {

		var keys = Object.keys(response.templates);
		for(i in keys) {
			var tplName = keys[i];
			if(PHOTemplates[tplName]) continue;
			PHOTemplates[tplName] = response.templates[tplName];
			Templates.bindController(tplName);
			Templates.initController(tplName);
		}

		var template = new Template(params.tplName, params.tplParams);

		template.render();
	},

	onloadError: function (response) {
		console.error(response.message);
	},

	bindController: function(tplName) {
		var controller = PHOControllers[tplName];
		if(!controller) return;
		if(controller.bind) controller.bind();
	},

	bind: function() {
		if(!PHOTemplates) return;
		if(!PHOControllers) return;
		for(tplName in PHOTemplates) {
			Templates.bindController(tplName);
		}
	},

	initController: function(tplName) {
		var controller = PHOControllers[tplName];
		if(!controller) return;
		if(controller.init) controller.init();
	},

	init: function() {
		if(!PHOTemplates) return;
		if(!PHOControllers) return;
		for(tplName in PHOTemplates) {
			Templates.initController(tplName);
		}
	},

	controllerReady: function(tplName) {
		var controller = PHOControllers[tplName];
		if(!controller) return;
		if(controller.ready) controller.ready();
	},

	ready: function() {
		if(!PHOTemplates) return;
		if(!PHOControllers) return;
		for(tplName in PHOTemplates) {
			Templates.controllerReady(tplName);
		}
	},

	onLoadScript: function() {
		if(PHOState.onLoadScript) eval(PHOState.onLoadScript);
	}
};

document.addEventListener("DOMContentLoaded", function() {
	Templates.bind();
	Templates.init();
	Templates.ready();
	Templates.onLoadScript();
});


function TmpFileUploader(id, data) {

	this.UPLOAD_REPEATS_COUNT = 10;
	this.UPLOAD_REPEAT = 10000;
	this.AJAX_RETRY_TIME = 3000;

	this.file = {
		id: id,
		data: data
	};
	this.startTime;
	this.minChunkSize = 1024 * 500; // 100 KB
	this.maxChunkSize = 1024 * 1024 * 10; // 10 MB
	this.chunkSize = this.minChunkSize;
	this.curChunkUplSize = 0;
	this.uploadedSize = 0;
	this.uploadPaused = false;
	this.uploadAborted = false;

	this.abort = function() {
		this.uploadAborted = true;
	};

	this.updateChunkSize = function() {
		var now = new Date().getTime();
		var speed = this.uploadedSize / ((now - this.startTime) / 1000);

		if(speed > this.maxChunkSize) {
			this.chunkSize = this.maxChunkSize;
		} else if(speed < this.minChunkSize) {
			this.chunkSize = this.minChunkSize;
		} else {
			this.chunkSize = speed.toFixed(0);
		}
	};

	this.fileSlice = function() {

		var chunkSize = Math.min(this.chunkSize, this.file.data.size - this.uploadedSize);
		if(this.file.data.slice) {
			return {size: chunkSize, data: this.file.data.slice(this.uploadedSize, this.uploadedSize + chunkSize)};
		}
		if(this.file.data.webkitSlice) {
			return {size: chunkSize, data: this.file.data.webkitSlice(this.uploadedSize, this.uploadedSize + chunkSize)};
		}
		if(this.file.data.mozSlice) {
			return {size: chunkSize, data: this.file.data.mozSlice(this.uploadedSize, this.uploadedSize + chunkSize)};
		}
		return false;
	};

	this.getRealUploadedSize = function() {
		return this.uploadedSize + this.curChunkUplSize;
	};

	this.getProgress = function() {
		return this.getRealUploadedSize() / this.file.data.size;
	};

	this.updateProgress = function(e) {
		if(e.total < 100) return false;
		if(e.lengthComputable) {
			this.curChunkUplSize = e.loaded;
		}
		PHO.dispatchEvent("tmp-file-uploader", {id: this.file.id, state: "progress-changed"});
	};

	this.closeFile = function() {

		(new Ajax({
			url: "/pho-ajax/tmp-file/close",
			data: {
				id: this.file.id,
				uploadedSize: this.uploadedSize
			},
			ok: function() {
				PHO.dispatchEvent("tmp-file-uploader", {id: this.file.id, state: "done"});
			}.bind(this),
			error: function(message) {
				PHO.dispatchEvent("tmp-file-uploader", {id: this.file.id, state: "error", message: message});
			}.bind(this)
		})).send();
	};

	this.uploadFileChunked = function() {

		var xhr = new XMLHttpRequest();

		if(this.uploadAborted) return false;
		if(this.uploadPaused) return true;
		if(!xhr) return false;

		xhr.addEventListener("progress", this.updateProgress.bind(this), false);
		xhr.upload.addEventListener("progress", this.updateProgress.bind(this), false);

		xhr.onreadystatechange = function() {
			if(xhr.readyState !== 4) {
				return false;
			}
			if(xhr.status !== 200) {
				// try again
				setTimeout(function() {
					if(this.UPLOAD_REPEATS_COUNT-- <= 0) return false;
					this.uploadFileChunked();
				}.bind(this), this.UPLOAD_REPEAT);
				return false;
			}

			this.UPLOAD_REPEATS_COUNT = 10; // FIXME

			try {
				var xhrResult = JSON.parse(xhr.responseText);
			} catch(e) {
				setTimeout(function() {
					this.uploadFileChunked();
				}.bind(this), this.AJAX_RETRY_TIME);
				return false;
			}

			if(xhrResult.status !== "OK") {
				setTimeout(function() {
					this.uploadFileChunked();
				}.bind(this), this.AJAX_RETRY_TIME);
				return false;
			} else if(xhrResult.response.size) {
				this.uploadedSize = parseInt(xhrResult.response.size);
				// update chunk size, if sent chunk is not the last one of the file
				if(this.uploadedSize >= this.minChunkSize) {
					this.updateChunkSize();
				}
			}

			if(this.uploadedSize < this.file.data.size) {
				this.uploadFileChunked();
			} else {
				this.closeFile();
			}

			return true;
		}.bind(this);

		var chunk = this.fileSlice();

		if(!chunk) {
			return false;
		}

		var url = "/pho-ajax/tmp-file/append";
		url += "?id=" + this.file.id;
		url += "&uploadedSize=" + this.uploadedSize;
		url += "&chunkSize=" + chunk.size;

		xhr.open("POST", url, true);
		xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
		xhr.send(chunk.data);
		return true;
	};

	this.upload = function() {
		/* start_time */
		this.startTime = Math.round(new Date().getTime());
		/* current upload offset (in bytes) */
		this.uploadedSize = 0;
		/* upload paused */
		this.uploadPaused = false;
		/* upload aborted */
		this.uploadAborted = false;
		/* upload file chunked */
		this.uploadFileChunked();

	};
}

