from __future__ import print_function, unicode_literals
import os
import sys
from warnings import warn
from argparse import ArgumentParser, Action, RawDescriptionHelpFormatter
from ._getreader import getreaderdict, pncopen
from ._getwriter import getwriterdict
from .conventions.ioapi import add_ioapi_from_cf
from .conventions.ioapi import add_cf_from_wrfioapi, add_cf_from_ioapi
from PseudoNetCDF import PseudoNetCDFFile
from PseudoNetCDF.netcdf import NetCDFFile
try:
    from netCDF4 import MFDataset
except ImportError:
    pass


from .sci_var import reduce_dim, mesh_dim, slice_dim, getvarpnc, extract
from .sci_var import mask_vals, seqpncbo, pncexpr, stack_files, add_attr
from .sci_var import convolve_dim, manglenames, removesingleton, merge
from .sci_var import extract_from_file, pncrename, WrapPNC


from argparse import SUPPRESS


conv2conv = {}
conv2conv[('ioapi', 'cf')] = add_cf_from_ioapi
conv2conv[('wrfioapi', 'cf')] = add_cf_from_wrfioapi
conv2conv[('cf', 'ioapi')] = add_ioapi_from_cf

allreaders = getreaderdict()
allwriters = getwriterdict()
globals().update(allreaders)
_readernames = [(k.count('.') if k[:1] != '_' else 9999, k)
                for k in getreaderdict().keys()]
_readernames.sort()
_readernames = [k for c, k in _readernames]
_writernames = [(k.count('.'), k) for k in getwriterdict().keys()]
_writernames.sort()
_writernames = [k for c, k in _writernames]
_coordkeys = ("time time_bounds TFLAG ETFLAG latitude latitude_bounds " +
              "longitude longitude_bounds lat lat_bnds lon lon_bnds " +
              "etam_pressure etai_pressure layer_bounds layer47 layer").split()


class PNCArgumentParser(ArgumentParser):
    def exit(self, status=0, message=None):
        import sys as _sys
        if message:
            self._print_message(message, _sys.stderr)


class _HelpAction(Action):
    def __init__(self,
                 option_strings,
                 dest=SUPPRESS,
                 default=SUPPRESS,
                 help=None):
        super(_HelpAction, self).__init__(
            option_strings=option_strings,
            dest=dest,
            default=default,
            nargs=0,
            help=help)

    def __call__(self, parser, namespace, values, option_string=None):
        parser.print_help()
        setattr(namespace, self.dest, True)


class _HelpListFormats(Action):
    def __init__(self,
                 option_strings,
                 dest=SUPPRESS,
                 default=SUPPRESS,
                 help=None):
        super(_HelpListFormats, self).__init__(
            option_strings=option_strings,
            dest=dest,
            default=default,
            nargs=0,
            help=help)

    def __call__(self, parser, namespace, values, option_string=None):
        print('The formats listed below are available ' +
              'for the following options')
        print('-f FORMAT or --format FORMAT or --help-format FORMAT')
        print('where FORMAT is one of the options below')
        print('\t' + '\n\t'.join(_readernames))
        print('**Some readers are listed twice (e.g., without dotted form)')
        setattr(namespace, 'help', True)


class _HelpFormat(Action):
    def __init__(self,
                 option_strings,
                 dest=SUPPRESS,
                 default=SUPPRESS,
                 help=None):
        super(_HelpFormat, self).__init__(
            option_strings=option_strings,
            dest=dest,
            default=default,
            nargs=1,
            help=help)

    def __call__(self, parser, namespace, values, option_string=None):
        file_format = values[0].split(',')[0]
        print('')
        print('All formats require a "path". Some formats also ')
        print('take extra arguments. All arguments other than ')
        print('the input path must be specified using keyword')
        print('arguments.')
        print('')
        helpformat = allreaders[file_format]
        try:
            import inspect
            print('Example:')
            idef = inspect.getargspec(helpformat.__init__)
            args = idef.args[2:]
            if idef.defaults is None:
                defs = ['<VAL>'] * len(args)
            else:
                defs = (len(args) - len(idef.defaults)) * \
                    ['<VAL>'] + list(idef.defaults)
            longform = (
                'pncdump -f ' +
                ','.join([file_format] +
                         [karg + "=%s" % kdef
                          for karg, kdef in zip(args, defs)]) + ' path')
            shortform = (
                'pncdump -f ' +
                ','.join([file_format] +
                         [karg + "=%s" % kdef
                          for karg, kdef in zip(args, defs)
                          if kdef == '<VAL>']) +
                ' path')
            print(shortform)
            if longform != shortform:
                print('')
                print('Extended example with keywords:')
                print(longform)
                print('')
                print('* Any keyword with a non "<VAL>" default ' +
                      'can be omitted.')
            print('')
        except Exception:
            print('** Short help not available for ' + file_format)
            print('')

        if 'y' == input('Hit Y/y to see detailed help\n').lower():
            help(helpformat)
        setattr(namespace, self.dest, values)
        setattr(namespace, 'help', True)


class AggCommaString(Action):
    def __call__(self, parser, namespace, values, option_string=None):
        setattr(namespace, self.dest, values.split(','))


_plotcmds = ['plot', 'plot2d', 'plotts', 'plotprofile', 'plotscatter']
_allcmds = ['gen', 'dump', 'eval', 'map'] + _plotcmds


def add_basic_options(parser):
    padd = parser.add_argument
    parser.add_argument('ifiles', nargs='*',
                        help='path to a file formatted as type -f')
    parser.add_argument("--verbose", dest="verbose", action="count",
                        default=0, help="Provides verbosity with pncgen")

    try:
        padd("--help",
             dest="help", action=_HelpAction, default=False,
             help="Displays help")
    except Exception:
        pass

    padd('--pnc', action='append', default=[],
         help='Set of pseudonetcdf commands to be process separately')

    padd("-f", "--format", dest="format", default='netcdf',
         metavar='{see --list-formats for choices}',
         help=("File format (default netcdf), can be one of the choices " +
               "listed, or an expression that evaluates to a reader. " +
               "Keyword arguments are passed via ,kwd=value."))

    padd("--list-formats", dest="help", default=None,
         action=_HelpListFormats, help="Show format options for -f")

    padd("--help-format", dest='helpformat',
         action=_HelpFormat, default=None,
         help="Show help for file format (must be one of the options for -f)")

    padd("--sep", dest="separator", action='store_true',
         default=False,
         help=("Used to separate groups of arguments for parsing (e.g., " +
               "pncgen -- [options1] file(s)1 [--sep [options2] file(s)2 " +
               "[... [--sep [optionsN] file(s)N]] "))

    padd("--inherit", dest="inherit", action="store_true",
         default=False,
         help=("Allow subparsed sections (separated with -- and --sep) to " +
               "inherit from global options (-f, --format is always " +
               "inherited)."))

    padd("--diskless", dest="diskless", action="store_true",
         default=False,
         help="Load file into memory; useful for subsequent processing")

    padd("--mangle", dest="mangle", action="store_true",
         default=False, help="Remove non-standard ascii from names")

    padd("--rename", dest="rename", action="append", default=[],
         help=("Provide pairs of strings to be substituted " +
               "--rename=type,oldkey,newkey (type: v = variable; d = " +
               "dimension;)"))

    padd("--remove-singleton", dest="removesingleton",
         type=lambda x: [k for k in x.split(',') if k != ''], default=[],
         help="Remove singleton (length 1) dimensions")

    padd("--coordkeys", dest="coordkeys", type=str,
         action=AggCommaString, default=_coordkeys, metavar="key1,key2",
         help="Variables to be ignored in pncbo.")

    padd("-v", "--variables", dest="variables", default=None,
         action=AggCommaString, metavar='varname1[,varname2[,...,varnameN]',
         help=("Variable names or regular expressions (using match) " +
               "separated by ','. If a group(s) has been specified, only " +
               "variables in that (those) group(s) will be selected."))

    padd("-a", "--attribute", dest="attribute", type=str,
         action="append", default=[],
         metavar="att_nm,var_nm,mode,att_typ,att_val",
         help=("Variables have attributes that can be added following nco " +
               "syntax (--attribute att_nm,var_nm,mode,att_typ,att_val); " +
               "mode = a,c,d,m,o and att_typ = f,d,l,s,c,b; att_typ is any " +
               "valid numpy type."))

    padd("-m", "--mask", dest="masks", action="append",
         default=[],
         help=("Masks to apply (e.g., greater,0 or less,0 or values,0, or " +
               "where,(time[:]%%24<12)[:,None].repeat(10,1))"))

    padd("--from-convention", dest="fromconv", type=str,
         default=None, help="From convention currently only support ioapi")

    padd("--to-convention", dest="toconv", type=str,
         default="cf", help="To convention currently only supports cf")

    padd("--stack", dest="stack", type=str,
         help="Concatentate (stack) files on the dimension.")

    padd("--merge", dest="merge", action='store_true',
         help="Combine variables into one file")

    padd("-s", "--slice", dest="slice", type=str,
         action="append", default=[], metavar='dim,start[,stop[,step]]',
         help=("Variables have dimensions (time, layer, lat, lon), which " +
               "can be subset using dim,start,stop,stride (e.g., " +
               "--slice=layer,0,47,5 would sample every fifth layer " +
               "starting at 0)"))

    padd("-r", "--reduce", dest="reduce", type=str,
         action="append", default=[], metavar='dim,function[,weight]',
         help=("Variable dimensions can be reduced using dim,function," +
               "weight syntax (e.g., --reduce=layer,mean,weight). " +
               "Weighting is not fully functional."))

    padd("--mesh", dest="mesh", type=str, action="append",
         default=[], metavar='dim,weight,function',
         help=("Variable dimensions can be meshed using dim,function," +
               "weight syntax (e.g., --mesh=time,0.5,mean)."))

    padd("-c", "--convolve", dest="convolve", type=str,
         action="append", default=[], metavar='dim,mode,wgt1,wgt2,...wgtN',
         help=("Variable dimension is reduced by convolve function " +
               "(dim,mode,wgt1,wgt2,...wgtN)"))

    padd("-e", "--extract", dest="extract", action="append",
         default=[],
         help=("lon/lat coordinates to extract lon1,lat1/lon2,lat2/lon3," +
               "lat3/.../lonN,latN"))

    padd("--extract-file", dest="extractfile", action="append",
         default=[], help="pncparse options for file")

    padd("--extractmethod", dest="extractmethod", type=str,
         default='nn', help="Method for extraction",
         choices=['ll2ij', 'nn', 'linear', 'cubic', 'quintic', 'KDTree'])

    padd("--op-typ", dest="operators", type=str,
         action='append', default=[],
         help=("Operator for binary file operations. Binary file operations " +
               "use the first two files, then the result and the next file, " +
               "etc. Use " +
               " or ".join(['//', '<=', '%%', 'is not', '>>', '&', '==', '!=',
                            '+', '*', '-', '/', '<', '>=', '**', '>', '<<',
                            '|', 'is', '^'])))

    padd("--expr", dest="expressions", type=str,
         action='append', default=[],
         help="Generic expressions to execute in the context of the file.")

    padd("--exprscript", dest="expressionscripts", type=str,
         action='append', default=[],
         help="Generic expressions to execute in the context of the file.")


def add_2d_options(parser):
    parser.add_argument('--swapaxes', action='store_true',
                        help='Swap x-y axes')


def add_eval_options(parser):
    padd = parser.add_argument
    from .pnceval import __all__ as evalfuncs
    add_interactive_options(parser)
    padd('--funcs', default=evalfuncs,
         type=lambda x: x.split(','),
         help=('Functions to evaluate split by , (default: %s)' %
               ','.join(evalfuncs)))


def add_map_options(parser):
    padd = parser.add_argument
    padd("--iter", dest="iter", action='append', default=[],
         help=("Create plots for each element of specified dimension " +
               "(e.g., --iter=time)."))

    padd("--resolution", dest="resolution",
         choices=['c', 'i', 'h'], default='c',
         help=("Use coarse (c), intermediate (i), or high (h) " +
               "resolution for maps."))

    padd("--no-coastlines", dest="coastlines",
         action='store_false',
         help="Disable coastlines by setting equal to False")

    padd("--no-countries", dest="countries",
         action='store_false',
         help="Disable countries by setting equal to False")

    padd("--states", dest="states", action='store_true',
         help="Enable states by setting equal to True")

    padd("--counties", dest="counties", action='store_true',
         help="Enable counties by setting equal to True")

    padd("--shapefile", "--shapefiles", dest="shapefiles",
         type=str, action='append', default=[],
         help=("Enable custom shapefiles (must be lon, lat). Keyword " +
               "arguments for display can be added with commas (see -f " +
               "for syntax)."))


def add_dump_options(parser):
    padd = parser.add_argument
    parser.add_argument("-H", "--header", dest="header",
                        action="store_true", default=False)

    parser.add_argument("-t", "--timestring", dest="timestring",
                        action="store_true", default=False)

    padd("--full-indices", dest="full_indices", default=None,
         metavar="[c|f]", choices=['c', 'f'],
         help=("Provide indices in CDL using either C or Fortran style " +
               "indexes. C style is 0-based and ordered from slowest " +
               "iterating dimension to fastest. Fortran style is 1-based " +
               "and ordered from fastest to slowest iterating dimension"))

    padd("-l", "--length", dest="line_length", type=int,
         default=80, metavar="LEN",
         help="CDL line length (pncdump only)")

    padd("--float-precision", dest="float_precision", type=int,
         default=8, metavar="FDIG",
         help="single precision digitis (default 8; pncdump only)")

    padd("--double-precision", dest="double_precision",
         type=int, default=16, metavar="PDIG",
         help="pdig double precision digits (default 16; pncdump only)")

    padd("--dump-name", dest="cdlname", type=str,
         default=None, help="Name for display in ncdump")


def add_output_options(parser):
    padd = parser.add_argument
    padd("-O", "--clobber", dest="clobber",
         action='store_true', default=False,
         help="Overwrite existing file if necessary.")

    ncex = "NETCDF3_CLASSIC, NETCDF4_CLASSIC, NETCDF4"
    padd("--out-format", dest="outformat",
         default="NETCDF4_CLASSIC", type=str,
         help="File output format (e.g., " + ncex + ";pncgen only)",
         choices=ncex.split(', ') + _writernames)

    padd("--mode", dest="mode", type=str, default="w",
         choices='w a r+ ws as r+s'.split(),
         help=("File mode for writing (w, a or r+ or with unbuffered " +
               "writes ws, as, or r+s; pncgen only)."))

    padd('outpath', default=None, type=str,
         help='path to a output file formatted as --out-format')


def add_interactive_options(parser):
    try:
        parser.add_argument("-i", "--interactive", dest="interactive",
                            action='store_true', default=False,
                            help="Use interactive mode")
    except Exception:
        pass


def add_plot_options(parser):
    padd = parser.add_argument
    add_interactive_options(parser)
    padd("--matplotlibrc", dest="matplotlibrc",
         type=lambda x: x.split(';'), action='append', default=[],
         help='rc options for matplotlib')
    padd("--figure-keywords", dest='figure_keywords',
         type=lambda x: eval('dict(' + x + ')'), default=dict(),
         help='options for figure')

    padd("--axes-keywords", dest='axes_keywords',
         type=lambda x: eval('dict(' + x + ')'),
         default=dict(), help='options for axes')

    padd("--plot-commands", dest="plotcommands", type=str,
         action='append', default=[],
         help=("Plotting functions to call for all variables expressions " +
               "to execute in the context of the file. The figure object " +
               "will always be 'fig'."))

    padd("--figformat", dest="figformat", type=str,
         default='png', help="Any format supported by matplotlib")

    padd("--norm", dest="normalize", type=str, default=None,
         help=("Typical examples Normalize(), LogNorm(), BoundaryNorm([0, " +
               "10, 20, 30, 40], ncolors = 256)"))

    padd("--colorbar-formatter", dest="colorbarformatter",
         type=str, default=None,
         help=("Typical examples LogFormatter(labelOnlyBase = False), " +
               "ScalarFormatter(), '%%3g'"))

    padd("--overlay", dest="overlay", action='store_true',
         help="Enable overlay by setting equal to True")

    padd('--no-squeeze', dest='squeeze', default=True,
         action='store_false',
         help=('Squeeze automatically removes singleton dimensions; ' +
               'disabling requires user to remove singleton dimensions ' +
               'with --remove-singleton option'))

    padd("--figroot", help='Path for base of figure (suffixed by action)')


_pncepilog = """
Detailed Steps
--------------

PseudoNetCDF has many operations and the order often matters. The order is
consistent with the order of options in the formatted help. The default order
is summarized as:

1. Open with specified reader (-f)
2. Select subset of variables (-v)
2. Add attributes (-a)
4. Apply masks (--mask)
5. Add conventions to support later operations (--to-conv, --from-conv)
6. Combine files via stacking on dimensions (--stack)
7. Slice dimensions (-s --slice)
8. Reduce dimensions (-r --reduce)
9. Convolve dimensions (-c)
10. Extract specific coordinates (--extract)
11. Remove singleton dimensions (--remove-singleton)
12. Apply expressions (--expr then --exprscripts)
13. Apply binary operators (--op_typ)

To impose your own order, use standard options (global options) and then
use -- to force positional interpretation of remaining options. In remaining
options, use --sep to separate groups of files and options to be evaluated
before any global operations."""


def getparser2(actions=False):
    rdesc = RawDescriptionHelpFormatter
    parent_parser = PNCArgumentParser(description="PseudoNetCDF",
                                      formatter_class=rdesc,
                                      add_help=False,
                                      prog='PNC')
    if actions:
        add_action_commands(
            parent_parser, withbasic=True, add_help=False)
    else:
        add_basic_options(parent_parser)
    return parent_parser


def getparser(has_ofile, plot_options=False, map_options=False,
              interactive=False, actions=False):
    """getparser produces a parser for PseudoNetCDF

Parameters
----------
has_ofile : boolean
            Requires the outpath option and processes existence check
plot_options : boolean, optional
               Adds options for plotting including matplotlibrc, spatial
               overlays, and normalization options, default is False
interactive : boolean, optional
              Adds for interactive option, default is False

Returns
-------
parser : ArgumentParser with options that are processable with pncparse

    """
    parser = PNCArgumentParser(description="""PseudoNetCDF Argument Parsing


""", formatter_class=RawDescriptionHelpFormatter)
    parser.epilog = _pncepilog
    add_basic_options(parser)
    if interactive:
        add_interactive_options(parser)

    if actions:
        add_action_commands(parser, add_help=False)
    else:
        # Only has output file if pncgen is called.
        if has_ofile:
            add_output_options(parser)
        else:
            add_dump_options(parser)

        if plot_options:
            add_plot_options(parser)
        if map_options:
            add_map_options(parser)

    return parser


def pncparse(has_ofile=False, plot_options=False, map_options=False,
             interactive=False, args=None, parser=None, ifiles=[]):
    """
Parameters
----------
has_ofile : boolean, optional
            Requires the outpath option and processes existence check
            default is False
plot_options : boolean, optional
               Processes matplotlib options before loading matplotlib
               (preprocessing important for backend), default is False.
interactive : boolean, optional
              Only relevant if parser is not provided (see getparser),
              default is False.
args : list or string, optional
       args are usually taken from the command-line, but can be provided
       in a function call, default is None.
parser : AgumentParser object, optional
         pncparser parser, default getparser(has_ofile,
                                             plot_options = plot_options,
                                             interactive = interactive)

Returns
-------
(ifiles, args)

ifiles : list of PseudoNetCDFFiles
args : args as parsed

    """
    inputargs = args
    if parser is None:
        parser = getparser(has_ofile, plot_options=plot_options,
                           map_options=map_options, interactive=interactive)
    subparser = getparser(
        has_ofile=False, plot_options=plot_options, interactive=interactive)
    try:
        args = parser.parse_args(args=args)
    except Exception as e:
        raise e
    except BaseException:
        return [], args

    args.inputargs = inputargs or sys.argv
    inpaths = getattr(args, 'ifiles', [])
    inpnc = getattr(args, 'pnc', [])
    inopts = len(inpaths + inpnc)
    if len(ifiles) == 0 and inopts == 0:
        if not getattr(args, 'help', False):
            parser.print_help()

        print('\n*ifile or pnc are required')
        return [], args

    subargs = split_positionals(subparser, args)
    ifiles = [ifile for ifile in ifiles]
    ipaths = ['unknown'] * len(ifiles)
    for subarg in subargs:
        ipaths.extend(subarg.ifiles)
        ifiles.extend(pncprep(subarg)[0])

    # ifile is set to results from parsing
    # this includes the standard ifile
    args.ifiles = ifiles
    args.ipath = ipaths
    # if args.stack is not None:
    #    pass
    # elif (len(args.ifiles) == 0 or
    #       (len(args.ifiles) - len(args.operators) - has_ofile) > 1):
    #    print('Too many arguments', len(args.ifiles),
    #          len(args.operators), has_ofile)
    #    parser.print_help()
    #    return
    if has_ofile or not getattr(args, 'outpath', None) is None:
        if not args.clobber and os.path.exists(args.outpath):
            parser.error(message=(('Output path (%s) exists; enable ' +
                                   'clobber (--clobber or -O) to ' +
                                   'overwrite.') % (args.outpath,)))

    if plot_options or hasattr(args, 'matplotlibrc'):
        from matplotlib import rcParams
        for rcassigns in args.matplotlibrc:
            for rcassign in rcassigns:
                key, value = rcassign.split('=')
                rcParams[key] = value

    out = pncprep(args)
    return out


def add_action_commands(parser, withbasic=False, add_help=True):
    subparsers = parser.add_subparsers(dest='subcommand',
                                       help='Adding commands for subparsing')
    dumpparser = subparsers.add_parser('dump',
                                       description=('Output Common Data ' +
                                                    'Language (like from ' +
                                                    'ncdump)'),
                                       help='sub-command help',
                                       add_help=add_help)
    add_dump_options(dumpparser)
    if withbasic:
        add_basic_options(dumpparser)

    genparser = subparsers.add_parser('gen',
                                      description=('Like ncgen with more ' +
                                                   'format support'),
                                      help='sub-command help',
                                      add_help=add_help)
    if withbasic:
        add_basic_options(genparser)
    add_output_options(genparser)

    evalparser = subparsers.add_parser('eval',
                                       description='Evaluation options.',
                                       help='sub-command help',
                                       add_help=add_help)
    add_eval_options(evalparser)
    if withbasic:
        add_basic_options(evalparser)

    mapparser = subparsers.add_parser('map',
                                      description='Make maps quickly',
                                      help='sub-command help',
                                      add_help=add_help)
    add_plot_options(mapparser)
    add_map_options(mapparser)
    if withbasic:
        add_basic_options(mapparser)

    for plotcmd in _plotcmds:
        plotparser = subparsers.add_parser(
            plotcmd, description='Plot data', add_help=add_help)
        add_plot_options(plotparser)
        if withbasic:
            add_basic_options(plotparser)
        if plotcmd == 'plotprofile':
            from .plotutil import add_vertprofile_options
            add_vertprofile_options(plotparser)


def do_actions(outargs):
    if 'subcommand' not in outargs:
        return
    if outargs.subcommand == 'dump':
        from .pncdump import pncdump
        for ifile in outargs.ifiles:
            pncdump(ifile, header=outargs.header,
                    full_indices=outargs.full_indices,
                    line_length=outargs.line_length,
                    float_precision=outargs.float_precision,
                    double_precision=outargs.double_precision,
                    timestring=outargs.timestring, name=outargs.cdlname)

    elif outargs.subcommand == 'gen':
        from .pncgen import pncgen
        nout = len(outargs.ifiles)
        if nout != 1:
            raise IOError(
                'pncgen can output only 1 file; user requested %d' % nout)
        ifile, = outargs.ifiles
        pncgen(ifile, outargs.outpath, outmode=outargs.mode,
               format=outargs.outformat, verbose=outargs.verbose)
    elif outargs.subcommand == 'eval':
        from .pnceval import pnceval
        nin = len(outargs.ifiles)
        if nin != 2:
            raise IOError(
                'pnceval requires 2 files; user requested %d' % nin)
        pnceval(outargs)
    elif outargs.subcommand == 'map':
        if getattr(outargs, 'outpath', None) is None:
            outargs.outpath = outargs.figroot
        from .plotutil.pncmap import makemaps
        makemaps(outargs)
    elif outargs.subcommand in _plotcmds:
        if getattr(outargs, 'outpath', None) is None:
            outargs.outpath = outargs.figroot
        from . import plotutil
        getattr(plotutil, outargs.subcommand)(outargs)


def PNC(*args, **kwds):
    """
Arguments:
    args - Command Line arguments/options for PseudoNetCDF
           for full list of potential args PNC('--help')
    ifiles - (optional) pre-loaded input files
    actions -  (default: False)
        False: only open files do not make outputs
        True: enable dump,gen,map,etc output actions
              for action options see subparsers help
              e.g., PNC('dump', '--help', actions = True)

Returns:
    out - Namespace object with parsed arguments
          including a list of processed files (out.ifiles)

Example:
    # Single File
    out = PNC('--format=netcdf', inpath)
    infile = out.ifiles[0]
    O3 = infile.variables['O3']

    # Multiple Files
    out = PNC('--format=netcdf', inpath1, inpath2)
    infile1, infile2 = out.ifiles
    O3_1 = infile1.variables['O3']
    O3_2 = infile2.variables['O3']

    # With Actions
    out = PNC('dump', '--variables=O3', '--format=netcdf', inpath)
netcdf icartt/dc3-mrg60-dc8_merge_20120518_R7_thru20120622.nc {
dimensions:
        POINTS = 6817 ;

variables:
        double O3(TSTEP, LAY, ROW, COL);
                O3_ESRL:units = "ppbV" ;
                O3_ESRL:standard_name = "Ozone" ;
                O3_ESRL:missing_value = -999999 ;
    ...
    """
    ifiles = kwds.pop('ifiles', [])
    actions = kwds.pop('actions', None)
    if actions is None:
        actions = args[0] in _allcmds

    parser = getparser2(actions=actions)
    nfiles = len(ifiles)
    ifiles, outargs = pncparse(args=args, ifiles=ifiles, parser=parser, **kwds)
    if nfiles > 0 and len(ifiles) != nfiles:
        raise IOError('PNC can only use files or paths, not both at this time')
    do_actions(outargs)

    return outargs


def pnc(*args, **kwds):
    """
Arguments - see PNC
Returns:
    file(s) - single file or, if more than 1 file is returned a list of files
    """
    ifiles = kwds.pop('ifiles', [])
    actions = kwds.pop('actions', None)
    out = PNC(*args, ifiles=ifiles, actions=actions, **kwds).ifiles
    if len(out) == 1:
        return out[0]
    else:
        return out


def split_positionals(parser, args):
    import shlex
    positionals = args.ifiles
    parser.set_defaults(
        **dict([(k, v) for k, v in args._get_kwargs()
                if args.inherit or k == 'format']))
    ins = [shlex.split(pnc) for pnc in args.pnc]
    last_split = 0
    for i in range(len(positionals)):
        if positionals[i] in ('--', '--sep'):
            ins.append(positionals[last_split:i])
            last_split = i + 1
    ins.append(positionals[last_split:])
    try:
        outs = list(map(parser.parse_args, ins))
        for outns, inargs in zip(outs, ins):
            outns.inputargs = inargs
    except Exception as e:
        print(str(e))
        raise e
    except BaseException as be:
        print(str(be))
        return [], args

    for out in outs:
        if out.cdlname is None:
            out.cdlname = ', '.join(out.ifiles)
    if len(outs) == 1 and getattr(args, 'cdlname', None):
        args.cdlname = outs[0].cdlname
    return outs


def pncprep(args):
    # nifiles = len(args.ifiles) - has_ofile
    ipaths = args.ifiles[:]
    fs = getfiles(ipaths, args)
    fs = subsetfiles(fs, args)
    fs = seqpncbo(args.operators, fs, coordkeys=args.coordkeys)
    for expr in args.expressions:
        fs = [pncexpr(expr, f) for f in fs]
    for script in args.expressionscripts:
        expr = open(script).read()
        fs = [pncexpr(expr, f) for f in fs]
    args.ifiles = fs
    if getattr(args, 'cdlname', None) is None:
        try:
            args.cdlname = args.ipath[0]
        except Exception:
            args.cdlname = 'unknown'
    return fs, args


def subsetfiles(ifiles, args):
    fs = []
    for f in ifiles:
        for opts in args.slice:
            f = slice_dim(f, opts)
        for opts in args.reduce:
            f = reduce_dim(f, opts, metakeys=args.coordkeys)
        for opts in args.mesh:
            f = mesh_dim(f, opts)
        for opts in args.convolve:
            f = convolve_dim(f, opts)
        if len(args.extract) > 0:
            f = extract(f, args.extract, method=args.extractmethod)
        if len(args.extractfile) > 0:
            extractfiles = []
            import shlex
            for extractfile in args.extractfile:
                extractfile, options = pncparse(
                    has_ofile=False, args=shlex.split(extractfile))
                extractfiles.extend(extractfile)

            f = extract_from_file(f, extractfiles, method=args.extractmethod)
        for rd in args.removesingleton:
            f = removesingleton(f, rd)
        fs.append(f)
    return fs


def getfiles(ipaths, args):
    fs = []
    for ipath in ipaths:
        format_options = args.format.split(',')
        file_format = format_options.pop(0)
        format_options = eval('dict(' + ', '.join(format_options) + ')')
        if isinstance(ipath, (PseudoNetCDFFile, NetCDFFile, MFDataset)):
            f = ipath
        elif isinstance(ipath, (str,)):
            try:
                # Most common usecase; open like pncopen
                f = pncopen(ipath, format=file_format, **format_options)
            except Exception:
                try:
                    allreaders = getreaderdict()
                    if file_format in allreaders:
                        # Unknown problem, try reader directly
                        allreaders[file_format](ipath, **format_options)
                    else:
                        # Maybe the format is an expression?
                        allreaders[file_format](ipath, **format_options)
                        f = eval(file_format)(ipath, **format_options)
                except Exception as e:
                    etmp = ('Unable to open path with %s(path, **%s)\n\t' +
                            'path="%s"\n\terror="%s"')
                    oute = IOError(etmp % (file_format, str(format_options),
                                           ipath, str(e)))
                    raise oute  # from e
        else:
            warn('File is type %s, which is unknown' % type(ipath))
            f = ipath

        history = getattr(f, 'history', getattr(f, 'HISTORY', ''))
        history += ' '.join(args.inputargs) + ';'
        laddconv = args.fromconv is not None and args.toconv is not None
        # Need to add backward compatibility
        # lslice = len(args.slice + args.reduce) > 0
        # lexpr = len(args.expressions) > 0
        if args.variables is not None:
            f = getvarpnc(f, args.variables, coordkeys=args.coordkeys)
        elif args.diskless:
            f = getvarpnc(f, None)
        for opts in args.attribute:
            add_attr(f, opts)
        for opts in args.masks:
            f = mask_vals(f, opts, metakeys=args.coordkeys)
        if laddconv:
            if not args.diskless:
                f = WrapPNC(f)
            try:
                conv2conv[args.fromconv, args.toconv](
                    f, coordkeys=args.coordkeys)
            except KeyError as e:
                warn('Cannot add %s from %s; %s' %
                     (args.toconv, args.fromconv, str(e)))

        try:
            setattr(f, 'history', history)
        except Exception:
            pass
        if args.mangle:
            f = manglenames(f)
        for rename in args.rename:
            f = pncrename(f, rename)
        fs.append(f)
    if args.stack is not None:
        fs = [stack_files(fs, args.stack, coordkeys=args.coordkeys)]
    if args.merge:
        fs = [merge(fs)]
    return fs
