"""
Recode a file to a specified encoding
"""
from pyaddons import __version__, __name__ as pkg_name, file_helper, gzopen
from simple_progress import progress
import argparse
import sys
import os
import gzip
import re
import tempfile
import shutil
import warnings

MSG_PREFIX = '[info]'
MSG_OUT = sys.stderr


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def main():
    """
    The main entry point to build an indexed wide file
    """
    # Initialise and parse the command line arguments
    parser = init_create_cmd_args()
    args = parse_create_cmd_args(parser)

    # Start a msg outputter, this will respect the verbosity and will output to
    # STDERR
    m = progress.Msg(prefix='', verbose=args.verbose, file=MSG_OUT)
    m.msg("= recode file ({0} v{1}) =".format(
        pkg_name, __version__))
    m.prefix = MSG_PREFIX
    m.msg_atts(args)

    # Make sure the wide file can be opened only if input is not from STDIN
    if args.file_path != '-':
        open(args.file_path).close()

    # Initialise the building of the index file. When used on the command line
    # the default is to sniff the csv dialect
    recode(
        args.file_path,
        args.outfile,
        args.recode,
        tmp_dir=args.tmp_dir,
        verbose=args.verbose
    )

    m.msg("*** END ***")


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def init_create_cmd_args():
    """
    Initialise the command line arguments and return the parsed arguments

    Returns
    -------
    parser : :obj:`argparse.ArgumentParser`
        The argument parser with the arguments set up
    """
    parser = argparse.ArgumentParser(
        description="read in a file and output a file that has been recoded"
        " in the specified format"
    )

    # The wide format UKBB data fields filex
    parser.add_argument('file_path', type=str, default='-',
                        help="The path to the file file, input from STDIN "
                        "use - for input file")
    parser.add_argument('outfile', type=str,
                        help="An output indexed file path. The output file "
                        "will be bgzipped, as part of the indexing process")
    parser.add_argument('-v', '--verbose',  action="store_true",
                        help="Give more output")
    parser.add_argument('-r', '--recode',  type=str, default='utf-8',
                        help="the file encoding that is required "
                        "(default=utf-8)")
    parser.add_argument('-t', '--tmp-dir',  type=str,
                        help="an alternative tmp-dir location "
                        "(default=system tmp)")

    return parser


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

    Parameters
    ----------
    parser : :obj:`argparse.ArgumentParser`
        The argument parser with the arguments set up

    Returns
    -------
    args : :obj:`argparse.Namespace`
        The argparse namespace object
    """
    args = parser.parse_args()

    # Make sure ~/ etc... is expanded
    for i in ['file_path', 'outfile']:
        # If input is from STDIN then do not check
        if i == 'file_path' and getattr(args, i) == '-':
            continue

        # Expand the file paths
        setattr(args, i, os.path.expanduser(getattr(args, i)))

    return args


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def recode(file_path, outfile, to='utf-8', tmp_dir=None, verbose=False):
    """
    Recode a file to the required encoding

    Parameters
    ----------
    file_path : str
        The location of the file to be indexed, this may or may not be
        compressed. If it is a dash (-) then input from STDIn is expected
    outfile : str
        The location of the BGZIP compressed output file that will have been
        indexed. The outfile is allowed to be the same as the input wide file
    verbose : bool, optional, default: False
    to : str, optional, default: 'utf-8'
        The required encoding of the file
    tmp_dir : str, optional, default: NoneType
        An alternative location for tmp
    """
    try:
        display_out = outfile
        temp_out_name = None

        # Now make sure we have set up the input file correctly, the user must
        # supply a - if they are giving input from STDIN
        if file_path == '-':
            infile = sys.stdin.buffer
        else:
            # GZIP agnostic, so does not care if compressed or uncompressed
            infile = gzopen.gzip_agnostic(file_path, 'b')
            # infile = open(file_path, 'rb')

        if outfile == '-':
            display_out = "STDOUT"
            sys.stdout.reconfigure(encoding=to)
            outfile_obj = sys.stdout
        elif re.search(r'\.b?gz$', outfile):
            # tmp output file
            temp_out_name = _get_temp_file(dir=tmp_dir)

            # GZIP agnostic, so does not care if compressed or uncompressed
            outfile_obj = gzip.open(temp_out_name, 'wt', encoding=to)
        else:
            # tmp output file
            temp_out_name = _get_temp_file(dir=tmp_dir)

            # GZIP agnostic, so does not care if compressed or uncompressed
            outfile_obj = open(temp_out_name, 'wt', encoding=to)

        prog = progress.RateProgress(
            verbose=verbose,
            file=MSG_OUT,
            default_msg="outputting '{0}' to '{1}'".format(display_out, to)
        )

        for row_no, row in enumerate(prog.progress(infile), 1):
            try:
                outfile_obj.write(row.decode(to, "replace"))
            except UnicodeDecodeError:
                # Still can't do it
                outfile_obj.write(row.decode(to), "ignore")
                warnings.warn(
                    "unicode error ignored at line: {0}".format(row_no)
                )
            except Exception as e:
                msg = "exception at row {0} in input file".format(row_no)
                raise TypeError(msg) from e
    except Exception:
        if outfile != '-':
            outfile_obj.close()
            os.unlink(temp_out_name)
        infile.close()
        raise

    if outfile != '-':
        outfile_obj.close()
        shutil.move(temp_out_name, outfile)

    # Close files
    infile.close()


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def _get_temp_file(**kwargs):
    """
    Get the path to a temp file

    Parameters
    ----------
    **kwargs
        Arguments to `tempfile.mkstemp`

    Returns
    -------
    temp_file_name : str
        The path to a temp file
    """
    temp_file_obj, temp_file_name = tempfile.mkstemp(**kwargs)
    os.close(temp_file_obj)
    return temp_file_name


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