# -*- encoding: utf-8 -*-
import re
import vim


def cjk_width(text):
    import sys
    if sys.version_info[0] < 3:
        if not isinstance(text, unicode):
            text = text.decode("utf-8")
    from unicodedata import east_asian_width
    return sum(1+(east_asian_width(c) in "WF") for c in text)


def create_separarator(widths, char):
    """Genera una linea para separar filas de una tabla.

    El parametro `widths` es un lista indicando el ancho de cada
    columna. En cambio el argumento `char` es el caracter que se
    tiene que utilizar para imprimir.

    El valor retornado es un string.

    Por ejemplo::

        >>> create_separarator([2, 4], '-')
        '+----+------+'
    """

    line = []

    for w in widths:
        line.append("+" + char * (w + 2))

    line.append("+")
    return ''.join(line)


def create_line(columns, widths):
    """Crea una fila de la tabla separando los campos con un '|'.

    El argumento `columns` es una lista con las celdas que se
    quieren imprimir y el argumento `widths` tiene el ancho
    de cada columna. Si la columna es mas ancha que el texto
    a imprimir se agregan espacios vacíos.

    Por ejemplo::

        >>> create_line(['nombre', 'apellido'], [7, 10])
        '| nombre  | apellido   |'
    """

    line = zip(columns, widths)
    result = []

    for text, width in line:
        spaces = " " * (width - cjk_width(text))
        text = "| " + text + spaces + " "
        result.append(text)

    result.append("|")
    return ''.join(result)


def create_table(content):
    """Imprime una tabla en formato restructuredText.

    El argumento `content` tiene que ser una lista
    de celdas.

    Por ejemplo::

        >>> print create_table([['software', 'vesion'], ['python', '3.2'], ['vim', '7.3']])
        +----------+--------+
        | software | vesion |
        +==========+========+
        | python   | 3.2    |
        +----------+--------+
        | vim      | 7.3    |
        +----------+--------+
    """

    # obtiene las columnas de toda la tabla.
    columns = zip(*content)
    # calcula el tamano maximo que debe tener cada columna.
    widths = [max([cjk_width(x) for x in i]) for i in columns]

    result = []

    result.append(create_separarator(widths, '-'))
    result.append(create_line(content[0], widths))
    result.append(create_separarator(widths, '='))

    for line in content[1:]:
        result.append(create_line(line, widths))
        result.append(create_separarator(widths, '-'))

    return '\n'.join(result)


def are_in_a_table(current_line):
    "Indica si el cursor se encuentra dentro de una tabla."
    return "|" in current_line or "+" in current_line


def are_in_a_paragraph(current_line):
    "Indica si la linea actual es parte de algun parrafo"
    return len(current_line.strip()) >= 1


def get_table_bounds(current_row_index, are_in_callback):
    """Obtiene el numero de fila donde comienza y termina la tabla.

    el argumento `are_in_callback` tiene que ser una función
    que indique si una determinada linea pertenece o no
    a la tabla que se quiere medir (o crear).

    Retorna ambos valores como una tupla.
    """

    top = 0
    buffer = vim.current.buffer
    max = len(buffer)
    bottom = max - 1

    for a in range(current_row_index, top, -1):
        if not are_in_callback(buffer[a]):
            top = a + 1
            break

    for b in range(current_row_index, max):
        if not are_in_callback(buffer[b]):
            bottom = b - 1
            break

    return top, bottom


def remove_spaces(string):
    "Elimina los espacios innecesarios de una fila de tabla."
    return re.sub("\s\s+", " ", string)


def create_separators_removing_spaces(string):
    return re.sub("\s\s+", "|", string)


def extract_cells_as_list(string):
    "Extrae el texto de una fila de tabla y lo retorna como una lista."
    string = remove_spaces(string)
    return [item.strip() for item in string.split('|') if item]


def extract_table(buffer, top, bottom):
    full_table_text = buffer[top:bottom]
    # selecciona solamente las lineas que tienen celdas con texto.
    only_text_lines = [x for x in full_table_text if '|' in x]
    # extrae las celdas y descarta los separadores innecesarios.
    return [extract_cells_as_list(x) for x in only_text_lines]


def extract_words_as_lists(buffer, top, bottom):
    "Genera una lista de palabras para crear una lista."

    lines = buffer[top:bottom + 1]
    return [create_separators_removing_spaces(line).split('|') for line in lines]


def copy_to_buffer(buffer, string, index):
    lines = string.split('\n')
    for line in lines:
        buffer[index] = line
        index += 1


def fix_table(current_row_index):
    """Arregla una tabla para que todas las columnas tengan el mismo ancho.

    `initial_row` es un numero entero que indica en que
    linea se encuenta el cursor."""

    # obtiene el indice donde comienza y termina la tabla.
    r1, r2 = get_table_bounds(current_row_index, are_in_a_table)

    # extrae de la tabla solo las celdas de texto
    table_as_list = extract_table(vim.current.buffer, r1, r2)

    # genera una nueva tabla tipo restructured text y la dibuja en el buffer.
    table_content = create_table(table_as_list)
    copy_to_buffer(vim.current.buffer, table_content, r1)


def FixTable():
    (row, col) = vim.current.window.cursor
    line = vim.current.buffer[row - 1]

    if are_in_a_table(line):
        fix_table(row - 1)
    else:
        print("No estoy en una tabla. Terminando...")


def CreateTable():
    (row, col) = vim.current.window.cursor

    top, bottom = get_table_bounds(row - 1, are_in_a_paragraph)
    lines = extract_words_as_lists(vim.current.buffer, top, bottom)
    table_content = create_table(lines)
    vim.current.buffer[top:bottom + 1] = table_content.split('\n')
