"""Get the min/max line length in characters in a file. This outputs:
<min_len>  <min_len_idx> <max_len> <max_idx>

Where min/max_len_idx are the one based line numbers of the min and max length
rows.
"""
from pyaddons import (
    __version__,
    __name__ as pkg_name,
    log,
    utils
)
from tqdm import tqdm
import argparse
import os
import sys
# import pprint as pp

_PROG_NAME = "line-length"
"""The name of the program that doubles as the logger name (`str`)
"""
_DESC = __doc__
"""The program description given to argparse (`str`)
"""


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def main():
    """The main entry point. For API access see
    ``umls_tools.mapping.map_file``
    """
    parser = _init_cmd_args()
    args = _parse_cmd_args(parser)

    logger = log.init_logger(_PROG_NAME, verbose=args.verbose)
    log.log_prog_name(logger, pkg_name, __version__)
    log.log_args(logger, args)

    verbose = False
    if int(args.verbose) > 1:
        verbose = True

    try:
        min_len, min_idx, max_len, max_idx = line_length(
            args.infile, verbose=verbose
        )
        log.log_end(logger)
        print(min_len, min_idx, max_len, max_idx)
    except (OSError, FileNotFoundError):
        raise
    except (BrokenPipeError, IOError, KeyboardInterrupt):
        # Python flushes standard streams on exit; redirect remaining
        # output to devnull to avoid another BrokenPipeError at shutdown
        devnull = os.open(os.devnull, os.O_WRONLY)

        try:
            os.dup2(devnull, sys.stdout.fileno())
        except Exception:
            pass
        log.log_interrupt(logger)


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def _init_cmd_args():
    """Use argparse to parse the command line arguments.

    Returns
    -------
    parser : `argparse.ArgumentParser`
        The arguments expected on the command line.
    """
    parser = argparse.ArgumentParser(
        description=_DESC
    )

    parser.add_argument(
        'infile', type=str,
        help="The input file path (can be gzip compressed)"
    )
    parser.add_argument(
        '-v', '--verbose', default=0, action="count",
        help="give more output, ``--vv`` turns on progress monitoring."
    )
    return parser


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def _parse_cmd_args(parser):
    """Use argparse to parse the command line arguments.

    Parameters
    ----------
    parser : `argparse.ArgumentParser`
        The arguments expected on the command line.

    Returns
    -------
    args : `ArgumentParser.args`
        The arguments from parsing the cmd line args.
    """
    args = parser.parse_args()
    return args


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def line_length(infile_path, verbose=False):
    """Calculate the shortest and longest line lengths (in characters) in a
    file.

    Parameters
    ----------
    infile_path : `str`
        The path to the input file (can be compressed).
    verbose : `bool`, optional, default: `False`
        Show a progress monitor.

    Returns
    -------
    min_line_len : `int`
        The length in characters of the shortest line in the file.
    min_line_idx : `int`
        The one based line number of the shortest line in the file.
    max_line_len : `int`
        The length in characters of the longest line in the file.
    max_line_idx : `int`
        The one based line number of the longest line in the file.
    """
    tqdm_kwargs = dict(
        desc="[info] reading file", unit=" rows", disable=not verbose
    )
    # Initialise
    min_len = None
    max_len = 0
    min_idx = 0
    max_idx = 0
    in_method = utils.get_open_method(infile_path)
    with in_method(infile_path, 'rt') as infile:
        for idx, row in enumerate(tqdm(infile, **tqdm_kwargs), ):
            ll = len(row)
            try:
                if ll < min_len:
                    min_len = ll
                    min_idx = idx
            except TypeError:
                if min_len is not None:
                    raise
                min_len = ll
            if ll > max_len:
                max_len = ll
                max_idx = idx
    return min_len, min_idx, max_len, max_idx


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