function MyThoughts(asGlobals)
{
	self = this;
	this.consts = asGlobals.consts;
	this.consts.hash_sep = '-';
	this.consts.default_page = 'home';
	this.consts.title = 'My Thoughts';
	this.consts.root = location.protocol+'//'+location.host+location.pathname;
	
	this.init = function()
	{
		//Variables & constants from php
		self.vars('tmp', 'object');
		self.vars('page', 'string');
		self.vars('template', 'boolean');
		self.updateVars(asGlobals.vars);
		
		//page elem
		self.elem = {};
		self.elem.$Container = $('#container');
		
		//on window resize
		$(window).resize(self.onResize).resize();
		
		//Hash management
		self.resetTmpFunctions();
		$(window)
			.bind('hashchange', self.onHashChange)
			.trigger('hashchange');
	};
	
	this.updateVars = function(asVars)
	{
		$.each(asVars, function(sKey, oValue){self.vars(sKey, oValue)});
	};
	
	/* Scrollbar */
	
	/*this.scrollbar = function(sPos)
	{
		var $Cv = $('#main');
		if(!self.vars('mobile'))
		{
			if(typeof self.vars('scrollbar') === 'undefined')
			{
				$Cv.tinyscrollbar({axis:'y', thumbSizeMin:'20', thumbSize:'20'});
				self.vars('scrollbar', $Cv.data("plugin_tinyscrollbar"));
			}
			else self.vars('scrollbar').update(sPos);
		}
		else $Cv.unbind("tinyscrollbar");
	}*/
	
	/* Menu */
	
	this.initMenu = function()
	{
		self.elem.$Menu.show('fast');
		
		//Thoughts on side menu
		self.updateSideMenu();
	};
	
	this.updateSideMenu = function() {
		Tools.ajax(
			'thoughts',
			function(asData){
				self.elem.$Side.find('.tag:not(.write)').remove();
				$.each(asData, function(iKey, asThought) {
					$Tile = $('<div>', {'class': 'tag'}).appendTo(self.elem.$Side);
					var $Link = $('<a>', {'href': '#read-'+asThought.id_thought}).html(asThought.created_f.replace(' ', '<br />')).appendTo($Tile);
				});
				self.elem.$Side.slideDown('fast', self.setSideElemVisibility);
			}
		);
	};
	
	this.setSideElemVisibility =  function() {
		if(self.elem.$Side) {
			var iHeight = 0;
			var iMaxHeight = self.elem.$Side.height();
			self.elem.$Side.children().each(function(){
				$(this).toggle(iMaxHeight - iHeight > 0);
				iHeight += $(this).outerHeight(true);
			});
		}
	};
	
	/* Events */
	
	this.onResize = function()
	{
		self.vars('mobile', $('body').css('min-width')=='120px');
		self.setSideElemVisibility();
		//self.scrollbar();
	};
	
	this.onHashChange = function()
	{
		var asHash = self.getHash();
		self.switchPage(asHash);
	};
	
	this.resetTmpFunctions = function()
	{
		self.pageInit = function(asHash, bFirstPage){console.log('no init for the page: '+asHash.page)};
		self.onSamePageMove = function(asHash){return false};
		self.onQuitPage = function(){return true};
		self.onFeedback = function(sType, sMsg){Tools.feedback(sType, sMsg);};
	};
	
	this.feedback = function(sType, sMsg) {
		self.onFeedback(sType, sMsg);
	};

	/* Hash Handling */
	
	this.getHash = function()
	{
		var sHash = self.hash();
		var asHash = sHash.split(self.consts.hash_sep);
		var sPage = asHash.shift() || '';
		return {hash:sHash, page:sPage, items:asHash};
	};
	
	this.setHash = function(sPage, asItems, bReboot)
	{
		bReboot = bReboot || false;
		sPage = sPage || '';
		asItems = asItems || [];
		if(typeof asItems == 'string') asItems = [asItems];
		if(sPage != '')
		{
			var sItems = (asItems.length > 0)?self.consts.hash_sep+asItems.join(self.consts.hash_sep):'';
			self.hash(sPage+sItems, bReboot);
		}
	};
	
	this.hash = function(hash, bReboot)
	{
		bReboot = bReboot || false;
		if(!hash) return window.location.hash.slice(1);
		else window.location.hash = '#'+hash;
		
		if(bReboot) location.reload();
	};
	
	this.getVars = function(fOnSuccess)
	{
		fOnSuccess = fOnSuccess || function(){};
		getInfo
		(
			'vars', 
			function(asData)
			{
				self.updateVars(asData.vars);
				fOnSuccess();
			},
			{}
		);
	};
	
	/* Page Switching */
	
	this.loadHome = function(asData) {
		self.vars('log_in', asData.log_in);
		self.vars('id', asData.id);
		
		if(self.vars('log_in')) {
			//Setup menu
			self.initMenu();
			
			//Swap to default page
			self.setHash('write');
		}
	};
	
	this.getActionLink = function(sAction, oVars)
	{
		if(!oVars) oVars = {};
		sVars = '';
		for(i in oVars)
		{
			sVars += '&'+i+'='+oVars[i];
		}
		return self.consts.process_page+'?a='+sAction+sVars;
	};
	
	this.switchPage = function(asHash)
	{
		var sCurrPage = self.vars('page');
		var sNextPage = asHash.page;
		var bLoggedIn = self.vars('log_in');
		var sDefaultPage = bLoggedIn?'write':'logon';
		
		if(	asHash.hash == '' || 
			sNextPage == '' ||
			!bLoggedIn && sNextPage != sDefaultPage ||
			bLoggedIn && sNextPage == 'logon'
		)
		{
			self.setHash(sDefaultPage); //force first page
		}
		else
		{
			var bSamePage = (sCurrPage == sNextPage);
			if(self.onQuitPage(bSamePage) && !bSamePage || self.onSamePageMove(asHash))
			{
				//Delete tmp variables
				self.vars('tmp', {});
				
				//disable tmp functions
				self.resetTmpFunctions();
				
				//Officially a new page
				var bFirstPage = (sCurrPage == '');
				self.vars('page', sNextPage);
				
				//Update Page Title
				var sDetail = asHash.items[0] || '';
				document.title = self.consts.title+' - '+sNextPage+' '+sDetail;
				
				//Replacing DOM
				var $Dom = $(self.consts.pages[sNextPage]);
				if(bFirstPage)
				{
					self.elem.$Container.html($(self.consts.pages['template']));
					self.elem.$Main = self.elem.$Container.find('#main');
					
					self.elem.$Menu = self.elem.$Container.find('#menu');
					self.elem.$Side = self.elem.$Container.find('#side');
					if(self.vars.log_in) self.initMenu();
					
					self.splash(self.elem.$Main, $Dom, asHash, bFirstPage); //first page
				}
				else
				{
					self.elem.$Main.stop().fadeTo('fast', 0, function(){self.splash(self.elem.$Main, $Dom, asHash, bFirstPage);}); //Switching page
				}
			}
		}
	};
	
	this.splash = function($FadeInElem, $Dom, asHash, bFirstPage)
	{
		//Switch main content
		$FadeInElem.empty();
		$FadeInElem.html($Dom);

		//Page Bootstrap
		self.elem.$Main.removeClass('no_frame');
		
		//Show main
		$FadeInElem.fadeTo('fast', 1, function(){self.pageInit(asHash, bFirstPage);});
	};
	
	/* Variables Handling */
	
	this.vars = function(sVarName, oValue)
	{
		var asTypes = {boolean:false, string:'', integer:0, array:[], object:{}};
		var asVarName = (typeof sVarName == 'object')?sVarName:[sVarName];
		var sFirstVarName = asVarName[0];
		
		//Get, only name parameter
		if(typeof oValue == 'undefined')
		{
			return getElem(self.vars, asVarName);
		}
		//Init, name & type / default value
		else if(typeof oValue !== 'undefined' && typeof self.vars[sFirstVarName] === 'undefined')
		{
			self.vars[sFirstVarName] = (typeof asTypes[oValue] !== 'undefined')?asTypes[oValue]:oValue;
			return self.vars[sFirstVarName];
		}
		//Set, name & value
		else if(typeof oValue !== 'undefined' && typeof self.vars[sFirstVarName] !== 'undefined')
		{
			setElem(self.vars, asVarName, oValue);
			return getElem(self.vars, asVarName);
		}
		return null;
	};
	
	this.tmp = function(sVarName, oValue)
	{
		var asVarName = (typeof sVarName == 'object')?sVarName:[sVarName];
		asVarName.unshift('tmp');
		return self.vars(asVarName, oValue);
	};
}

class ThoughtDate extends Quill.import('blots/inline') {    
    static create(value) {
        let node = super.create();
        node.setAttribute('class', 'edi_header');
        return node;
    } 
}

class Editor {
	constructor(sContainerId, bReadOnly) {
		this.id = 0;
		this.page = 0;
		this.keystrokes = 0;
		this.line = false;
		this.readOnly = bReadOnly || false;

		//DOM Elements
		var sEditorId = 'edi'+Math.floor(Math.random() * 1000);
		this.$Container = $(sContainerId).addClass('editor').append(self.consts.pages['editor']);
		this.$Header = this.$Container.find('.edi_header');
		this.$EditorBox = this.$Container.find('.edi_container');
		this.$Editor = this.$Container.find('.edi_table').attr('id', sEditorId);
		this.$PrevBtn = $('.prev');
		this.$NextBtn = $('.next');
		
		this.onKeyStroke = function(){};

		//Date format
		ThoughtDate.blotName = 'thought_date';
		ThoughtDate.tagName = 'div';
		Quill.register(ThoughtDate);
		
		this.oQuill = new Quill('#'+sEditorId, {
			theme: 'bubble',
			placeholder: 'What\'s on your mind?',
			readOnly: bReadOnly,
			modules: {
				toolbar: [['bold', 'italic', 'underline'], [{ 'list': 'ordered'}, { 'list': 'bullet' }], ['clean']]
			}
		});

		//Key strokes & mouse Events
		this.$Editor.keydown((e) => {
			if($.inArray(e.which, [13, 37, 38, 39, 40]) != -1) this._onChange('', '', 'user', e);
			else if(e.which==33) this.$PrevBtn.click();
			else if(e.which==34) this.$NextBtn.click();
		});
		
		this.oQuill.on('text-change', (delta, oldDelta, sSource) => {this._onChange(delta, oldDelta, sSource);});
		
		$(window).mousewheel((turn, iDelta) => {
			var iNewPage = this.page + ((iDelta > 0)?-1:1);
			this.moveToPage(iNewPage);
			return false;
		});
		
		//Page buttons
		this.$PrevBtn.click(() => {this.moveToPage(this.page - 1);});
		this.$NextBtn.click(() => {this.moveToPage(this.page + 1);});
		
		this._postInit();
	}
	
	setHeader(sHeader) {
		this.$Editor.find('.edi_header').remove();
		if(this.readOnly) {
			this.oQuill.insertText(0, sHeader+"\n");
			this.oQuill.formatText(0, sHeader.length, 'thought_date', true);
		}
	}
	
	open(iThoughtId) {
		Tools.ajax(
			'load',
			(asData) => {
				this.id = asData.id;
				this.oQuill.setContents(asData.ops);
				this.setHeader('Thoughts on '+asData.created_f);
				this._postInit();
			},
			{id: iThoughtId}
		);
	}

	_setPageHeight() {
		var iHeight = this.$EditorBox.height();
		var iLineHeight = parseInt(this.$Editor.find('p').css('line-height'));
		var iMaxHeight = Math.floor(iHeight / iLineHeight) * iLineHeight;
		this.$EditorBox.height(iMaxHeight+'px');
		
		this.lineHeight = iLineHeight;
		this.pageHeight = iMaxHeight;
	}
	
	_postInit(iPage) {
		iPage = iPage || 0;
		this._setPageHeight();
		this.moveToPage(iPage);
		if(!this.readOnly) this.oQuill.focus();
	}
	
	_onChange(delta, oldDelta, sSource, e) {
		if(sSource == 'user')
		{
			var range = this.oQuill.getSelection();
			if(range)
			{
				//sCursorPos = '';
				var bSelection = (typeof e != 'undefined')?e.shiftKey:false;
				var oSelBound = this.oQuill.getBounds(range.index, range.length);
				
				var oEditorCurBound = {	top:	this.pageHeight * this.page,
										bottom:	this.pageHeight * (this.page + 1)};
				
				//console.log('oEditorCurBound: top='+oEditorCurBound.top+' bottom='+oEditorCurBound.bottom);
				//console.log('---------------------');
				//console.log('range.length = '+range.length);
				//console.log('oSelBound: top='+oSelBound.top+' bottom='+oSelBound.bottom);
				
				//Detecting new selection & saving original line
				if(!this.line && bSelection) this.line = $.extend({}, oSelBound);
				else if(!bSelection) this.line = false;
				
				//Detecting navigating back to original line
				var bReset = (this.line && this.line.top == oSelBound.top && this.line.bottom == oSelBound.bottom);
				
				//Anticipating arrows (downside of using keydown event)
				if(e)
				{
					switch(e.which)
					{
						case 13: //Enter
						case 40: //Down
							if(bSelection) {
								if(bReset) sCursorPos = 'last';
								if(sCursorPos == 'last') { //Downwards selection, expanding
									oSelBound.bottom += this.lineHeight;
								}
								else { //Upwards selection, reducing
									oSelBound.top += this.lineHeight;
								}
							}
							else oSelBound.bottom += this.lineHeight;
							break;
						case 38: //Up
							if(bSelection) {
								if(bReset) sCursorPos = 'first';
								if(sCursorPos == 'last') { //Downwards selection, reducing
									oSelBound.bottom -= this.lineHeight;
								}
								else { //Upwards selection, expanding
									oSelBound.top -= this.lineHeight;
								}
							}
							else oSelBound.top = Math.max(0, oSelBound.top  - this.lineHeight);
							break;
						case 37: //Left
							if(bReset && bSelection) sCursorPos = 'first';
							oSelBound = this.oQuill.getBounds(Math.max(0, range.index - 1), range.length);
							break;
						case 39: //Right
							if(bReset && bSelection) sCursorPos = 'last';
							oSelBound = this.oQuill.getBounds(range.index + 1,  range.length);
							break;
					}
				}
				else this._incKeyStrokes();
				
				//console.log('oSelBound: top='+oSelBound.top+' bottom='+oSelBound.bottom);
			
				var sNewPage = this.page;
				if(	oSelBound.top < oEditorCurBound.top && (!bSelection || sCursorPos == 'first') ||
					oSelBound.bottom < oEditorCurBound.top && (!bSelection || sCursorPos == 'last')) {
					sNewPage--;
				}
				else if(oSelBound.bottom > oEditorCurBound.bottom && (!bSelection || sCursorPos == 'last') ||
						oSelBound.top > oEditorCurBound.bottom && (!bSelection || sCursorPos == 'first')) {
					sNewPage++;
				}
				this.moveToPage(sNewPage);
			}
		}
	}
	
	moveToPage(iNewPage) {
		var iContentHeight = this.$Editor.height();
		var iLastPage = Math.floor(iContentHeight / this.pageHeight);
		
		if(iNewPage == 'last') iNewPage = iLastPage;
		
		if(iNewPage >= 0 && iNewPage <= iLastPage)
		{
			if(iNewPage != this.page)
			{
				var iOldPage = this.page;
				this.page = iNewPage;
				
				//Page Position
				var iOffset = this.page * this.pageHeight * -1;
				this.$Editor.css('top', iOffset);
			}
			
			//Page Number
			//$('.curr').text(self.vars('quill_page') + 1);
			
			//Detect First/Last Page
			this.$PrevBtn.toggleClass('visible', this.page != 0);
			this.$NextBtn.toggleClass('visible', this.page != iLastPage);
		}
	}
	
	_incKeyStrokes() {
		this.keystrokes += + 1;
		this.onKeyStroke();
	}
	
	getContent() {
		return this.oQuill.getContents().ops;
	}
	
	isEmpty() {
		const rEmpty = /^(<p>(<br>|<br\/>|<br\s\/>|\s+|)<\/p>|)$/gm;
	    return rEmpty.test(this.oQuill.getText().trim());
	}
	
	quill2Html(asDelta) {
	    var tempCont = document.createElement('div');
	    (new Quill(tempCont)).setContents(asDelta);
	    return tempCont.getElementsByClassName('ql-editor')[0].innerHTML;
	}
}