/*jslint browser: true*/

"use strict";

/*jshint esversion: 6 */

String.prototype.find = function(string, position = -1) {
	return this.indexOf(string, position);
};
String.prototype.erase = function(start, offset) {
	if(start != 0)
		return this.substring(0, start) + this.substring(start + offset, this.length);
	else
		return this.substring(start + offset, this.length);
};

String.prototype.insert = function(start, text) {
	if(start != 0)
		return this.substring(0, start) + text + this.substring(start, this.length);
	else
		return text + this.substring(start, this.length);
};

String.prototype.regexIndexOf = function(regex, startpos) {
	var indexOf = this.substring(startpos || 0).search(regex);
	return (indexOf >= 0) ? (indexOf + (startpos || 0)) : indexOf;
};
String.prototype.splice = function(idx, rem, str) {
	return this.slice(0, idx) + str + this.slice(idx + Math.abs(rem));
};
String.prototype.replace_last_one = function (to_be_replaced, replace_to_this) {
	var lIndex = this.lastIndexOf(to_be_replaced);
	var str = this.substring(0, lIndex) + replace_to_this + this.substr(lIndex + to_be_replaced.length);
	return str;
};
String.prototype.strip_tags = function (to_be_replaced, replace_to_this) {
	var lIndex = this.lastIndexOf(to_be_replaced);
	var str = this.substring(0, lIndex) + replace_to_this + this.substr(lIndex + to_be_replaced.length);
	return str;
};

String.prototype.size = function() {
	return this.length;
};

const BLOCK = 2;
const IMAGE = 1;
const LINK = 0;
const NO_TAG = -1;

/*
 * Changes from original:
 *  - this.line is a reference shared from list_item until unquoted_tags
 *    is a way to simulate reference string argument
 *  - size() to js idiom
 *  - add this. before every object property
 */
class tedi2lang{
	constructor(td) {
		this._start_heading_first_level_tag = td.start_heading_first_level_tag; 
		this._start_heading_second_level_tag = td.start_heading_second_level_tag;
		this._start_heading_third_level_tag = td.start_heading_third_level_tag;
		this._start_heading_fourth_level_tag = td.start_heading_fourth_level_tag;
		
		this._end_heading_first_level_tag = td.end_heading_first_level_tag;
		this._end_heading_second_level_tag = td.end_heading_second_level_tag;
		this._end_heading_third_level_tag = td.end_heading_third_level_tag;
		this._end_heading_fourth_level_tag = td.end_heading_fourth_level_tag;
		
		this._start_list_tag = td.start_list_tag;
		this._list_item_tag = td.list_item_tag;
		this._end_list_tag = td.end_list_tag;
		
		this._start_container_tag = td.start_container_tag;
		this._middle_container_tag = td.middle_container_tag;
		this._end_container_tag = td.end_container_tag;
		
		this._start_link_tag = td.start_link_tag;
		this._middle_link_tag = td.middle_link_tag;
		this._end_link_tag = td.end_link_tag;
		
		this._start_image_tag = td.start_image_tag;
		this._middle_image_tag = td.middle_image_tag;
		this._end_image_tag = td.end_image_tag;
		
		this._start_table_tag = td.start_table_tag;
		this._end_table_tag = td.end_table_tag;
		this._start_table_row_tag = td.start_table_row_tag;
		this._end_table_row_tag = td.end_table_row_tag;
		this._start_table_data_tag = td.start_table_data_tag;
		this._end_table_data_tag = td.end_table_data_tag;
		this._end_paragraph_tag = td.end_paragraph_tag;
		this._line = "";
			
	}
	
	found(position) { return (position != -1); }
	
	/*
	 * Split string into array with lines
	 */
	get_lines(text) {
		let lines_array = [];
		if(this.found(text.indexOf('\n'))) {
			lines_array = text.split('\n');
			//JS when splits strings with \n at end
			//creates a fake newline in array
			if(text[text.size() - 1] == '\n') {
				//Delete last fake line
				lines_array.pop();
			}
		}else
			if(text.size() > 0)
				lines_array = [text];
		
		return lines_array;	
	}
	
	/*
	 * Add header, convert line by line through text and finish with footer
	 *
	 */
	convert(text, header = "", footer = "") {
		let index = this.get_lines(text);
			
		let return_text = header;
		this._open_brackets = 0;
		this._is_converting_table = false;
		this._is_unordered_list = false;

		for(let i = 0; i < index.length; ++i) {
			this._has_block = false;
			this._has_image = false;
			let line = index[i];
			return_text += this.convert_line(line);
		}
		
		if(this._open_brackets > 0)
			throw "Missing '}' in document. End of file";
		if(this._is_converting_table) return_text += this._end_table_tag + "\n";
		return_text += footer;

		return return_text;
	}


	/*
	 * Main logic converting line. 
	 *
	 */
	convert_line(line) {
		if(line[0] != '<') {
			let end_table = this.convert_end_table(line);
			let hash = line.find('#');

			if(this.found(hash) && this.is_first_tag(line, hash))
				return end_table + this.convert_line_heading(line, hash);
			else {
				let ul = line.find("__");
				if(this.found(ul))
					return end_table + this.convert_line_list_start(line);
				else {
					this._line = line;
					let li = this._line.find("--");
					if(this.found(li) && this._is_unordered_list)
						this.convert_line_list_item(li);
					
					let ul_end = this._line.find(",,");
					if(this.found(ul_end) && this._is_unordered_list)
						return end_table + this.convert_line_list_end(line);
					else {
						if(this._line[0] == '"')
							this.convert_line_quote();
						else
							this.convert_unquoted_tags();
						
						let first_pipe = this._line.find("|");
						if(this.found(first_pipe))
							return this.convert_line_table(first_pipe);
						else
							return end_table + this.convert_line_ending(this._line);
						
					}
				}
			}
		} else
			return this.convert_end_table_control_tags(line) + this.convert_line_control_tags(line);
		
	}
	
	/*
	 * Deletes heading tag and insert new heading tag
	 *
	 */
	convert_line_heading(line, hash_position) {

		let level = 0;
		while(this.found(hash_position) && line[hash_position + level] == '#')
			++level;

		if((hash_position + level) >= line.size())
			throw "Unexpected heading size" + line;
		else
			line = line.substring(hash_position + level, line.size());
		
		line = this.strip_escaping(line);
		
		switch(level) {
			case 1: line = this._start_heading_first_level_tag + line + this._end_heading_first_level_tag; break;
			case 2: line = this._start_heading_second_level_tag + line + this._end_heading_second_level_tag; break;
			case 3: line = this._start_heading_third_level_tag + line + this._end_heading_third_level_tag; break;
			case 4: line = this._start_heading_fourth_level_tag + line + this._end_heading_fourth_level_tag; break;
			default: line = this._start_heading_fourth_level_tag + line + this._end_heading_fourth_level_tag; break;
		}
		
		return line + "\n";
	}
	
	// List tags

	convert_line_list_start() {

		++this._is_unordered_list;
		return this._start_list_tag + "\n";
		
	}

	convert_line_list_item(li) {

		this._line = this._line.erase(li, 2);
		this._line = this._line.insert(li, this._list_item_tag);
		
	}

	convert_line_list_end() {

		--this._is_unordered_list;
		return this._end_list_tag + "\n";
		
	}

	convert_line_quote() {

		if(this._line[this._line.size() - 1] == '"')
			this._line = this.strip_escaping(this._line.substr(1, this._line.size() - 2));
		else
			throw "Missing end quotes." + this._line;
	}

	/*
	 * Convert Tedi link to a link tag
	 *
	 */
	convert_line_link() {

		let start_tag = this.get_not_escaped_tag("[(");
		if(this.found(start_tag) && !this.is_tag_escaped(start_tag)) {

			this._line = this._line.erase(start_tag, 2);
			this._line = this._line.insert(start_tag, this._start_link_tag);

			let end_parenthesis = this.get_not_escaped_tag(") ");
			if(this.found(end_parenthesis) && !this.is_tag_escaped(end_parenthesis)) {

				this._line = this._line.erase(end_parenthesis, 2);
				this._line = this._line.insert(end_parenthesis, this._middle_link_tag);

				let bracket = this.correct_position(end_parenthesis + this._middle_link_tag.size(), '[', ']');
				if(this.found(bracket)) {
					this._line = this._line.erase(bracket, 1);
					this._line = this._line.insert(bracket, this._end_link_tag);
				} else
					throw "Missing ']' in link tag." + this._line;
			} else
				throw "Missing ')' in link tag." + this._line;
		} else
			throw "Missing link tag." + this._line;

	}
	
	/*
	 * Deletes image tag and insert image tag
	 *
	 */
	convert_line_image() {

		let start_tag = this.get_not_escaped_tag("([");
		if(this.found(start_tag) && !this.is_tag_escaped(start_tag)) {

			this._line = this._line.erase(start_tag, 2);
			this._line = this._line.insert(start_tag, this._start_image_tag);

			let square_bracket = this.get_not_escaped_tag("] ");
			if(this.found(square_bracket) && !this.is_tag_escaped(square_bracket)) {
				this._line = this._line.erase(square_bracket, 2);
				this._line = this._line.insert(square_bracket, this._middle_image_tag);

				let last_bracket = this.correct_position(square_bracket + this._middle_image_tag.size(), '(', ')');
				if(this.found(last_bracket)) {
					this._line = this._line.erase(last_bracket, 1);
					this._line = this._line.insert(last_bracket, this._end_image_tag);
					this._has_image = true;
				} else
					throw "Missing ')' in images tag." + this._line;
			} else
				throw "Missing ']' in images tag." + this._line;
		} else
			throw "Missing images tag." + this._line;

	}
	
	/*
	 * Deletes block tag and insert block tag
	 *
	 */
	convert_line_block() {
		let start_tag = this.get_not_escaped_tag("{(");
		if(this.found(start_tag) && !this.is_tag_escaped(start_tag)) {

			this._line = this._line.erase(start_tag, 2);
			this._line = this._line.insert(start_tag, this._start_container_tag);

			start_tag = this.get_not_escaped_tag(") ");
			if(this.found(start_tag) && !this.is_tag_escaped(start_tag)) {
				this._line = this._line.erase(start_tag, 2);
				this._line = this._line.insert(start_tag, this._middle_container_tag);
				let end_tag = this.correct_position(start_tag + this._middle_container_tag.size(), '{', '}');
				if(this.found(end_tag)) {
					this._line = this._line.erase(end_tag, 1);
					this._line = this._line.insert(end_tag, this._end_container_tag);
				} else
					++this._open_brackets;
				
			} else
				throw "Missing ')' in block tag." + this._line;
		} else
			throw "Missing block tag." + this._line;

		this._has_block = true;
	}
	
	/*
	 * Start or continue table conversion
	 *
	 */
	convert_line_table(first_pipe) {
		let return_text = "";
		if(!this._is_converting_table) {
			if(first_pipe != -1 && first_pipe != this._line.lastIndexOf("\|")) {
				return_text = this._start_table_tag + "\n";
				this._is_converting_table = true;
				return return_text + this.convert_line_table_row(this._line);
			} else
				return this.convert_line_ending(this._line);
		}else
			return this.convert_line_table_row(this._line);

	}
	
	/*
	 * Convert every cell of one table row
	 *
	 */
	convert_line_table_row(line) {

		let pipe = line.find("|");
		if(this.found(pipe)) {
			++pipe;
			let return_text = this._start_table_row_tag + "\n";
			line = line.substr(pipe, line.size() - 1);
			pipe = line.find("|");
			if(this.found(pipe)) {
				let size = line.size() - 1;
				if(line[size] == '|') {
					while(this.found(pipe) && line[size] == '|') {
						return_text += this._start_table_data_tag + this.strip_escaping(line.substr(0, pipe)) + this._end_table_data_tag + "\n";
						++pipe;
						line = line.substr(pipe, size);
						pipe = line.find("|");
						size = line.size() - 1;
					}
					return return_text + this._end_table_row_tag + "\n";
				} else
					throw "Table not correctly written\n" +
						"Maybe there's a whitespace at end of line" + line;
			} else
				throw "Expected '|' in table" + this._line;
		} else
			throw "Expected '|' in table" + this._line;
	}
	
	/*
	 * Checks if there is a non-space character (every char except \n \t \r)
	 * before the given position
	 *
	 */
	is_first_tag(line, position) {
		let i = 0;
		while(i < position && this.isspace(line[i]))
			++i;
		return (i == position);
	}
	
	isspace(character) {
		return ([' ', '\t','\n','\v', '\f', '\r'].includes(character));
	}
	
	/*
	 * End table if there isn't a pipe char and there's a open
	 * table tag
	 *
	 */
	convert_end_table(line) {
		let pipe = line.find("|");
		if(!this.found(pipe) && this._is_converting_table) {
			this._is_converting_table = false;
			return this._end_table_tag + "\n";
		}
		return "";
	}
	
	/*
	 * Check line ending and convert depending ending whitespace
	 *
	 */
	convert_line_ending(line) {
		line = this.strip_escaping(line);
		if(line[line.size() - 1] == ' ')
			return line + this._end_paragraph_tag + "\n";
		else
			return line + "\n";
	}
	
	/*
	 * Control tags:
	 * <!, <>, <+. <
	 */
	convert_line_control_tags(line) {
		if(line[1] != '!') {
			if(line[1] == '>') {
				line = line.erase(0,2);
				return line + "\n";
			} else if(line[1] == '+') {
				line = line.erase(0,2);
				return line + this._end_paragraph_tag + "\n";

			} else {
				line = line.erase(0,1);
				return line + "\n";
			}
		} else
			return "";
		
	}
	
	/*
	 * Get next unquoted tag (link, image, container) from line
	 *
	 */ 
	convert_unquoted_tags() {
			let tag = this.main_tag();
			while(tag != NO_TAG) {
				switch(tag) {
					case LINK : this.convert_line_link(); break;
					case IMAGE : this.convert_line_image(); break;
					case BLOCK : this.convert_line_block(); break;
				}
				tag = this.main_tag();
			}
			this.check_ending_container();
	}

	main_tag(line) {
		let tag = NO_TAG;
		let line_size = this._line.size();
		let first_bracket = this.get_not_escaped_tag("{(");
		let first_parenthesis = this.get_not_escaped_tag("([");
		let first_square_bracket = this.get_not_escaped_tag("[(");

		if(first_bracket != -1 && first_bracket < line_size && this._line[first_bracket - 1] != '\\') {
			tag = BLOCK;
			line_size = first_bracket;
		}
		if(first_square_bracket != -1 && first_square_bracket < line_size && this._line[first_square_bracket - 1] != '\\') {
			line_size = first_square_bracket;
			tag = LINK;
		}
		if(first_parenthesis != -1 && first_parenthesis < line_size && this._line[first_parenthesis - 1] != '\\')
			tag = IMAGE;

		return tag;
	}
	
	/*
	 * Get ending char position or -1 if doesn't exist
	 *
	 */ 
	correct_position(position, starting_char, ending_char) {

		let char_founds = 1;
		while(char_founds != 0 && this._line.size() > position) {
			if(this._line[position] == starting_char)
				++char_founds;
			else if(this._line[position] == ending_char && position == 0)
				--char_founds;
			else if(this._line[position] == ending_char && position > 0 && !this.is_tag_escaped(position))
				--char_founds;
			++position;
		}

		if(this._line.size() == position && this._line[position - 1] != ending_char)
			return -1;
		else
			return position - 1;
	}
	
	/*
	 * Convert every container ending if there are open container tags.
	 *
	 */
	check_ending_container() {

		let first_bracket_closed = this._line.find("}");
		let first_bracket_open = this._line.find("{");
		if(this.found(first_bracket_closed) && !this.is_tag_escaped(first_bracket_closed) && !this.found(first_bracket_open)) {
			if(this._open_brackets == 0)
				throw "Missing '{' in block tag." + this._line;
			--this._open_brackets;
			this._has_block = true;

			this._line = this._line.erase(first_bracket_closed, 1);
			this._line = this._line.insert(first_bracket_closed, this._end_container_tag);
		}
	}
	/*
	 * 
	 * End table if there's a open table tag
	 * (Added for control tags that 
	 */
	convert_end_table_control_tags(line) {
		if(this._is_converting_table) {
			this._is_converting_table = false;
			return this._end_table_tag + "\n";
		}
		return "";
	}
	/*
	 * Check if position is escaped with '\' char
	 * 
	 */
	is_tag_escaped(position) {
		let is_escaped = false;
		if(position > 0 && this._line[position - 1] == '\\')
			is_escaped = true;
		
		return is_escaped;
	}
	
	/*
	 * Check if position is escaped with '\' char
	 * 
	 */
	get_not_escaped_tag(find_character, position = -1) {
		if(!this.found(position))
			position = this._line.find(find_character);

		while(this.found(position) && this.is_tag_escaped(position)) {
			position = this._line.find(find_character, position + find_character.size());
		}
		return position;
	}
	
	/*
	 * Convert escaped characters to their equivalents
	 *
	 */
	strip_escaping(line) {
		line = line.replace(/\\\\/g, "\\");
		line = line.replace(/\\\[/g, "[");
		line = line.replace(/\\\]/g, "]");
		line = line.replace(/\\\{/g, "{");
		line = line.replace(/\\\}/g, "}");
		line = line.replace(/\\\)/g, ")");
		line = line.replace(/\\\(/g, "(");
		return line;
	}

}
