#!/usr/bin/env python3
# coding=utf-8
"""Convert GSI Compact RINEX into Standard RINEX,
using concurent.futures.

The convert function rely on RNXCMP software. Check if you have
installed RNXCMP by typing `crx2rnx -h` in cmd.

:author: Jon Jiang
:email: jiangyingming@live.com
"""
from concurrent import futures
from textwrap import shorten
import argparse
import glob
import itertools
import os
import subprocess
import sys

import tqdm

MAX_THREADING = max(6, os.cpu_count())


def crx2rnx(src_file, out_dir, keep):
    """Convert compact RINEX file to standard RINEX."""
    filename = os.path.basename(src_file)
    if filename.lower().endswith('crx'):
        dst_file = os.path.join(out_dir, filename[0:-3]+'rnx')
    else:
        dst_file = os.path.join(out_dir, filename[0:-1]+'o')
    # run crx2rnx, redirect standard RINEX stdout into destination file
    # and ignore the stderr.
    args = 'crx2rnx', '-', src_file
    with open(dst_file, 'w') as dst_writer:
        status = subprocess.call(
            args, stdout=dst_writer, stderr=subprocess.DEVNULL)
    # check exit status of crx2rnx: {0: success, 1: error, 2: warning}
    if status == 1:
        # if run crx2rnx failed, remove dest file and return filename
        os.remove(dst_file)
        return filename
    # remove source file if keep is False when successful
    if not keep:
        os.remove(src_file)

    return


def parallel_run(function, argvs):
    """Parallel run function using argvs, display a process bar."""
    # check platform, use ASCII process bar in Windows
    use_ascii = True if sys.platform == 'win32' else False
    with futures.ThreadPoolExecutor(max_workers=MAX_THREADING) as executor:
        todo_list = [executor.submit(function, *argv) for argv in argvs]
        task_iter = futures.as_completed(todo_list)
        failed_files = []
        for future in tqdm.tqdm(
                task_iter, total=len(todo_list), ascii=use_ascii, unit='file'):
            # return None means task is success
            res = future.result()
            if res:
                failed_files.append(res)

    return failed_files


def init_args():
    """Initilize function, parse user input"""
    # initilize a argument parser
    parser = argparse.ArgumentParser(
        description='Convert GSI Compact RINEX into Standard RINEX.'
    )
    # add arguments
    parser.add_argument('-v', '--version', action='version',
                        version='%(prog)s 0.2.3')
    parser.add_argument('-k', '--keep', action='store_true',
                        help='keep original file')
    parser.add_argument('-r', '--recursive', action='store_true',
                        help='search file recursively')
    parser.add_argument('-out', metavar='<directory>', default='rinex',
                        help='output directory [default: rinex in current]')
    parser.add_argument('files', metavar='<file>', nargs='+',
                        help='file will be processed')

    return parser.parse_args()


def main():
    """Main function."""
    args = init_args()
    globstrs, out_dir = args.files, args.out
    keep_src, recursive = args.keep, args.recursive
    # create output directory
    os.makedirs(out_dir, exist_ok=True)
    # collect input globstrs into a glob list
    globs = [glob.iglob(globstr, recursive=recursive) for globstr in globstrs]
    # make input args for crx2rnx function
    conv_args = ((src, out_dir, keep_src) for src in itertools.chain(*globs))
    print('Start processing: {}'.format(shorten(', '.join(globstrs), 62)))
    if not keep_src:
        print('Delete source files when complete')
    # start parallel task, get a file name list of convert failed.
    failed = parallel_run(crx2rnx, conv_args)
    if failed:
        print('\nConvert failed filename: {}'.format(', '.join(failed)))
    else:
        print('\nAll convert tasks are finished!')


if __name__ == '__main__':
    main()