"""
mfde4 module.  Contains the ModflowDe4 class. Note that the user can access
the ModflowDe4 class as `flopy.modflow.ModflowDe4`.

Additional information for this MODFLOW package can be found at the `Online
MODFLOW Guide
<http://water.usgs.gov/ogw/modflow-nwt/MODFLOW-NWT-Guide/de4.htm>`_.

"""
import sys
from ..pakbase import Package


class ModflowDe4(Package):
    """
    MODFLOW DE4 - Direct Solver Package

    Parameters
    ----------
    model : model object
        The model object (of type :class:`flopy.modflow.mf.Modflow`) to which
        this package will be added.
    itmx : int
        Maximum number of iterations for each time step. Specify ITMAX = 1 if
        iteration is not desired. Ideally iteration would not be required for
        direct solution. However, it is necessary to iterate if the flow
        equation is nonlinear or if computer precision limitations result in
        inaccurate calculations as indicated by a large water budget error
        (default is 50).
    mxup : int
        Maximum number of equations in the upper part of the equations to be
        solved. This value impacts the amount of memory used by the DE4
        Package. If specified as 0, the program will calculate MXUP as half
        the number of cells in the model, which is an upper limit (default
        is 0).
    mxlow : int
        Maximum number of equations in the lower part of equations to be
        solved. This value impacts the amount of memory used by the DE4
        Package. If specified as 0, the program will calculate MXLOW as half
        the number of cells in the model, which is an upper limit (default is
        0).
    mxbw : int
        Maximum band width plus 1 of the lower part of the head coefficients
        matrix. This value impacts the amount of memory used by the DE4
        Package. If specified as 0, the program will calculate MXBW as the
        product of the two smallest grid dimensions plus 1, which is an
        upper limit (default is 0).
    ifreq : int
        Flag indicating the frequency at which coefficients in head matrix
        change.
        IFREQ = 1 indicates that the flow equations are linear and that
        coefficients of simulated head for all stress terms are constant
        for all stress periods.
        IFREQ = 2 indicates that the flow equations are linear, but
        coefficients of simulated head for some stress terms may change
        at the start of each stress period.
        IFREQ = 3 indicates that a nonlinear flow equation is being solved,
        which means that some terms in the head coefficients matrix depend
        on simulated head (default is 3).
    mutd4 : int
        Flag that indicates the quantity of information that is printed when
        convergence information is printed for a time step.
        MUTD4 = 0 indicates that the number of iterations in the time step
        and the maximum head change each iteration are printed.
        MUTD4 = 1 indicates that only the number of iterations in the time
        step is printed.
        MUTD4 = 2 indicates no information is printed (default is 0).
    accl : int
        Multiplier for the computed head change for each iteration. Normally
        this value is 1. A value greater than 1 may be useful for improving
        the rate of convergence when using external iteration to solve
        nonlinear problems (default is 1).
    hclose : float
        Head change closure criterion. If iterating (ITMX > 1), iteration
        stops when the absolute value of head change at every node is less
        than or equal to HCLOSE. HCLOSE is not used if not iterating, but a
        value must always be specified (default is 1e-5).
    iprd4 : int
        Time step interval for printing out convergence information when
        iterating (ITMX > 1). If IPRD4 is 2, convergence information is
        printed every other time step. A value must always be specified
        even if not iterating (default is 1).
    extension : string
        Filename extension (default is 'de4')
    unitnumber : int
        File unit number (default is None).
    filenames : str or list of str
        Filenames to use for the package. If filenames=None the package name
        will be created using the model name and package extension. If a
        single string is passed the package will be set to the string.
        Default is None.


    Attributes
    ----------

    Methods
    -------

    See Also
    --------

    Notes
    -----

    Examples
    --------

    >>> import flopy
    >>> m = flopy.modflow.Modflow()
    >>> de4 = flopy.modflow.ModflowDe4(m)

    """

    def __init__(self, model, itmx=50, mxup=0, mxlow=0, mxbw=0,
                 ifreq=3, mutd4=0, accl=1., hclose=1e-5, iprd4=1,
                 extension='de4', unitnumber=None, filenames=None):
        """
        Package constructor.

        """

        # set default unit number of one is not specified
        if unitnumber is None:
            unitnumber = ModflowDe4.defaultunit()

        # set filenames
        if filenames is None:
            filenames = [None]
        elif isinstance(filenames, str):
            filenames = [filenames]

        # Fill namefile items
        name = [ModflowDe4.ftype()]
        units = [unitnumber]
        extra = ['']

        # set package name
        fname = [filenames[0]]

        # Call ancestor's init to set self.parent, extension, name and unit number
        Package.__init__(self, model, extension=extension, name=name,
                         unit_number=units, extra=extra, filenames=fname)

        # check if a valid model version has been specified
        if model.version == 'mfusg':
            err = 'Error: cannot use {} package with model version {}'.format(
                self.name, model.version)
            raise Exception(err)

        self.heading = '# {} package for '.format(self.name[0]) + \
                       ' {}, '.format(model.version_types[model.version]) + \
                       'generated by Flopy.'
        self.url = 'de4.htm'

        self.itmx = itmx
        self.mxup = mxup
        self.mxlow = mxlow
        self.mxbw = mxbw
        self.ifreq = ifreq
        self.mutd4 = mutd4
        self.accl = accl
        self.hclose = hclose
        self.iprd4 = iprd4
        self.parent.add_package(self)
        return

    def write_file(self):
        """
        Write the package file.

        Returns
        -------
        None

        """
        # Open file for writing
        f = open(self.fn_path, 'w')
        f.write('{}\n'.format(self.heading))
        ifrfm = self.parent.get_ifrefm()
        if ifrfm:
            f.write('{} '.format(self.itmx))
            f.write('{} '.format(self.mxup))
            f.write('{} '.format(self.mxlow))
            f.write('{} '.format(self.mxbw))
            f.write('\n')
            f.write('{} '.format(self.ifreq))
            f.write('{} '.format(self.mutd4))
            f.write('{} '.format(self.accl))
            f.write('{} '.format(self.hclose))
            f.write('{} '.format(self.iprd4))
            f.write('\n')
        else:
            f.write('{:10d}'.format(self.itmx))
            f.write('{:10d}'.format(self.mxup))
            f.write('{:10d}'.format(self.mxlow))
            f.write('{:10d}'.format(self.mxbw))
            f.write('\n')
            f.write('{:10d}'.format(self.ifreq))
            f.write('{:10d}'.format(self.mutd4))
            f.write('{:9.4e} '.format(self.accl))
            f.write('{:9.4e} '.format(self.hclose))
            f.write('{:10d}'.format(self.iprd4))
            f.write('\n')
        f.close()

    @staticmethod
    def load(f, model, ext_unit_dict=None):
        """
        Load an existing package.

        Parameters
        ----------
        f : filename or file handle
            File to load.
        model : model object
            The model object (of type :class:`flopy.modflow.mf.Modflow`) to
            which this package will be added.
        ext_unit_dict : dictionary, optional
            If the arrays in the file are specified using EXTERNAL,
            or older style array control records, then `f` should be a file
            handle.  In this case ext_unit_dict is required, which can be
            constructed using the function
            :class:`flopy.utils.mfreadnam.parsenamefile`.

        Returns
        -------
        de4 : ModflowDe4 object

        Examples
        --------

        >>> import flopy
        >>> m = flopy.modflow.Modflow()
        >>> de4 = flopy.modflow.ModflowDe4.load('test.de4', m)

        """

        if model.verbose:
            sys.stdout.write('loading de4 package file...\n')

        openfile = not hasattr(f, 'read')
        if openfile:
            filename = f
            f = open(filename, 'r')

        # read dataset 0 -- header
        while True:
            line = f.readline()
            if line[0] != '#':
                break
        # read dataset 1
        ifrfm = model.get_ifrefm()
        if model.version != 'mf2k':
            ifrfm = True
        ifreq = 1
        if ifrfm:
            t = line.strip().split()
            itmx = int(t[0])
            mxup = int(t[1])
            mxlow = int(t[2])
            mxbw = int(t[3])
            line = f.readline()
            t = line.strip().split()
            ifreq = int(t[0])
            mutd4 = int(t[1])
            accl = float(t[2])
            hclose = float(t[3])
            iprd4 = int(t[4])
        else:
            itmx = int(line[0:10].strip())
            mxup = int(line[10:20].strip())
            mxlow = int(line[20:30].strip())
            mxbw = int(line[30:40].strip())
            line = f.readline()
            ifreq = int(line[0:10].strip())
            mutd4 = int(line[10:20].strip())
            accl = float(line[20:30].strip())
            hclose = float(line[30:40].strip())
            iprd4 = int(line[40:50].strip())

        if openfile:
            f.close()

        # set package unit number
        unitnumber = None
        filenames = [None]
        if ext_unit_dict is not None:
            unitnumber, filenames[0] = \
                model.get_ext_dict_attr(ext_unit_dict,
                                        filetype=ModflowDe4.ftype())

        de4 = ModflowDe4(model, itmx=itmx, mxup=mxup, mxlow=mxlow, mxbw=mxbw,
                         ifreq=ifreq, mutd4=mutd4, accl=accl, hclose=hclose,
                         iprd4=iprd4, unitnumber=unitnumber,
                         filenames=filenames)
        return de4

    @staticmethod
    def ftype():
        return 'DE4'

    @staticmethod
    def defaultunit():
        return 28
