# ===============================================================================
# Copyright 2018 ross
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expr
ess or implied.
# See the License for the specific language governing permissions and
# limitations under the Lice
nse.
# ===============================================================================


import logging
import os
import shutil
import subprocess
import time

from apptools.preferences.preference_binding import bind_preference
from pyface.confirmation_dialog import confirm
from pyface.constant import OK, YES
from pyface.directory_dialog import DirectoryDialog
from pyface.message_dialog import warning
from traits.api import Str, Enum, Bool, Int, Float, HasTraits, List, Instance, Button, CFloat
from traitsui.api import UItem, Item, View, TableEditor, VGroup, InstanceEditor, ListStrEditor, HGroup
from traitsui.table_column import ObjectColumn
from uncertainties import nominal_value, std_dev

from pychron.core.helpers.iterfuncs import groupby_group_id, groupby_key
from pychron.core.helpers.traitsui_shortcuts import okcancel_view
from pychron.core.pychron_traits import BorderVGroup
from pychron.envisage.icon_button_editor import icon_button_editor
from pychron.envisage.tasks.base_editor import grouped_name
from pychron.globals import globalv
from pychron.mdd import GEOMETRIES
from pychron.options.options_manager import MDDFigureOptionsManager
from pychron.paths import paths
from pychron.pipeline.nodes.base import BaseNode
from pychron.pipeline.nodes.figure import FigureNode
from pychron.processing.analyses.analysis_group import StepHeatAnalysisGroup
from pychron.regex import MDD_PARAM_REGEX


# EOF = 1


class MDDWorkspace(HasTraits):
    roots = List
    add_root_button = Button

    def _add_root_button_fired(self):
        dlg = DirectoryDialog(default_path=paths.mdd_data_dir)
        if dlg.open() == OK and dlg.path:
            name = os.path.basename(dlg.path)
            if os.path.isfile(os.path.join(dlg.path, '{}.in'.format(name))):
                self.roots.append(dlg.path)
            else:
                warning(None, 'Invalid MDD directory. {}. Directory must contain file '
                              'named {}.in'.format(dlg.path, name))

    def traits_view(self):
        w = BorderVGroup(UItem('roots', editor=ListStrEditor()), label='Workspaces')
        b = HGroup(icon_button_editor('add_root_button', 'add',
                                      tooltip='Add an MDD workspace (folder) to the available workspace list'))
        v = View(VGroup(b, w), title='Select a MDD Workspace')
        return v

    def _roots_default(self):
        r = []
        if globalv.mdd_workspace_debug:
            r = [
                # os.path.join(paths.mdd_data_dir, '66208-01'),
                os.path.join(paths.mdd_data_dir, '12H')
            ]
        return r


class MDDWorkspaceNode(BaseNode):
    name = 'MDD Workspace'
    workspace = Instance(MDDWorkspace, ())

    def run(self, state):
        state.mdd_workspace = self.workspace

    def traits_view(self):
        g = VGroup(UItem('workspace', style='custom', editor=InstanceEditor()))
        return okcancel_view(g)


fortranlogger = logging.getLogger('FortranProcess')


class MDDNode(BaseNode):
    executable_name = ''
    configuration_name = ''
    _dumpables = None

    root_dir = Str
    executable_root = Str

    def bind_preferences(self):
        bind_preference(self, 'executable_root', 'pychron.mdd.executable_root')

    def run(self, state):
        if state.mdd_workspace:
            for root in state.mdd_workspace.roots:
                self.root_dir = root

                self._write_configuration_file()
                os.chdir(root)
                fortranlogger.info('changing to workspace {}'.format(root))
                self.run_fortan()

    def _write_configuration_file(self):
        with open(self.configuration_path, 'w') as wfile:
            for d in self._dumpables:
                if d.startswith('!'):
                    v = d[1:]
                else:
                    v = getattr(self, d)

                if isinstance(v, bool):
                    v = int(v)
                line = '{}\n'.format(v)
                wfile.write(line)

    def run_fortan(self):
        path = self.executable_path
        name = self.executable_name

        fortranlogger.info('------ started {}'.format(name))
        p = subprocess.Popen([path],
                             stderr=subprocess.PIPE,
                             stdin=subprocess.PIPE,
                             stdout=subprocess.PIPE)

        while p.poll() is None:
            msg = p.stdout.readline().decode('utf8').strip()
            if msg == 'PYCHRON_INTERRUPT':
                self.handle_interrupt(p)
            else:
                self.handle_stdout(p, msg)
                fortranlogger.info(msg)
            time.sleep(1e-6)

        fortranlogger.info('------ complete {}'.format(name))
        self.post_fortran()

    def handle_stdout(self, proc, msg):
        pass

    def handle_interrupt(self, proc):
        pass

    def post_fortran(self):
        pass

    @property
    def configuration_path(self):
        if not self.configuration_name:
            raise NotImplementedError
        return self.get_path('{}.cl'.format(self.configuration_name))

    @property
    def executable_path(self):
        if not self.executable_name:
            raise NotImplementedError

        root = self.executable_root
        if not root:
            root = paths.clovera_root

        return os.path.join(root, self.executable_name)

    @property
    def rootname(self):
        return os.path.basename(self.root_dir)

    def get_path(self, name):
        return os.path.join(self.root_dir, name)


class MDDLabTableNode(MDDNode):
    name = 'MDD Lab Table'

    temp_offset = Float
    time_offset = Float

    def bind_preferences(self):
        bind_preference(self, 'temp_offset', 'pychron.mdd.default_temp_offset')

    def run(self, state):
        # from the list of unknowns in the current state
        # assemble the <sample>.in file
        # for use with files_py
        roots = []
        state.mdd_workspace = MDDWorkspace()
        for gid, unks in groupby_group_id(state.unknowns):
            unks = list(unks)
            unk = unks[0]
            name = '{}-{:02n}'.format(unk.identifier, unk.aliquot)
            root = os.path.join(paths.mdd_data_dir, name)
            if os.path.isdir(root):
                if confirm(None, '{} already exists. Backup existing?'.format(root)) == YES:
                    head, tail = os.path.split(root)
                    dest = os.path.join(head, '~{}'.format(tail))
                    if os.path.isdir(dest):
                        shutil.rmtree(dest)

                    shutil.move(root, dest)

            if not os.path.isdir(root):
                os.mkdir(root)

            roots.append(root)
            ag = StepHeatAnalysisGroup(analyses=unks)
            with open(os.path.join(root, '{}.in'.format(name)), 'w') as wfile:
                step = 0
                for unk in ag.analyses:
                    if unk.age > 0:
                        cum39 = ag.cumulative_ar39(step)
                        line = self._assemble(step, unk, cum39)
                        wfile.write(line)
                        step += 1

        state.mdd_workspace.roots = roots

    def _assemble(self, step, unk, cum39):
        """
        step, T(C), t(min), 39mol, %error 39, cum ar39, age, age_er, age_er_w_j, cl_age, cl_age_er

        cl_age and cl_age_er are currently ignored. Ar/Ar age is used as a placeholder instead
        :param unk:
        :return:
        """
        temp = unk.extract_value
        time_at_temp = unk.ex