# -*- coding: UTF-8 -*-
#
# Copyright (c) 2014, Yung-Yu Chen <yyc@solvcon.net>
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# - Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
# - Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
# - Neither the name of the copyright holder nor the names of its contributors
#   may be used to endorse or promote products derived from this software
#   without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

"""
Input and output facilities.
"""


import os
import time
import math
import warnings

import numpy as np

import solvcon as sc

from solvcon.io import vtkxml


class MeshInfoHook(sc.MeshHook):
    """
    Print mesh information.
    """

    def __init__(self, cse, show_bclist=False, perffn=None, **kw):
        """
        If keyword psteps is None, postmarch method will not output
        performance information.
        """
        #: Flag to show the list of boundary conditions.  Default is ``False``.
        self.show_bclist = show_bclist
        #: Performance file name.
        self.perffn = perffn
        super(MeshInfoHook, self).__init__(cse, **kw)

    def preloop(self):
        blk = self.blk
        self.info("Block information:\n  %s\n" % str(blk))
        if self.show_bclist:
            for bc in blk.bclist:
                self.info("  %s\n" % bc)

        # explicit info to prompt users to watch out issues like issue #177
        if any(bc.__class__ is sc.BC for bc in blk.bclist):
            self.info("  One of the boundary conditions is generic boundary "
                      "type.\n")

    def _show_performance(self):
        """
        Show and store performance information.
        """
        ncell = self.blk.ncell
        time = self.cse.log.time['solver_march']
        step_init = self.cse.execution.step_init
        step_current = self.cse.execution.step_current
        neq = self.cse.execution.neq
        npart = self.cse.execution.npart
        # determine filename.
        perffn = '%s_perf.txt' % self.cse.io.basefn
        perffn = self.perffn if self.perffn is not None else perffn
        perffn = os.path.join(self.cse.io.basedir, perffn)
        pf = open(perffn, 'w')
        # calculate and output performance.
        def out(msg):
            self.info(msg)
            pf.write(msg)
        perf = (step_current-step_init)*ncell / time * 1.e-6
        out('Performance of %s:\n' % self.cse.io.basefn)
        out('  %g seconds in marching solver.\n' % time)
        out('  %g seconds/step.\n' % (time/(step_current-step_init)))
        out('  %g microseconds/cell.\n' % (1./perf))
        out('  %g Mcells/seconds.\n' % perf)
        out('  %g Mvariables/seconds.\n' % (perf*neq))
        if isinstance(self.cse.execution.npart, int):
            out('  %g Mcells/seconds/computer.\n' % (perf/npart))
            out('  %g Mvariables/seconds/computer.\n' % (perf*neq/npart))
        pf.close()

    def postmarch(self):
        istep = self.cse.execution.step_current
        nsteps = self.cse.execution.steps_run
        psteps = self.psteps
        if istep > 0 and psteps and istep%psteps == 0 and istep != nsteps:
            self._show_performance()

    def postloop(self):
        self._show_performance()


class ProgressHook(sc.MeshHook):
    """
    Print simulation progess.
    """

    def __init__(self, cse, linewidth=50, **kw):
        #: The maximum width for progress mark.
        self.linewidth = linewidth
        super(ProgressHook, self).__init__(cse, **kw)

    def preloop(self):
        istep = self.cse.execution.step_current
        nsteps = self.cse.execution.steps_run
        info = self.info
        info("Steps %d/%d\n" % (istep, nsteps))

    def postmarch(self):
        istep = self.cse.execution.step_current
        nsteps = self.cse.execution.steps_run
        tstart = self.cse.log.time['run_march'][0]
        psteps = self.psteps
        linewidth = self.linewidth
        info = self.info
        # calculate estimated remaining time.
        tcurr = time.time()
        tleft = (tcurr-tstart) * ((float(nsteps)-float(istep))/float(istep))
        # output information.
        if istep%psteps == 0:
            info("#")
        if istep > 0 and istep%(psteps*linewidth) == 0:
            info("\nStep %d/%d, %.1fs elapsed, %.1fs left\n" % (
                istep, nsteps, tcurr-tstart, tleft,
            ))
        elif istep == nsteps:
            info("\nStep %d/%d done\n" % (istep, nsteps))


class FillAnchor(sc.MeshAnchor):
    """
    Fill the specified arrays of a :py:class:`~.solver.GasSolver` with
    corresponding value.
    """

    def __init__(self, svr, mappers=None, **kw):
        assert None is not mappers
        #: A :py:class:`dict` maps the names of attributes of the
        #: :py:attr:`MeshAnchor.svr <solvcon.MeshAnchor.svr>` to the
        #: filling value.
        self.mappers = mappers if mappers else {}
        super(FillAnchor, self).__init__(svr, **kw)

    def provide(self):
        for key, value in self.mappers.items():
            getattr(self.svr, key).fill(value)


################################################################################
# Begin CFL evaluation.
class CflAnchor(sc.MeshAnchor):
    """
    Counting CFL numbers.  Use :py:attr:`MeshSolver.marchret
    <solvcon.solver.MeshSolver.marchret>` to return results.  Overrides
    :py:meth:`~solvcon.anchor.MeshAnchor.postmarch` method.

    Pair with :py:class:`CflHook`.
    """

    def __init__(self, svr, rsteps=None, **kw):
        """
        >>> from solvcon.testing import create_trivial_2d_blk
        >>> from solvcon.solver import MeshSolver
        >>> svr = MeshSolver(create_trivial_2d_blk())
        >>> ank = CflAnchor(svr) # doctest: +ELLIPSIS
        Traceback (most recent call last):
            ...
        TypeError: int() argument must be a string...
        >>> ank = CflAnchor(svr, 1)
        >>> ank.rsteps
        1
        """
        #: Steps to run (:py:class:`int`).
        self.rsteps = int(rsteps)
        super(CflAnchor, self).__init__(svr, **kw)

    def postmarch(self):
        svr = self.svr
        istep = svr.step_global
        rsteps = self.rsteps
        if istep > 0 and istep%rsteps == 0:
            # download data.
            ocfl = svr.ocfl[svr.ngstcell:]
            cfl = svr.cfl[svr.ngstcell:]
            # determine extremum.
            mincfl = ocfl.min()
            maxcfl = ocfl.max()
            nadj = (cfl==1).sum()
            # store.
            lst = svr.marchret.setdefault('cfl', [0.0, 0.0, 0, 0])
            lst[0] = mincfl
            lst[1] = maxcfl
            lst[2] = nadj
            lst[3] += nadj