"""This is to aid the documentation by turning the data dictionary tables into
numbered lists that can be added to ReST documentation. This is not
really intended for end user usage, but is documented and handled in exactly
the same way as the other scripts.

This requires the input file to be tab delimited and have a header of:

1. ``column`` (``int``) - The column number.
2. ``name`` - (``str``) - The column name.
3. ``data_type`` (``str``) - The data type, such as ``string`` or ``integer``.
4. ``data_type_length`` (``int``) - An optional length of the data type, only
   used for strings.
5. ``description`` (``str``) - A long form description of the column.

The order of the columns is not important. The description can contain
restructured text markup.

It will output the format:
``<COLUMN_NUMBER>. <COLUMN_NAME> (<DATA_TYPE>(<LENGTH>)) - <DESCRIPTION>``
"""
import argparse
import csv
import re
import pathlib
# import pprint as pp

_PROG_NAME = 'doc-column-list'
"""The program name (`str`)
"""
_DESC = __doc__
"""The program description (`str`)
"""


COL_NUMBER_FIELD = 'column'
"""The name of the column number column in the header (`str`)
"""
COL_NAME_FIELD = 'name'
"""The name of the column name column in the header (`str`)
"""
DATA_TYPE_FIELD = 'data_type'
"""The name of the data type column in the header (`str`)
"""
DATA_DESC_FIELD = 'description'
"""The name of the data type description column in the header (`str`)
"""
DATA_TYPE_LEN_FIELD = 'data_type_length'
"""The name of the data type length column in the header (`str`)
"""
DELIMITER = "\t"
"""The expected delimiter of the input file (`str`)
"""


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def main():
    """The main entry point for the script.
    """
    # Initialise and parse the command line arguments
    parser = _init_cmd_args()
    args = _parse_cmd_args(parser)

    with open(args.infile, 'rt') as infile:
        reader = csv.DictReader(infile, delimiter=DELIMITER)

        with open(args.outfile, 'wt') as outfile:
            for idx, row in enumerate(reader, 1):
                row[COL_NAME_FIELD] = _add_backticks(row[COL_NAME_FIELD])
                _process_data_type(row)

                # Add a full stop to the description if needed
                try:
                    if not row[DATA_DESC_FIELD].endswith('.'):
                        row[DATA_DESC_FIELD] = row[DATA_DESC_FIELD] + '.'
                except IndexError:
                    row.append("")

                # fixed width formatting gaps
                row[DATA_DESC_FIELD] = re.sub(
                    r'([\.,])``', '\\1 ``', row[DATA_DESC_FIELD]
                )

                outstr = "{0}. {1} ({2}) - {3}\n".format(
                    idx, row[COL_NAME_FIELD], row[DATA_TYPE_FIELD],
                    row[DATA_DESC_FIELD]
                )
                outfile.write(outstr)


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def _init_cmd_args():
    """Initialise the command line arguments and return the parser.

    Returns
    -------
    args : :obj:`argparse.ArgumentParser`
        The argparse parser object with arguments added
    """
    parser = argparse.ArgumentParser(
        description=_DESC
    )
    parser.add_argument(
        'infile', type=str, help="The input file describing the columns."
    )
    parser.add_argument(
        'outfile', type=str, help="The output rst file."
    )

    return parser


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def _parse_cmd_args(parser):
    """Parse the command line arguments.

    Parameters
    ----------
    parser : :obj:`argparse.ArgumentParser`
        The argparse parser object with arguments added.

    Returns
    -------
    args : :obj:`argparse.Namespace`
        The argparse namespace object containing the arguments
    """
    args = parser.parse_args()
    args.infile = pathlib.Path(args.infile)
    args.outfile = pathlib.Path(args.outfile)
    if args.infile.resolve() == args.outfile.resolve():
        raise ValueError("input and output paths are the same (post resolve)")
    return args


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def _add_backticks(test_str):
    """Add backticks to a string if they do not already exist.

    Parameters
    ----------
    test_str : `str`
        The string to update.

    Returns
    -------
    processed_str : `str`
        A string with backticks added at the start/end.
    """
    if not test_str.startswith('``'):
        test_str = '``' + test_str
    if not test_str.endswith('``'):
        test_str = test_str + '``'

    return test_str


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def _process_data_type(row):
    """Process the data type and add the optional length. It is updated
    inplace.

    Parameters
    ----------
    row : `dict`
        The input row.
    """
    row[DATA_TYPE_FIELD] = row[DATA_TYPE_FIELD].strip().lower()
    if row[DATA_TYPE_FIELD] in ['str', 'string'] and \
       DATA_TYPE_LEN_FIELD in row:
        # Remove any existing backticks
        row[DATA_TYPE_FIELD] = re.sub(r"`+", "", row[DATA_TYPE_FIELD])
        row[DATA_TYPE_FIELD] = \
            f"{row[DATA_TYPE_FIELD]}({row[DATA_TYPE_LEN_FIELD]})"

    row[DATA_TYPE_FIELD] = _add_backticks(row[DATA_TYPE_FIELD])


# @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
if __name__ == '__main__':
    main()
