#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" Yet Another Table Generator, convert CSV or html table to ASCII art table. """

__author__ = 'cig01'
__email__ = 'juhani@163.com'
__version__ = '0.10.0'
__source__ = 'https://github.com/10gic/yatg'
__license__ = 'AGPLv3+'

__all__ = ['html_2_ascii_table', 'csv_2_ascii_table', 'FORCE_WIDTH1_CHARS']

import sys
import unicodedata
import logging
import csv

try:
    from html.parser import HTMLParser  # Python 3
except ImportError:
    from HTMLParser import HTMLParser  # Python 2

logger = logging.getLogger('app')
logging.basicConfig(
    format=
    "[%(filename)s:%(lineno)-3s:%(funcName)20s %(levelname)s] %(message)s",
    level=logging.INFO)

# Maximum number of rows that supported, you can enlarge it for big table
MAX_TABLE_ROWS = 500
# Maximum number of columns that supported, you can enlarge it for big table
MAX_TABLE_COLS = 100

FORCE_WIDTH1_CHARS = []


def _wide_chars(s):
    """ Return the number of wide chars in string s. WIDE(W) and FULLWIDTH(F) is
    considered as wide chars. AMBIGUOUS(A), for example “”‘’, is not wide."""
    if 'emoji' in FORCE_WIDTH1_CHARS:
        try:
            import emoji
            # emoji don't consider as wide char if it in FORCE_WIDTH1_CHARS
            return sum(x not in emoji.UNICODE_EMOJI and \
                       (unicodedata.east_asian_width(x) == 'W' or \
                        unicodedata.east_asian_width(x) == 'F')
                for x in s)
        except ImportError:
            # error message already printed in entry function
            pass
    return sum(
        unicodedata.east_asian_width(x) == 'W'
        or unicodedata.east_asian_width(x) == 'F' for x in s)


def _width(s):
    """ Return the width of string s."""
    # python 2, convert str to unicode. In python 3, str is unicode
    if sys.version_info[0] < 3:
        if isinstance(s, str):
            s = s.decode('utf-8')
    return len(s) + _wide_chars(s)


class MyTableCell:
    """ Represent cell in table """

    def __init__(self, data='', cell_type='th', attrs=None):
        self.data = data
        self.cell_type = cell_type
        self.attrs = attrs

    def get_colspan(self):
        if self.attrs is None:
            return 1
        for attr in self.attrs:
            if attr[0] == 'colspan':
                return int(attr[1])
        return 1  # default

    def get_rowspan(self):
        if self.attrs is None:
            return 1
        for attr in self.attrs:
            if attr[0] == 'rowspan':
                return int(attr[1])
        return 1  # default


class MyHTMLParser(HTMLParser):
    """ This class serves as a html table parser. It is able to parse multiple
    tables which you feed in. You can access the result per .tables field.
    Modified from https://github.com/schmijos/html-table-parser-python3
    """

    def __init__(
            self,
            data_separator='',
    ):
        HTMLParser.__init__(self)

        self._data_separator = data_separator

        self._in_td = False
        self._in_th = False
        self._in_tr = False
        self._attrs = []
        self._current_table = []
        self._current_row = []
        self._current_cell = []
        self.tables = []

    def handle_starttag(self, tag, attrs):
        """ We need to remember the opening point for the content of interest.
        The other tags (<table>, <tr>) are only handled at the closing point.
        """
        # logger.debug("handle_starttag tag=[%s], attrs=%s", tag, attrs)
        if tag in ['td', 'th', 'tr']:
            # Handle the case of ignoring closing tag tr/th/td, for example:
            # <table border=1>
            #  <tr><th>row1,col1<th>row1,col2
            #  <tr><td>row2,col1<td>row2,col2
            # </table>
            if self._in_th:
                self.handle_endtag('th')
            elif self._in_td:
                self.handle_endtag('td')
            if tag == 'tr' and self._in_tr:
                self.handle_endtag('tr')
        if tag == 'td':
            self._in_td = True
        if tag == 'th':
            self._in_th = True
        if tag == 'tr':
            self._in_tr = True
        if tag in ['td', 'th']:
            self._attrs = attrs
        # Just convert <br> to one space
        if tag == 'br' and (self._in_td or self._in_th):
            self._current_cell.append(' ')

    def handle_data(self, data):
        """ This is where we save content to a cell """
        if self._in_td or self._in_th:
            # logger.debug("handle_data data=[%s]", data)
            self._current_cell.append(data)

    def handle_entityref(self, name):
        """ Handle HTML encoded characters &NAME, for example &gt;, &nbsp;
        Note: this function only called when python version < 3.5,
        In python 3.5+, HTMLParser(*, convert_charrefs=True), the default
        of convert_charrefs is True, handle_entityref is not called."""
        if name == 'nbsp':
            # self.unescape('&nbsp;') produce NO-BREAK SPACE(0xA0)
            # but, we need SPACE(0x20).
            unescaped = " "
        else:
            unescaped = self.unescape('&{0};'.format(name))
        self.handle_data(unescaped)

    def handle_charref(self, name):
        """ Handle HTML encoded characters &#NNN, for example &#62;
        Note: this function only called when python version < 3.5,
        In python 3.5+, HTMLParser(*, convert_charrefs=True), the default
        of convert_charrefs is True, handle_charref is not called."""
        if name == '160':
            # self.unescape('&#160;') produce NO-BREAK SPACE(0xA0)
            # but, we need SPACE(0x20).
            unescaped = " "
        else:
            unescaped = self.unescape('&#{0};'.format(name))
        self.handle_data(unescaped)

    def handle_endtag(self, tag):
        """ Here we exit the tags. If the closing tag is </tr>, we know that we
        can save our currently parsed cells to the current table as a row and
        prepare for a new row. If the closing tag is </table>, we save the
        current table and prepare for a new one.
        """
        # logger.debug("handle_endtag tag=[%s]", tag)
        if tag == 'table':
            # Handle the case of ignoring closing tag tr/th/td, for example:
            # <table border=1>
            #  <tr><th>row1,col1<th>row1,col2
            #  <tr><td>row2,col1<td>row2,col2
            # </table>
            if self._in_th:
                self.handle_endtag('th')
            elif self._in_td:
                self.handle_endtag('td')
            if self._in_tr:
                self.handle_endtag('tr')
        if tag == 'td':
            self._in_td = False
        elif tag == 'th':
            self._in_th = False
        elif tag == 'tr':
            self._in_tr = False

        if tag in ['td', 'th']:
            final_cell = self._data_separator.join(self._current_cell).strip()
            final_cell = final_cell.replace('\r\n', \
                                    ' ').replace('\r', ' ').replace('\n', ' ')
            # Remove leading and tailing tab, replace tab in mid to one space
            final_cell = final_cell.strip('\t')
            final_cell = final_cell.replace('\t', ' ')
            self._current_row.append(MyTableCell(final_cell, tag, self._attrs))
            self._current_cell = []
            self._attrs = []
        elif tag == 'tr':
            self._current_table.append(self._current_row)
            self._current_row = []
        elif tag == 'table':
            self.tables.append(self._current_table)
            self._current_table = []


def dump_cell_types(table):
    """ Return cell type in table, just for debugging """
    out_str = ''
    for i in range(len(table)):
        current_row = ""
        for j in range(len(table[i])):
            current_row += "None\t" if table[i][j] is None else table[i][j].cell_type + "\t"
        out_str += current_row + "\n"
    return out_str


class IncreaseUp(object):
    """ For generating auto-increase span id """

    def __init__(self, n):
        self.n = n

    def next(self):
        r = self.n
        self.n += 1
        return r


def gen_expand_table(origtable):
    """" The output of this function in an internal table, we use it to convert
    final output table. If output table likes:
    +--------------------+------+-------+--------+
    | Item / Desc.       | Qty. | @     | Price  |
    +--------------------+------+-------+--------+
    | Paperclips (Box)   | 100  | 1.15  | 115.00 |
    +--------------------+------+-------+--------+
    | Paper (Case)       | 10   | 45.99 | 459.90 |
    +--------------------+------+-------+--------+
    | Wastepaper Baskets | 2    | 17.99 | 35.98  |
    +--------------------+------+-------+--------+
    | Subtotal                          | 610.88 |
    +---------------------------+-------+--------+
    | Tax                       | 7%    | 42.76  |
    +---------------------------+-------+--------+
    | Total                             | 653.64 |
    +-----------------------------------+--------+
    Following cell_type would set in this function:
    th	th	th	th
    td	td	td	td
    td	td	td	td
    td	td	td	td
    span0	span0	span0	td
    span1	span1	td	td
    span2	span2	span2	td

    If the ID part in spanID is same, means it in same span cell.
    """
    logger.debug("origtable is:\n" + dump_cell_types(origtable))
    table_expand_spans = []
    for i in range(MAX_TABLE_ROWS):
        table_expand_spans.append([])
        for _ in range(MAX_TABLE_COLS):
            table_expand_spans[i].append(None)  # Initialize each cell to None

    span_id = IncreaseUp(0)

    pending_no_leader_span_cells = []
    for i in range(MAX_TABLE_ROWS):
        colspan_passed = 0

        # Check pending no leader span cells
        pending_span_cells_current_row = filter(lambda item: item[0] == i,
                                                pending_no_leader_span_cells)
        for item in pending_span_cells_current_row:
            logger.debug("normal span cell index is: %d, %d", item[0], item[1])
            colspan_passed += 1
            table_expand_spans[i][item[1]] = MyTableCell(None, item[2])
        if i > len(origtable) - 1:
            break
        for j in range(len(origtable[i])):
            colspan = origtable[i][j].get_colspan()
            assert colspan > 0, "Find colspan <= 0"
            rowspan = origtable[i][j].get_rowspan()
            assert rowspan > 0, "Find rowspan <= 0"

            try:
                # find index of first None cell, and insert data
                item_index = table_expand_spans[i].index(None)
                if colspan > 1 or rowspan > 1:
                    current_span_id = "span" + str(span_id.next())
                    table_expand_spans[i][item_index] = MyTableCell(
                        origtable[i][j].data, current_span_id,
                        origtable[i][j].attrs)
                else:
                    table_expand_spans[i][item_index] = origtable[i][j]
            except ValueError:  # If None is not found in table_expand_spans
                sys.stderr.write(
                    "Warn: your table has too many columns, only output partial result.\n"
                )
            for _ in range(colspan - 1):
                try:
                    # find index of first None cell, and insert spanID
                    item_index = table_expand_spans[i].index(None)
                    table_expand_spans[i][item_index] = MyTableCell(
                        None, current_span_id)
                except ValueError:  # If None is not found in table_expand_spans
                    sys.stderr.write(
                        "Warn: your table has too many columns, only output partial result.\n"
                    )
            # If current is leading span cell, save other cells in same span
            # unit into pending_no_leader_span_cells
            for x in range(rowspan - 1):
                for y in range(colspan):
                    logger.debug(
                        "add (%d,%d,%s) to pending_no_leader_span_cells",
                        i + x + 1, j + colspan_passed + y, current_span_id)
                    pending_no_leader_span_cells.append(
                        (i + x + 1, j + colspan_passed + y, current_span_id))
            colspan_passed += colspan - 1
    # shrink table_expand_spans
    max_rows = 0
    max_cols = 0
    for i in range(MAX_TABLE_ROWS):
        current_max_cols = 0
        for j in range(MAX_TABLE_COLS):
            if table_expand_spans[i][j] is not None:
                current_max_cols += 1
        if current_max_cols == 0:  # if entire row are None
            max_rows = i
            break
        if i == MAX_TABLE_ROWS - 1:
            sys.stderr.write(
                "Warn: your table has too many rows, only output partial result.\n"
            )
            max_rows = MAX_TABLE_ROWS
            break
        if current_max_cols > max_cols:
            max_cols = current_max_cols
    logger.debug("max_rows=%d, max_cols=%d", max_rows, max_cols)
    shrinked = [table_expand_spans[i][0:max_cols] for i in range(max_rows)]
    # If there is None cell in shrinked, change it to MyTableCell("", "none")
    # Following table is an example of this case
    # <table border="1">
    # <tr>
    #  <th>Header content 1</th>
    #  <th>Header content 2</th>
    # </tr>
    # <tr>
    #  <td>Body content 1</td>
    # </tr>
    # </table>
    for column in shrinked:
        for ix, elem in enumerate(column):
            if elem is None:
                column[ix] = MyTableCell("", "none")
    return shrinked


def is_span_cell(cell):
    """ Return true if cell has span type"""
    return cell.cell_type.startswith("span")


def is_leader_span_cell(cell):
    """ Return true if cell is the first(leader) span cell.
    Consider following table, There are 4 "cells" in each row.
    Pay attention to 4 cells in row 2.
    +-------------+------+-------+--------+
    |  Baskets    | 2    | 17.99 | 35.98  |
    +-------------+------+-------+--------+
    | Subtotal                   | 610.88 |
    +----------------------------+--------+
        ^            ^        ^
        |            |        |
     leader      continue    continue
    span cell    span cell   span cell
    """
    return cell.cell_type.startswith("span") and cell.data is not None


def is_continue_span_cell(cell):
    """ Return true if cell is span cell but not leader span cell"""
    return cell.cell_type.startswith("span") and cell.data is None


def is_same_span_id(cell1, cell2):
    """ Return true is cell1 and cell2 in same span"""
    return is_span_cell(cell1) and is_span_cell(cell2) \
           and cell1.cell_type == cell2.cell_type


def gen_output_cols_width(expand_table, output_style):
    """ Compute the width of each col that can hold cell data"""
    assert output_style in ['emacs', 'orgmode', 'mysql', 'markdown']
    if not expand_table:
        return []
    num_cols = len(expand_table[0])
    cols_max_width = [0 for _ in range(num_cols)]  # 0 repeated num_cols times
    leader_cells_colspan_ge_2 = []
    for i in range(len(expand_table)):
        for j in range(len(expand_table[i])):
            if is_leader_span_cell(expand_table[i][j]):
                if expand_table[i][j].get_colspan() == 1:
                    if _width(expand_table[i][j].data) > cols_max_width[j]:
                        cols_max_width[j] = _width(expand_table[i][j].data)
                else:
                    leader_cells_colspan_ge_2.append(
                        (i, j, expand_table[i][j].get_colspan()))
            if not is_span_cell(expand_table[i][j]):
                assert expand_table[i][j].get_colspan() == 1, \
                    "Colspan must be 1 in normal cell"
                if _width(expand_table[i][j].data) > cols_max_width[j]:
                    cols_max_width[j] = _width(expand_table[i][j].data)
    logger.debug("leader_cells_colspan_ge_2=%s", leader_cells_colspan_ge_2)
    spans = [x[2] for x in leader_cells_colspan_ge_2]  # x[2] is colspan value
    spans = sorted(set(spans))
    for span in spans:
        assert span >= 2, "Span of cell in leader_cells_colspan_ge_2 must >=2"
        leader_cells = [x for x in leader_cells_colspan_ge_2 if x[2] == span]
        for cell in leader_cells:
            ind_i = cell[0]
            ind_j = cell[1]
            if output_style == 'emacs':
                spans_widths = cols_max_width[ind_j:ind_j + span]
                total_width = sum(spans_widths) + (span - 1) * len(" | ")
                width_diff = total_width - \
                             _width(expand_table[ind_i][ind_j].data)
                # If content is span is to long, increase _width of last cell
                # in same span
                if width_diff < 0:
                    logger.debug("width_diff=%d", width_diff)
                    cols_max_width[ind_j + span - 1] += -width_diff
            else:
                width_diff = cols_max_width[ind_j] - \
                             _width(expand_table[ind_i][ind_j].data)
                # If content is span is to long, increase _width of first cell
                # in same span
                if width_diff < 0:
                    logger.debug("width_diff=%d", width_diff)
                    cols_max_width[ind_j] += -width_diff
    logger.debug("cols_max_width=%s", cols_max_width)
    return cols_max_width


def output_emacs_table(table, cols_max_width, column_align):
    if not table:
        return ""
    alignment = ['l' for _ in range(MAX_TABLE_COLS)]  # default is left align
    if column_align:
        if len(column_align) > MAX_TABLE_COLS:
            column_align = column_align[0:MAX_TABLE_COLS]
        for index, item in enumerate(column_align):
            alignment[index] = item
    out_str = ''
    for i in range(len(table)):
        # output horizontal line
        for j in range(len(table[i])):
            # output first character in horizontal line
            if i == 0:  # first row
                if j > 0 and is_same_span_id(table[i][j - 1], table[i][j]):
                    out_str += "-"
                else:
                    out_str += "+"
            else:
                if j == 0:  # first col
                    if is_same_span_id(table[i - 1][j], table[i][j]):
                        out_str += "|"
                    else:
                        out_str += "+"
                else:  # here, i>0, j>0
                    if is_same_span_id(table[i - 1][j - 1], table[i][j]):
                        out_str += " "
                    elif is_same_span_id(table[i - 1][j - 1], table[i - 1][j]) \
                         and is_same_span_id(table[i][j - 1], table[i][j]):
                        out_str += "-"
                    else:
                        out_str += "+"
            if i > 0 and is_same_span_id(table[i - 1][j], table[i][j]):
                out_str += " " * (1 + cols_max_width[j] + 1)
            else:
                out_str += "-" * (1 + cols_max_width[j] + 1)
        # last character in horizontal line
        if i > 0 and is_same_span_id(table[i - 1][len(table[i]) - 1],
                                     table[i][len(table[i]) - 1]):
            out_str += "|\n"
        else:
            out_str += "+\n"
        # output content
        for j in range(len(table[i])):
            # output first character
            if j == 0:
                out_str += "|"
            else:  # j > 0
                if i == 0:
                    if is_same_span_id(table[i][j - 1], table[i][j]):
                        # output nothing, there is leading span in previous cell
                        pass
                    else:
                        out_str += "|"
                else:  # i > 0
                    if is_same_span_id(table[i][j - 1], table[i][j]):
                        if is_same_span_id(table[i - 1][j], table[i][j]):
                            out_str += " "
                        else:
                            # output nothing, there is leading span in previous cell
                            pass
                    else:
                        out_str += "|"
            if not is_span_cell(table[i][j]):
                data_width = _width(table[i][j].data)
                diff = cols_max_width[j] - data_width
                if alignment[j] == 'l':
                    content = " " + table[i][j].data + " " * diff + " "
                elif alignment[j] == 'r':
                    content = " " + " " * diff + table[i][j].data + " "
                else:
                    raise Exception(
                        'Found invalid column align char {0}'.format(
                            alignment[j]))
                # logger.debug("normal cell[%d][%d] content=[%s]", i, j, content)
                out_str += content
            elif is_leader_span_cell(table[i][j]):
                data_width = _width(table[i][j].data)
                cell_colspan = table[i][j].get_colspan()
                cell_width = sum(cols_max_width[j:j + cell_colspan]) + \
                             len("   ") * (cell_colspan - 1)
                diff = cell_width - data_width
                # logger.debug("cell_colspan=[%s]", cell_colspan)
                # logger.debug("cell_width=[%s]", cell_width)
                # logger.debug("diff=[%s]", diff)
                if alignment[j] == 'l':
                    content = " " + table[i][j].data + " " * diff + " "
                elif alignment[j] == 'r':
                    content = " " + " " * diff + table[i][j].data + " "
                else:
                    raise Exception(
                        'Found invalid column align char {0}'.format(
                            alignment[j]))
                out_str += content
            elif i > 0 and is_same_span_id(table[i - 1][j], table[i][j]):
                # output empty string for rowspan
                out_str += " " + " " * cols_max_width[j] + " "
        out_str += "|\n"
    # output last line of current table
    last_row = len(table) - 1
    for j in range(len(table[0])):
        if j > 0 and is_same_span_id(table[last_row][j - 1],
                                     table[last_row][j]):
            out_str += "-"
        else:
            out_str += "+"
        out_str += "-" * (1 + cols_max_width[j] + 1)
    out_str += "+\n"
    return out_str


def output_table_no_span(table, cols_max_width, output_style, column_align,
                         no_header):
    r""" Output table to orgmode or mysql or markdown style.
    An example of orgmode style:
    | Name  | Phone | Age |
    |-------+-------+-----|
    | Peter |  1234 |  17 |
    | Anna  |  4321 |  25 |

    An example of mysql style:
    +-------+-------+-----+
    | Name  | Phone | Age |
    +-------+-------+-----+
    | Peter |  1234 |  17 |
    | Anna  |  4321 |  25 |
    +-------+-------+-----+

    An example of markdown style:
    | Name  | Phone | Age |
    |-------|-------|-----|
    | Peter |  1234 |  17 |
    | Anna  |  4321 |  25 |

    Note:
    Character | in cell need be escaped.
    In orgmode table:  convert | to \vert
    In markdown table: convert | to \|
    """
    assert output_style in ['orgmode', 'mysql', 'markdown']
    if not table:
        return ""
    cols_max_width_new = list(cols_max_width)  # copy list
    if output_style == 'mysql':
        # No escaping for |
        pass
    else:
        # Compute number of | in each column
        num_cols = len(table[0])
        nums_of_vert_bar = [0 for _ in range(num_cols)]
        for i in range(len(table)):
            for j in range(len(table[i])):
                if is_leader_span_cell(
                        table[i][j]) or not is_span_cell(table[i][j]):
                    count = table[i][j].data.count('|')
                    if count > nums_of_vert_bar[j]:
                        nums_of_vert_bar[j] = count
        if output_style == 'orgmode':
            escaped = '\\vert'
        elif output_style == 'markdown':
            escaped = '\\|'
        multi = len(escaped)
        cols_max_width_new = \
            [a + multi * b for a, b in zip(cols_max_width, nums_of_vert_bar)]

    alignment = ['l' for _ in range(MAX_TABLE_COLS)]  # default is left align
    if column_align:
        if len(column_align) > MAX_TABLE_COLS:
            column_align = column_align[0:MAX_TABLE_COLS]
        for index, item in enumerate(column_align):
            alignment[index] = item
    out_str = ''
    for i in range(len(table)):
        if i == 0 and output_style == 'mysql':
            # first horizontal line for mysql style
            for j in range(len(table[i])):
                if j == 0:
                    out_str += "+"
                else:
                    out_str += "+"
                out_str += "-" * (1 + cols_max_width_new[j] + 1)
            out_str += "+\n"
        if no_header:
            pass
        else:
            if i == 1:
                # output horizontal line
                for j in range(len(table[i])):
                    if j == 0:  # first character in horizontal line
                        if output_style == 'orgmode':
                            out_str += "|"
                        elif output_style == 'mysql':
                            out_str += "+"
                        elif output_style == 'markdown':
                            out_str += "|"
                    else:
                        if output_style == 'orgmode':
                            out_str += "+"
                        elif output_style == 'mysql':
                            out_str += "+"
                        elif output_style == 'markdown':
                            out_str += "|"
                    out_str += "-" * (1 + cols_max_width_new[j] + 1)
                # last character and newline in horizontal line
                if output_style == 'orgmode':
                    out_str += "|\n"
                elif output_style == 'mysql':
                    out_str += "+\n"
                elif output_style == 'markdown':
                    out_str += "|\n"
        # output content
        for j in range(len(table[i])):
            out_str += "|"
            if is_continue_span_cell(table[i][j]):
                content = " " + " " * cols_max_width_new[j] + " "
                out_str += content
            else:
                if output_style == 'mysql':
                    data_new = table[i][j].data
                else:
                    data_new = table[i][j].data.replace("|", escaped)
                data_width = _width(data_new)
                diff = cols_max_width_new[j] - data_width
                if alignment[j] == 'l':
                    content = " " + data_new + " " * diff + " "
                elif alignment[j] == 'r':
                    content = " " + " " * diff + data_new + " "
                else:
                    raise Exception(
                        'Found invalid column align char {0}'.format(
                            alignment[j]))
                out_str += content
        out_str += "|\n"
        if i == len(table) - 1 and output_style == 'mysql':
            # last horizontal line for mysql style
            for j in range(len(table[i])):
                if j == 0:
                    out_str += "+"
                else:
                    out_str += "+"
                out_str += "-" * (1 + cols_max_width_new[j] + 1)
            out_str += "+\n"
    return out_str


def gen_table_from_csv(csv_content, csv_delimiter):
    lines = csv_content.splitlines()
    readCSV = csv.reader(lines, delimiter=csv_delimiter)
    ret_tables = []
    current_table = []
    continuous_newlines = 0
    for row in readCSV:
        current_row = []
        if row:
            continuous_newlines = 0
            for element in row:
                current_row.append(MyTableCell(element, "csv"))
            current_table.append(current_row)
        else:
            continuous_newlines += 1
        # If found two continuous newlines, we consider new table is begin
        logger.debug("continuous_newlines=%d", continuous_newlines)
        if continuous_newlines >= 2 and current_table:
            logger.debug("recoginize csv trunck as table")
            ret_tables.append(current_table)
            current_table = []
    if current_table:
        logger.debug("recoginize csv trunck as table")
        ret_tables.append(current_table)
    return ret_tables


def csv_2_ascii_table(csv_content,
                      csv_delimiter=',',
                      output_style='orgmode',
                      column_align=None,
                      no_header=False):
    """ Convert csv to ascii table.

    Arguments:
    csv_content: Data of input csv.
    csv_delimiter: The delimiter of csv data (default is ',').
    output_style: The output style: emacs|orgmode|mysql|markdown
                  (default is 'orgmode').

    Returns:
    Ascii table
    """
    assert output_style in ['emacs', 'orgmode', 'mysql', 'markdown']
    output_str = ''
    for table in gen_table_from_csv(csv_content, csv_delimiter):
        table_expand_spans = gen_expand_table(table)
        logger.debug("cell types of table_expand_spans:\n" +
                     dump_cell_types(table_expand_spans))
        cols_max_width = gen_output_cols_width(table_expand_spans,
                                               output_style)
        logger.debug("cols_max_width=%s", cols_max_width)
        if output_style == 'emacs':
            output_str += output_emacs_table(table_expand_spans,
                                             cols_max_width, column_align)
        else:
            output_str += output_table_no_span(table_expand_spans,
                                               cols_max_width, output_style,
                                               column_align, no_header)
        output_str += "\n"  # output newline as the delimiter of multiple tables
    # logger.debug("Out put is:\n" + output_str)
    return output_str[:-1] if output_str.endswith("\n") else output_str


def html_2_ascii_table(html_content,
                       output_style='orgmode',
                       column_align=None,
                       no_header=False):
    """ Convert html table to ascii table.

    Arguments:
    html_content: Data of input html.
    output_style: The output style: emacs|orgmode|mysql|markdown
                  (default is 'orgmode').

    Returns:
    Ascii table
    """
    assert output_style in ['emacs', 'orgmode', 'mysql', 'markdown']
    output_str = ''
    parser = MyHTMLParser()
    parser.feed(html_content)
    for table in parser.tables:
        table_expand_spans = gen_expand_table(table)
        logger.debug("cell types of table_expand_spans:\n" +
                     dump_cell_types(table_expand_spans))
        cols_max_width = gen_output_cols_width(table_expand_spans,
                                               output_style)
        logger.debug("cols_max_width=%s", cols_max_width)
        if output_style == 'emacs':
            output_str += output_emacs_table(table_expand_spans,
                                             cols_max_width, column_align)
        else:
            output_str += output_table_no_span(table_expand_spans,
                                               cols_max_width, output_style,
                                               column_align, no_header)
        output_str += "\n"  # output newline as the delimiter of multiple tables
    # With Python 3.6, &nbsp; &#160; would convert to NO-BREAK SPACE(0xA0) by
    # HTMLParser. I expect it just convert to SPACE(0x20).
    output_str = output_str if sys.version_info[0] < 3 \
                            else output_str.replace(chr(0xA0), chr(0x20))
    # logger.debug("Out put is:\n" + output_str)
    return output_str[:-1] if output_str.endswith("\n") else output_str


def main_entry():
    input_file = None
    input_format = None
    output_file = None
    output_style = 'orgmode'  # default
    csv_delimiter = None
    no_header = False
    column_align = None
    width1_chars = None
    try:
        import argparse  # argparse is introduced in Python 2.7 and Python 3.2
        a_parser = argparse.ArgumentParser(
            description=
            'Yet Another Table Generator, convert CSV or html table to ASCII art table.'
        )
        a_parser.add_argument(
            "-i",
            "--input-file",
            type=argparse.FileType('r'),
            help="source file, read from stdin if not specified",
            metavar='INFILE',
            dest="input_file")
        a_parser.add_argument(
            "-f",
            "--input-format",
            help=
            "format of input file, can be 'html' or 'csv', auto guess it if not specified",
            dest="input_format",
            metavar='FORMAT',
            choices=["html", "csv"])
        a_parser.add_argument(
            "-d",
            "--csv-delimiter",
            help="delimiter of csv data, guess it if not specified",
            metavar='DELIMITER',
            dest="csv_delimiter")
        a_parser.add_argument(
            "-o",
            "--output-file",
            help="output file, write to stdout if not specified",
            metavar='OUTFILE',
            dest="output_file")
        a_parser.add_argument(
            "-s",
            "--output-style",
            help=
            "specify output table style, support 'orgmode', 'emacs', 'mysql', 'markdown', default is orgmode style",
            metavar='STYLE',
            default="orgmode",
            dest="output_style",
            choices=["emacs", "orgmode", "mysql", "markdown"])
        a_parser.add_argument(
            '--no-header',
            help=
            "horizontal header line would not be printed if this option present",
            action='store_true',
            dest="no_header")
        a_parser.add_argument(
            "--column-align",
            help=
            "specify align string of columns, support 'l/r'. For example, 'llrr' specify first two colums align left, 3rd and 4th columns align right. Default alignment is left.",
            metavar='ALIGN',
            dest="column_align")
        a_parser.add_argument(
            "--width1-chars",
            help=
            "specify chars that should consider one character width by force, only 'emoji' is supported currently. emoji is considered as WIDE in unicode, but most terminal render it only one character wide, you can set --width1-chars=emoji to make output aligned in your terminal",
            metavar='CHARS',
            dest="width1_chars",
            choices=["emoji"])
        args = a_parser.parse_args()
        input_file = args.input_file
        input_format = args.input_format
        output_file = args.output_file
        output_style = args.output_style
        csv_delimiter = args.csv_delimiter
        no_header = args.no_header
        width1_chars = args.width1_chars
        column_align = args.column_align
    except ImportError:
        sys.stderr.write(
            "Warn: Cannot import argparse. Read from stdin, convert to {0} style always\n".
            format(output_style))
    if column_align is not None:
        if column_align.replace("l", "").replace("r", ""):
            sys.stderr.write(
                "--column-align is not valid, only l/r is supported.\n")
            sys.exit(1)
    if width1_chars == 'emoji':
        try:
            import emoji
        except ImportError:
            sys.stderr.write(
                "Error: force emoji be one character width need package emoji, please install it.\n"
            )
            sys.exit(1)
        FORCE_WIDTH1_CHARS.append('emoji')
    if input_file:  # read from file
        input_content = input_file.read()
        input_file.close()
    else:  # read from stdin
        sys.stderr.write(
            "Info: read data from stdin, press Ctrl-D (i.e. EOF) when finished\n"
        )
        input_content = sys.stdin.read()
    if input_format is None:
        # Guess input_format is html if it starts with <
        if input_content.strip(" \r\n\t").startswith("<"):
            input_format = "html"
        else:
            input_format = "csv"
        logger.debug("Guess input_format as [%s]", input_format)
    if input_format == "csv" and csv_delimiter is None:
        # Guess csv_delimiter
        firstline = input_content.strip(" \r\n").split('\n', 1)[0]
        candidate = [",", "\t", ";", "|"]
        occurs = [firstline.count(c) for c in candidate]
        best_index = occurs.index(max(occurs))  # index of max item
        csv_delimiter = candidate[best_index]
        if firstline:  # not empty
            sys.stderr.write(
                "Info: auto set character [{0}] as csv_delimiter.\n".format(
                    csv_delimiter))
    out_content = ""
    if input_format == "html":
        out_content = html_2_ascii_table(input_content, output_style,
                                         column_align, no_header)
    elif input_format == "csv":
        out_content = csv_2_ascii_table(input_content, csv_delimiter,
                                        output_style, column_align, no_header)
    if out_content:
        if output_file is None:
            sys.stdout.write(out_content)
        else:
            outfile = open(output_file, 'w')
            outfile.write(out_content)
            outfile.close()
    else:
        sys.stderr.write(
            "Warn: No table is generated, may be your input data is empty or unnormal.\n"
        )


if __name__ == '__main__':
    main_entry()
