""" Utilities for pretty printing table data and more
    Author: A.Tsaregorodtsev
"""
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division

__RCSID__ = "$Id$"

import six
from six import StringIO


def int_with_commas(inputValue):
    """Utility to make a string of a large integer with comma separated degrees
        of thousand

    :param int inputValue: value to be interpreted
    :return: output string
    """
    s = str(inputValue)
    news = ""
    while len(s) > 0:
        news = s[-3:] + "," + news
        s = s[:-3]
    return news[:-1]


def printTable(fields, records, sortField="", numbering=True, printOut=True, columnSeparator=" "):
    """Utility to pretty print tabular data

    :param list fields: list of column names
    :param list records: list of records, each record is a list or tuple of field values
           where field value itself can be a string or a list of strings or a dictionary
           of the structure { "Value": string_value, 'Just': 'L|R|C' } to specify justification
    :param str sortField: name of the column by which the output will be sorted
    :param bool numbering: flag for numbering rows
    :param bool printOut: flag for printing into the stdout
    :param str columnSeparator: string to be used as a column separator
    :return: pretty table string
    """

    def __writeField(buffer, value, length, columnSeparator, lastColumn=False):

        justification = None
        if isinstance(value, dict):
            justification = value.get("Just")
            value = value.get("Value", "")

        if justification is None:
            # try casting to int and then align to the right, if it fails align to the left
            justification = "r"
            try:
                _ = int("".join(value.split(",")))
            except ValueError:
                justification = "l"

        if justification.lower() == "l":
            output = value.ljust(length) + columnSeparator
        elif justification.lower() == "r":
            output = value.rjust(length) + columnSeparator
        elif justification.lower() == "c":
            margin = length - len(value)
            if margin <= 1:
                output = value.ljust(length) + columnSeparator
            else:
                m1 = int(margin / 2)
                m2 = margin - m1
                output = " " * m1 + value + " " * m2 + columnSeparator
        if lastColumn:
            output = output.rstrip()
        buffer.write(output)
        return len(output)

    if not records:
        if printOut:
            print("No output")
        return "No output"

    # Strip all strings
    fieldList = [f.strip() for f in fields]
    recordList = []
    for record in records:
        strippedRecord = []
        for fieldValue in record:
            if isinstance(fieldValue, six.string_types):
                strippedRecord.append(fieldValue.strip())
            elif isinstance(fieldValue, list):
                strippedList = []
                for ll in fieldValue:
                    if isinstance(ll, six.string_types):
                        strippedList.append(ll.strip())
                    elif isinstance(ll, dict):
                        ll["Value"] = ll["Value"].strip()
                        strippedList.append(ll)
                    else:
                        out = "Wrong type for field value: %s" % type(ll)
                        if printOut:
                            print(out)
                        return out
                strippedRecord.append(strippedList)
            elif isinstance(fieldValue, dict):
                itemValue = fieldValue["Value"]
                if isinstance(itemValue, six.string_types):
                    itemValue = itemValue.strip()
                    fieldValue.update({"Value": itemValue})
                    strippedRecord.append(fieldValue)
                elif isinstance(itemValue, list):
                    itemValue = [r.strip() for r in itemValue]
                    fieldValue.update({"Value": itemValue})
                    strippedRecord.append(fieldValue)
                else:
                    out = "Wrong type for field value: %s" % type(itemValue)
                    if printOut:
                        print(out)
                    return out
        recordList.append(strippedRecord)

    nFields = len(fields)
    for rec in records:
        if nFields != len(rec):
            out = "Incorrect data structure to print, nFields %d, nRecords %d" % (nFields, len(rec))
            if printOut:
                print(out)
            return out

    if sortField:
        recordList.sort(key=lambda x: x[fieldList.index(sortField)])

    # Compute the maximum width for each field
    fieldWidths = []
    for i in range(nFields):
        fieldWidths.append(len(fieldList[i]))
        for record in recordList:
            if isinstance(record[i], list):
                for item in record[i]:
                    if isinstance(item, dict):
                        itemValue = item["Value"]
                    else:
                        itemValue = item
                    fieldWidths[i] = max(len(itemValue), fieldWidths[i])
            elif isinstance(record[i], dict):
                fieldValue = record[i]["Value"]
                if isinstance(fieldValue, list):
                    fieldWidths[i] = max(max(len(item) for item in fieldValue), fieldWidths[i])
                else:
                    fieldWidths[i] = max(len(fieldValue), fieldWidths[i])
            else:
                fieldWidths[i] = max(len(record[i]), fieldWidths[i])

    numberWidth = len(str(len(recordList))) + 1
    separatorWidth = len(columnSeparator)
    totalLength = sum(fieldWidths) + separatorWidth * nFields
    if numbering:
        totalLength += numberWidth + separatorWidth

    # Accumulate the table output in the stringBuffer now
    stringBuffer = StringIO()
    topLength = (numberWidth + separatorWidth) if numbering else 0
    stringBuffer.write(" " * (topLength))

    for i in range(nFields):
        lastColumn = False if i < nFields - 1 else True
        length = __writeField(stringBuffer, fieldList[i], fieldWidths[i], columnSeparator, lastColumn)
        topLength += length
    stringBuffer.write("\n")
    if columnSeparator == " ":
        stringBuffer.write("=" * topLength + "\n")
    else:
        stringBuffer.write("=" * totalLength + "\n")

    for count, record in enumerate(recordList):
        total = count == len(recordList) - 1 and recordList[-1][0] == "Total"
        if numbering:
            if total:
                # Do not number the line with the total
                stringBuffer.write(" " * (numberWidth + separatorWidth))
            else:
                stringBuffer.write(str(count + 1).rjust(numberWidth) + columnSeparator)

        listMode = 0
        for item in record:
            if isinstance(item, list):
                listMode = max(len(item), listMode)
            elif isinstance(item, dict) and isinstance(item["Value"], list):
                listMode = max(len(item["Value"]), listMode)

        for i, (fieldValue, fieldWidth) in enumerate(zip(record, fieldWidths)):
            lastColumn = False if i < nFields - 1 else True
            value = fieldValue
            if isinstance(fieldValue, list):
                value = fieldValue[0]
            elif isinstance(fieldValue, dict) and isinstance(fieldValue["Value"], list):
                value = dict(fieldValue)
                value.update({"Value": value["Value"][0]})

            __writeField(stringBuffer, value, fieldWidth, columnSeparator, lastColumn)

        # If the field has a list type value, print out one value per line
        if listMode:
            stringBuffer.write("\n")
            for ll in range(1, listMode):
                # Do not number continuation lines
                if numbering:
                    stringBuffer.write(" " * (numberWidth + separatorWidth))
                for i, (fieldValue, fieldWidth) in enumerate(zip(record, fieldWidths)):
                    lastColumn = False if i < nFields - 1 else True
                    valueList = fieldValue
                    if isinstance(valueList, list) and ll < len(valueList):
                        value = valueList[ll]
                    elif (
                        isinstance(valueList, dict)
                        and isinstance(valueList["Value"], list)
                        and ll < len(valueList["Value"])
                    ):
                        value = dict(valueList)
                        value.update({"Value": valueList["Value"][ll]})
                    else:
                        value = ""

                    __writeField(stringBuffer, value, fieldWidth, columnSeparator, lastColumn)

                stringBuffer.write("\n")

        if not listMode:
            stringBuffer.write("\n")
        if total:
            stringBuffer.write("-" * totalLength + "\n")

    output = stringBuffer.getvalue()
    if printOut:
        print(output)

    return output


def printDict(dDict, printOut=False):
    """Utility to pretty print a dictionary

    :param dict dDict: Dictionary to be printed out
    :param bool printOut: flag to print to the stdout
    :return: pretty dictionary string
    """
    lines = []
    keyLength = 0
    for key in dDict:
        if len(key) > keyLength:
            keyLength = len(key)
    for key in sorted(dDict):
        line = "%s: " % key
        line = line.ljust(keyLength + 2)
        value = dDict[key]
        if isinstance(value, (list, tuple)):
            line += ",".join(list(value))
        else:
            line += str(value)
        lines.append(line)
    output = "{\n%s\n}" % "\n".join(lines)
    if printOut:
        print(output)
    return output
