# -*- coding: utf-8 -*-

# Created: 2013-07-07 
# Author:  Klaus-Dieter Bauer <bauer.klaus.dieter@gmail.com>
#
# UI part copied from Jaap Karssenberg "inlinecalculator" plugin.

from __future__ import with_statement
from __future__ import division # We are doing math in this module ...


import logging
import re
import math
import cmath
import sys
import re
import tempfile
import shutil
import zim.formats.wiki
from warnings import warn

from zim.plugins import PluginClass
from zim.errors import Error



logger = logging.getLogger('zim.plugins.insertsymbol')


ui_xml = '''
<ui>
<menubar name='menubar'>
	<menu action='tools_menu'>
		<placeholder name='plugin_items'>
			<menuitem action='table_realign'/>
		</placeholder>
	</menu>
</menubar>
</ui>
'''

ui_actions = (
	# name, stock id, label, accelerator, tooltip, readonly
	('table_realign', None, _('Reallign _Tables'), '', '', False), # T: menu item
)


# helper functions

class TableTrixPlugin(PluginClass):

	plugin_info = {
		'name': _('Table Trix'), # T: plugin name
		'description': _('''\
This plugin handles ascii-art tables, a makeshift solution for displaying data
tables in zim. Currently it can:

TODO: List of features.
'''), 
		'author': 'Klaus-Dieter Bauer',
		# TODO: Help text.
		# 'help': 'Plugins:TableTrix',
	}

	#~ plugin_preferences = (
		# key, type, label, default
	#~ )

	def initialize_ui(self, ui):
		if self.ui.ui_type == 'gtk':
			self.ui.add_actions(ui_actions, self)
			self.ui.add_ui(ui_xml, self)

	def table_realign(self):
		'''Action called by the menu item or key binding,
		will look through page for tables to realiign. 
		'''
		# A gtk.TextBuffer
		notebookinterface = self.ui
		textbuffer = notebookinterface.mainwindow.pageview.view.get_buffer()
		page = textbuffer.page
		wiki_source_lines = page.dump(zim.formats.wiki)
		modified_lines = list(_align_tables(wiki_source_lines))
		page.parse(zim.formats.wiki, modified_lines, append=True)
		
		
		
		
		
##################################################################################
##################################################################################
##################################################################################
##################################################################################
##################################################################################


# A "quick" tool for reformatting ascii-art tables in zim.
    

def _line_get_indent_level(line):
    """Count number of tabs at beginning of line."""
    for count,char in enumerate(line):
        if char != "\t":
            break
    return count


_LINE_CELL_DELIMITER_REGEX = re.compile(r"\s+\|+\s+|\s{2,}")
                                        
def _line_get_cells(line):
    """
    Arguments:
        line    A line of a table.
        
    Returns:
        The data cells of the line, stripped of ''VERBATIM'' markers and delimiters.
        No-break spaces (\\u00A0) are replaced by normal spaces.
    """
    line_purified = line.replace("''","").replace(u"\u00A0"," ")\
                        .replace("{{|","").replace("|}}","")
    line_purified = line_purified.strip()
    if line_purified.endswith("|"):
        line_purified = line_purified + " "
    if line_purified.startswith("|"):
        line_purified = " "+ line_purified
    line_cells = _LINE_CELL_DELIMITER_REGEX.split(line_purified)
    line_cells = [ cell.strip() for cell in line_cells ]
    return line_cells
        

_FORMAT_SPECIFICATION_ALLOWED_CHARS = "lr|"

def _format_row(row, fs_dict):
    """
    Format a row (a list of strings) according to the format specifications in 'fs_dict'.
    """
    outstr = []
    outstr.append(fs_dict["indent_string"])
    outstr.append("''")
    outstr.append(fs_dict["border_left"])
    
    delims = fs_dict["delimiter_strings"] + [""]
    for cell_align, cell_width, cell_data, delimiter in \
                zip(fs_dict["alignments"], fs_dict["column_widths"], row, delims):
        if re.match("^---+$", cell_data):
            outstr.append("-" * cell_width)
        elif re.match("^===+$", cell_data):
            outstr.append("=" * cell_width)
        else:
            if cell_data == "":
                cell_data = "-"
            pad_string = " " * ( cell_width - len(cell_data) ) 
            if cell_align == "right":
                cell_data = pad_string + cell_data
            else:
                cell_data = cell_data + pad_string
            # Preserve images and links un-verbatimed.
            while re.match(r"\{\{.*\}\}", cell_data):
                cell_data = re.sub(r"\{\{(.*?)\}\}",r"''{{\1}}''",cell_data)
            while re.match(r"\[\[.*\]\]", cell_data):
                cell_data = re.sub(r"\[\[(.*?)\]\]",r"''[[\1]]''",cell_data)
            outstr.append(cell_data)
        outstr.append(delimiter)
    
    outstr.append(fs_dict["border_right"])
    outstr.append("''\n")
    # The unquoting of images/links may have caused quadruple-quotes
    joined =  u"".join(outstr).replace(" ",u"\u00A0").replace("''''","")
    return joined
                               
def _parse_reformat_table(table_lines): 
    """
    Arguments:
        table_lines: 
                The full list of lines of the table. The first line is
                guaranteed to start with {|| or ''{||, the last to end with |}}
                or |}}''.
                
                The first line may have one of these forms:
                
                  1. {|| FORMAT CELL2 CELL3 ... 
                     A formatting specified followed by the contents of the the
                     first line, the topleft cell being assumed empty.
                  2. {|| FORMAT
                     Just the formatting specifier.
                
                Likewise the last line may be a data line ended with |}} or a
                line containing only ||}.
                
                That form is preserved by reformatting.  
        
    Returns:
        The table lines, reformatted.
    """
    table_data = []
    
    #### PREPROCESS FIRST LINE ####
    
    line_0 = table_lines[0]
    line_0_indent_level = _line_get_indent_level(line_0)
    line_0_cells = _line_get_cells(line_0)
    if all(( char in _FORMAT_SPECIFICATION_ALLOWED_CHARS for char in line_0_cells[0])):
        format_specifiers = line_0_cells[0]
        if len(line_0_cells) == 1:
            print_line_0_as = "format_only"
        else:
            print_line_0_as = "data"
            
    else:
        format_specifiers = ""
        print_line_0_as = "data"
            
    if print_line_0_as == "data":
        line_0_cells[0] = "{{| " + line_0_cells[0]
        table_data.append(line_0_cells)
    
    #### PREPROCESS LINES EXCEPT FOR LAST LINE ####
    
    for line_num in xrange(1,len(table_lines)-1):
        table_data.append(_line_get_cells(table_lines[line_num]))
        
    #### PREPROCESS LAST LINE ####
    
    line_last = table_lines[-1]
    line_last_cells = _line_get_cells(line_last)
    
    if line_last_cells == [""]:
        print_line_last_as = "end_tag_only"
    else:
        print_line_last_as = "data"
        table_data.append(line_last_cells)
        
    #### MAKE TABLE_DATA RECTANGULAR ####
    
    column_count = max((len(row) for row in table_data))
    for row in table_data:
        while len(row) < column_count:
            row.append("")
    
    #### DETERMINE COLUMN WIDTHS ####
    
    
    #### DETERMINE FULL FORMATTING SPEFICIATION ####
    
    format_specification_dict = {}
    
    format_specification_dict["column_widths"] = [max(( len(cell) for cell in column))
                                                  for column in zip(*table_data)]
    
    g = re.match("^(\|*)(.*?)(\|*)$",format_specifiers).groups()
    format_specifiers_inner = g[1]
    format_specification_dict["border_left"]  = "" if g[0] == "" else g[0] + " "
    format_specification_dict["border_right"] = "" if g[2] == "" else " " + g[2]
    format_specification_dict["indent_string"] = indent_string = "\t" * line_0_indent_level
    delimiter_strings = []
    alignments = []
    for char in format_specifiers_inner:
        if char == "l":
            alignments.append("left")
            delimiter_strings.append("  ")
        elif char == "r":
            alignments.append("right")
            delimiter_strings.append("  ")
        elif char == "|": 
            # Due to previous border handling this cannot be the first char,
            # delimiter_strings must already contain something.
            delim = delimiter_strings[-1]
            delim = delim[0] + "|" + delim[1:]
            delimiter_strings[-1] = delim
        else:
            warn("Invalid format specifier char '%s', assuming 'l' instead." % (char))
            alignments.append("left")
            delimiter_strings.append("  ")
    format_specification_dict["delimiter_strings"] = delimiter_strings
    format_specification_dict["alignments"] = alignments
    
    # Format specifier may be too short.        
    while len(delimiter_strings) < column_count - 1:
        delimiter_strings.append("  ")
    while len(delimiter_strings) > column_count - 1:
        delimiter_strings.pop()
    while len(alignments) < column_count:
        alignments.append("left")
    while len(alignments) > column_count:
        alignments.pop()
    
    #### YIELD THE RESULT LINES ####
    
    #### HANDLING OF FIRST ROW ####
    
    if print_line_0_as == "data":
        # Note that in this case the table data for the first line includes {{| tag.
        yield _format_row(table_data[0], format_specification_dict)
    else:
        yield format_specification_dict["indent_string"]  + "''{{| " + line_0_cells[0] + "''\n"
        yield _format_row(table_data[0], format_specification_dict)
    
    #### HANDLING OF ROWS IN BETWEEN ####
    
    for row in table_data[1:-1]:
        yield _format_row(row, format_specification_dict)
        
    #### HANDLING OF LAST ROW ####
    
    if print_line_last_as == "data":
        yield _format_row(table_data[-1], format_specification_dict)[:-3] + " |}}''\n"
    else:
        yield _format_row(table_data[-1], format_specification_dict)
        yield format_specification_dict["indent_string"] + "''|}}''\n"
        
#     print ">format", format_specifiers
#     print ">line_0", line_0_cells
#     print ">line_l", line_last_cells 
#     print ">print_0", print_line_0_as
#     print ">print_l", print_line_last_as
        

def _align_tables(lines):
    """
    Processes 'line_stack', a list of strings, and returns the line_stack with realigned
    tables.
    """
    # Implemented as a state machine collecting the table lines and passing it
    # to a subroutine.
    
    state = "outside_table"
    
    for line_num, line in enumerate(lines):
        line_stripped = line.strip()
        # Preprocess line line_stripped for easier handling.
        
        if state == "outside_table":
            if line_stripped.startswith("{{|") or line_stripped.startswith("''{{|"):
                # Indent of first line determines indent of whole table.
                table_lines = [line]
                state = "inside_table"
            else:
                yield line
        elif state == "inside_table":
            if line_stripped.endswith("|}}") or line_stripped.endswith("|}}''"):
                table_lines.append(line)
                table_lines_reformatted = _parse_reformat_table(table_lines)
                for line in table_lines_reformatted:
                    yield line
                state = "outside_table"
                table_lines = []
            elif line_stripped.startswith("{{|") or line_stripped.startswith("''{{|"):
                warn("Unfinished table at line %(line_num)d, '%(line_stripped)s'" % locals())
                for table_line_unprocessed in table_lines: 
                    yield table_line_unprocessed
                table_lines = [line]
            else:
                table_lines.append(line)
        else:
            assert False, "Invalid state %(state)s" % locals()
                
    # If an table is unfinished, output the lines unprocessed.
    if state == "inside_table":
        warn("Unfinished table at end of file")
        for table_line_unprocessed in table_lines: 
            yield table_line_unprocessed
            

def main(argv):
    if len(argv) != 2:
        print >>stderr, "Syntax: %s ZIMTEXTFILE" % (argv[0])
        
    zim_file_name = argv[1]
    tmp_file_name = tempfile.mktemp("txt", "zim-table-reformat-")
    
    with open(zim_file_name) as zim_file, open(tmp_file_name,"w") as tmp_file:
        in_lines = zim_file.readlines()
        zim_file.close()
        out_lines = list(_align_tables(in_lines))
        tmp_file.write("".join(out_lines))
        tmp_file.close()
        #shutil.move(tmp_file_name,zim_file_name)
        print "".join(out_lines)

if __name__ == '__main__':
    main(["",r"E:\zim-notebooks\Testing\MakeShiftTabl\Table.txt"])
    #main(sys.argv)
		
		
	