#!/usr/bin/env python
# -*- coding: utf-8 -*-
# #########################################################################
# Copyright (c) 2020, UChicago Argonne, LLC. All rights reserved.         #
#                                                                         #
# Copyright 2020. UChicago Argonne, LLC. This software was produced       #
# under U.S. Government contract DE-AC02-06CH11357 for Argonne National   #
# Laboratory (ANL), which is operated by UChicago Argonne, LLC for the    #
# U.S. Department of Energy. The U.S. Government has rights to use,       #
# reproduce, and distribute this software.  NEITHER THE GOVERNMENT NOR    #
# UChicago Argonne, LLC MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR        #
# ASSUMES ANY LIABILITY FOR THE USE OF THIS SOFTWARE.  If software is     #
# modified to produce derivative works, such modified software should     #
# be clearly marked, so as not to confuse it with the version available   #
# from ANL.                                                               #
#                                                                         #
# Additionally, 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 UChicago Argonne, LLC, Argonne National       #
#       Laboratory, ANL, the U.S. Government, 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 UChicago Argonne, LLC 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 UChicago     #
# Argonne, LLC 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.                                             #
# #########################################################################

import numpy

import scipy.constants as codata
m2ev = codata.c * codata.h / codata.e

from AnyQt.QtWidgets import QMessageBox

from orangewidget import gui
from orangewidget.settings import Setting
from orangewidget.widget import Input, Output

from oasys2.canvas.util.canvas_util import add_widget_parameters_to_module

from oasys2.widget.widget import OWWidget, OWAction
from oasys2.widget import gui as oasysgui
from oasys2.widget.gui import ConfirmDialog, Styles
from oasys2.widget.util import congruence
from oasys2.widget.util.widget_objects import TriggerIn, TriggerOut
from oasys2.widget.util.exchange import DataExchangeObject

try:
    from orangecontrib.shadow4.util.shadow4_util import ShadowCongruence, ShadowPlot
except ImportError:
    pass

AUTOBINNING_FILE = "autobinning.dat"
FILTERS_FILE     = "filters.dat"
NONE             = numpy.nan

class EnergyBinning(object):
    def __init__(self,
                 energy_value = 0.0,
                 energy_value_to = None,
                 energy_step       = 0.0,
                 power_step        = None):
        self.energy_value    = energy_value
        self.energy_value_to = energy_value_to
        self.energy_step     = energy_step
        self.power_step      = power_step

    def __str__(self):
        return str(self.energy_value) + ", " + str(self.energy_value_to) + ", " + str(self.energy_step) + ", " + str(self.power_step)

class PowerLoopPoint(OWWidget):

    name = "Thermal Load Loop Point"
    description = "Tools: Thermal Loop Point"
    icon = "icons/cycle_power.png"
    maintainer = "Luca Rebuffi"
    maintainer_email = "lrebuffi(@at@)anl.gov"
    priority = 6.0
    category = "User Defined"
    keywords = ["data", "file", "load", "read"]

    class Inputs:
        trigger         = Input("Trigger", TriggerIn, default=True, auto_summary=False)
        energy_spectrum = Input("Energy Spectrum", DataExchangeObject, default=True, auto_summary=False)
        filters         = Input("Filters", DataExchangeObject, default=True, auto_summary=False)
    
    class Outputs:
        trigger = Output("Trigger", TriggerOut, default=True, auto_summary=False)
    
    want_main_area = 1

    _run_loop     = True
    _suspend_loop = False

    current_new_object       = 0
    number_of_new_objects    = 0
    total_current_new_object = 0
    total_new_objects        = Setting(0)

    energies              = Setting("")
    seed_increment        = Setting(1)
    autobinning           = Setting(1)
    auto_n_step           = Setting(1001)
    auto_perc_total_power = Setting(99)
    send_power_step       = Setting(0)

    load_file_mode        = Setting(1) # automatic
    skip_rows             = Setting(1)
    autobinning_file_name = Setting(AUTOBINNING_FILE)
    filters_file_name     = Setting(FILTERS_FILE)

    recovery_last_energy_step = -1
    recovery_file_name        = "<Partial Result before Crash>"

    current_energy_binning = -1
    current_energy_value   = NONE
    current_energy_step    = None
    current_power_step     = None

    energy_binnings  = None
    external_binning = False

    filters       = None
    spectrum_data = None

    #################################
    process_last = True
    #################################

    def __init__(self):
        self.runaction = OWAction("Start", self)
        self.runaction.triggered.connect(self.start_loop)
        self.addAction(self.runaction)

        self.runaction = OWAction("Stop", self)
        self.runaction.triggered.connect(self.stop_loop)
        self.addAction(self.runaction)

        runaction = OWAction("Suspend", self)
        runaction.triggered.connect(self.suspend_loop)
        self.addAction(runaction)

        runaction = OWAction("Restart", self)
        runaction.triggered.connect(self.restart_loop)
        self.addAction(runaction)

        runaction = OWAction(None, self)
        runaction.setSeparator(True)
        self.addAction(runaction)

        runaction = OWAction("Reload Spectrum and Filters", self)
        runaction.triggered.connect(self.read_spectrum_and_filters_file)
        self.addAction(runaction)

        runaction = OWAction("Reload Spectrum Only", self)
        runaction.triggered.connect(self.read_spectrum_file_only)
        self.addAction(runaction)

        self.setFixedWidth(1200)
        self.setFixedHeight(730)

        button_box = oasysgui.widgetBox(self.controlArea, "", addSpace=True, orientation="horizontal")

        self.start_button = gui.button(button_box, self, "Start", callback=self.start_loop)
        self.start_button.setFixedHeight(35)

        stop_button = gui.button(button_box, self, "Stop", callback=self.stop_loop)
        stop_button.setStyleSheet("color: red; font-weight: bold; font-style: italic;")
        stop_button.setFixedHeight(35)

        self.stop_button = stop_button

        button_box = oasysgui.widgetBox(self.controlArea, "", addSpace=True, orientation="horizontal")

        suspend_button = gui.button(button_box, self, "Suspend", callback=self.suspend_loop)
        suspend_button.setStyleSheet("color: orange; font-weight: bold;")
        suspend_button.setFixedHeight(35)
        
        self.re_start_button = gui.button(button_box, self, "Restart", callback=self.restart_loop)
        self.re_start_button.setFixedHeight(35)
        self.re_start_button.setEnabled(False)

        tabs = oasysgui.tabWidget(self.controlArea)
        
        tab_loop     = oasysgui.createTabPage(tabs, "Loop")
        tab_recovery = oasysgui.createTabPage(tabs, "Advanced")

        left_box_1 = oasysgui.widgetBox(tab_loop, "", addSpace=False, orientation="vertical", width=385, height=560)

        oasysgui.lineEdit(left_box_1, self, "seed_increment", "Source Montecarlo Seed Increment", labelWidth=250, valueType=int, orientation="horizontal")

        gui.separator(left_box_1)

        gui.comboBox(left_box_1, self, "autobinning", label="Energy Binning",
                     items=["Manual", "Automatic (Constant Power)", "Automatic (Constant Energy)"], labelWidth=150,
                     callback=self.set_autobinning, sendSelectedValue=False, orientation="horizontal")

        self.autobinning_box_1 = oasysgui.widgetBox(left_box_1, "", addSpace=False, orientation="vertical", height=60)
        self.autobinning_box_2 = oasysgui.widgetBox(left_box_1, "", addSpace=False, orientation="vertical", height=155)

        gui.separator(self.autobinning_box_1)

        gui.button(self.autobinning_box_1, self, "Compute Bins", callback=self.calculate_energy_binnings, height=35)

        gui.separator(self.autobinning_box_1)

        header = oasysgui.widgetLabel(self.autobinning_box_1, "Energy From, Energy To, Energy Step [eV]")
        header.setStyleSheet("font-size: 10pt")

        # ----------------------------------------------

        oasysgui.lineEdit(self.autobinning_box_2, self, "auto_n_step", "Number of Steps", labelWidth=250, valueType=int, orientation="horizontal")
        oasysgui.lineEdit(self.autobinning_box_2, self, "auto_perc_total_power", "% Total Power", labelWidth=250, valueType=float, orientation="horizontal")
        gui.comboBox(self.autobinning_box_2, self, "send_power_step", label="Send Power Step", items=["No", "Yes"], labelWidth=350, sendSelectedValue=False, orientation="horizontal")

        gui.separator(self.autobinning_box_2)

        button_box = oasysgui.widgetBox(self.autobinning_box_2, "", addSpace=False, orientation="horizontal")

        gui.button(button_box, self, "Reload Spectrum and Filters", callback=self.read_spectrum_and_filters_file, height=35)
        gui.button(button_box, self, "Reload Spectrum Only",        callback=self.read_spectrum_file_only, height=35)

        gui.separator(self.autobinning_box_2)

        header = oasysgui.widgetLabel(self.autobinning_box_2, "Energy Value [eV], Energy Step [eV], Power Step [W]")
        header.setStyleSheet("font-size: 10pt")

        def write_text(): self.energies = self.text_area.toPlainText()

        self.text_area = oasysgui.textArea(height=191, width=385, readOnly=False)
        self.text_area.setText(self.energies)
        self.text_area.setStyleSheet("background-color: white; font-family: Courier, monospace;")
        self.text_area.textChanged.connect(write_text)

        left_box_1.layout().addWidget(self.text_area)

        gui.separator(left_box_1)

        self.le_total_new_objects = oasysgui.lineEdit(left_box_1, self, "total_new_objects", "Total Energy Values", labelWidth=250, valueType=int, orientation="horizontal")
        self.le_total_new_objects.setReadOnly(True)
        self.le_total_new_objects.setStyleSheet(Styles.line_edit_read_only)

        self.le_number_of_new_objects = oasysgui.lineEdit(left_box_1, self, "number_of_new_objects", "Current Binning Energy Values", labelWidth=250, valueType=int, orientation="horizontal")
        self.le_number_of_new_objects.setReadOnly(True)
        self.le_number_of_new_objects.setStyleSheet(Styles.line_edit_read_only)

        gui.separator(left_box_1)

        le_total_current_value = oasysgui.lineEdit(left_box_1, self, "total_current_new_object", "Total New " + self.get_object_name(), labelWidth=250, valueType=int, orientation="horizontal")
        le_total_current_value.setReadOnly(True)
        le_total_current_value.setStyleSheet(Styles.line_edit_read_only)

        le_current_value = oasysgui.lineEdit(left_box_1, self, "current_new_object", "Current Binning New " + self.get_object_name(), labelWidth=250, valueType=int, orientation="horizontal")
        le_current_value.setReadOnly(True)
        le_current_value.setStyleSheet(Styles.line_edit_read_only)

        le_current_energy_value = oasysgui.lineEdit(left_box_1, self, "current_energy_value", "Current Energy Value", labelWidth=250, valueType=float, orientation="horizontal")
        le_current_energy_value.setReadOnly(True)
        le_current_energy_value.setStyleSheet(Styles.line_edit_read_only)

        # ----------------------------------------------

        left_box_2 = oasysgui.widgetBox(tab_recovery, "Input Files Management", addSpace=False, orientation="vertical", width=385, height=150)

        gui.comboBox(left_box_2, self, "load_file_mode", label="Input Files Mode",
                     items=["Manual", "Automatic"], labelWidth=150,
                     callback=self.set_load_file_mode, sendSelectedValue=False, orientation="horizontal")

        self.load_file_mode_box_1 = oasysgui.widgetBox(left_box_2, "", addSpace=False, orientation="vertical", height=100)
        self.load_file_mode_box_2 = oasysgui.widgetBox(left_box_2, "", addSpace=False, orientation="vertical", height=100)

        gui.separator(self.load_file_mode_box_2)
        oasysgui.lineEdit(self.load_file_mode_box_1, self, "skip_rows", "Skip Rows", labelWidth=260, valueType=int, orientation="horizontal")

        load_box = oasysgui.widgetBox(self.load_file_mode_box_1, "", addSpace=False, orientation="horizontal", height=30)
        self.le_autobinning_file_name = oasysgui.lineEdit(load_box, self, "autobinning_file_name", "Energy Spectrum", labelWidth=110, valueType=str, orientation="horizontal")
        gui.button(load_box, self, "...", callback=self.select_autobinning_file, width=25)

        load_box = oasysgui.widgetBox(self.load_file_mode_box_1, "", addSpace=False, orientation="horizontal", height=30)
        self.le_filters_file_name = oasysgui.lineEdit(load_box, self, "filters_file_name", "Filters File", labelWidth=110, valueType=str, orientation="horizontal")
        gui.button(load_box, self, "...", callback=self.select_filters_file, width=25)

        self.set_load_file_mode()

        left_box_2 = oasysgui.widgetBox(tab_recovery, "Crash Recovery", addSpace=False, orientation="vertical", width=385)

        oasysgui.lineEdit(left_box_2, self, "recovery_last_energy_step", "Last Energy Step #", labelWidth=260, valueType=int, orientation="horizontal")

        load_box = oasysgui.widgetBox(left_box_2, "", addSpace=False, orientation="horizontal", height=30)
        self.le_recovery_file_name = oasysgui.lineEdit(load_box, self, "recovery_file_name", "Extract from File", labelWidth=110, valueType=str, orientation="horizontal")
        gui.button(load_box, self, "...", callback=self.select_recovery_file, width=25)

        gui.button(left_box_2, self, "Restart from Crash", callback=self.restart_loop_from_crash, height=35)

        gui.rubber(self.controlArea)

        # ----------------------------------------------

        tabs = oasysgui.tabWidget(self.mainArea)
        tabs.setFixedHeight(self.height()-40)
        tabs.setFixedWidth(775)

        tab_plot = oasysgui.createTabPage(tabs, "Cumulated Power")
        tab_flux = oasysgui.createTabPage(tabs, "Spectral Flux")
        tab_fil = oasysgui.createTabPage(tabs, "Filter")

        self.cumulated_power_plot = oasysgui.plotWindow(tab_plot, position=True)
        self.cumulated_power_plot.setFixedHeight(self.height()-80)
        self.cumulated_power_plot.setFixedWidth(765)
        self.cumulated_power_plot.setGraphXLabel("Energy [eV]")
        self.cumulated_power_plot.setGraphYLabel("Cumulated Power [W]")
        self.cumulated_power_plot.setGraphTitle("Cumulated Power")

        self.spectral_flux_plot = oasysgui.plotWindow(tab_flux, position=True)
        self.spectral_flux_plot.setFixedHeight(self.height()-80)
        self.spectral_flux_plot.setFixedWidth(765)
        self.spectral_flux_plot.setGraphXLabel("Energy [eV]")
        self.spectral_flux_plot.setGraphYLabel("Flux [ph/s/.1%bw]")
        self.spectral_flux_plot.setGraphTitle("Spectral Flux")

        self.filter_plot = oasysgui.plotWindow(tab_fil, position=True)
        self.filter_plot.setFixedHeight(self.height()-80)
        self.filter_plot.setFixedWidth(765)
        self.filter_plot.setGraphXLabel("Energy [eV]")
        self.filter_plot.setGraphYLabel("Intensity Factor")
        self.filter_plot.setGraphTitle("Filter on Flux")

        self.set_autobinning()

    def set_autobinning(self):
        self.autobinning_box_1.setVisible(self.autobinning==0)
        self.autobinning_box_2.setVisible(self.autobinning==1 or self.autobinning==2)
        self.text_area.setReadOnly(self.autobinning>=1)
        self.text_area.setFixedHeight(185 if self.autobinning>=1 else 280)

    def set_load_file_mode(self):
        self.load_file_mode_box_1.setVisible(self.load_file_mode==0)
        self.load_file_mode_box_2.setVisible(self.load_file_mode==1)

    def select_autobinning_file(self):
        self.le_autobinning_file_name.setText(oasysgui.selectFileFromDialog(self, self.autobinning_file_name, "Select File", file_extension_filter="Text Files (*.txt *.dat)"))

    def select_recovery_file(self):
        self.le_recovery_file_name.setText(oasysgui.selectFileFromDialog(self, self.recovery_file_name, "Select File", file_extension_filter="HDF5 Files (*.hdf5)"))

        try:
            plot_file = ShadowPlot.PlotXYHdf5File(congruence.checkDir(self.recovery_file_name), mode="r")

            try:
                self.recovery_last_energy_step = plot_file.get_attribute(attribute_name="current_step", dataset_name="additional_data")
            except:
                raise ValueError("Last step non available in this file")

            plot_file.close()
        except Exception as e:
            QMessageBox.critical(self, "Error", str(e), QMessageBox.Ok)

            if self.IS_DEVELOP: raise e

    def select_filters_file(self):
        self.le_filters_file_name.setText(oasysgui.selectFileFromDialog(self, self.filters_file_name, "Select File", file_extension_filter="Text Files (*.txt *.dat)"))

    def read_spectrum_file(self, reset_filters=True):
        try:
            if self.load_file_mode == 1:
                autobinning_file_name = AUTOBINNING_FILE
                skip_rows = 1
            else:
                autobinning_file_name  = congruence.checkFile(self.autobinning_file_name)
                skip_rows              = congruence.checkPositiveNumber(self.skip_rows, "Skip Rows")

            if reset_filters:
                self.filters=None
                self.filter_plot.clear()

            data = numpy.loadtxt(autobinning_file_name, skiprows=skip_rows)

            calculated_data = DataExchangeObject(program_name="Shadow4", widget_name="PowerLoopPoint")
            calculated_data.add_content("spectrum_data", data)

            self.accept_energy_spectrum(calculated_data)
        except Exception as e:
            QMessageBox.critical(self, "Error", str(e), QMessageBox.Ok)

            if self.IS_DEVELOP: raise e

    def read_spectrum_file_only(self):
        self.read_spectrum_file(True)

    def read_spectrum_and_filters_file(self):
        try:
            if self.load_file_mode == 1:
                filter_file_name = FILTERS_FILE
                skip_rows = 1
            else:
                filter_file_name  = congruence.checkFile(self.filters_file_name)
                skip_rows         = congruence.checkPositiveNumber(self.skip_rows, "Skip Rows")

            data = numpy.loadtxt(filter_file_name, skiprows=skip_rows)

            calculated_data = DataExchangeObject(program_name="ShadowOui", widget_name="PowerLoopPoint")
            calculated_data.add_content("filters_data", data)

            self.accept_filters(calculated_data)
        except Exception as e:
            self.filters = None

            QMessageBox.critical(self, "Error", str(e), QMessageBox.Ok)

            if self.IS_DEVELOP: raise e

        self.read_spectrum_file(False)

    
    @Inputs.filters
    def accept_filters(self, exchange_data):
        if not exchange_data is None:
            try:
                try:
                    data = exchange_data.get_content("filters_data")
                    write_file = False
                except:
                    if not exchange_data.get_program_name() in ["XOPPY", "Shadow4-Thermal"]: raise ValueError("Only XOPPY or Shadow4-Thermal widgets are accepted")
                    if not exchange_data.get_widget_name() in ["XF1F2", "POWER", "XCRYSTAL", "MULTILAYER", "TOTAL_FILTER"]: raise ValueError("Only XF1F2, POWER, XCRYSTAL, MULTILAYER, Total Filter Calculator widgets are accepted")

                    if exchange_data.get_widget_name() == "TOTAL_FILTER":
                        data = exchange_data.get_content("total_filter")
                    elif exchange_data.get_widget_name() in ["XF1F2"]:
                        data = exchange_data.get_content("xoppy_data")
                    else:
                        input_data = exchange_data.get_content("xoppy_data")

                        if exchange_data.get_widget_name() == "POWER":
                            data = numpy.zeros((input_data.shape[0], 2))
                            data[:, 0] = input_data[:, 0]
                            data[:, 1] = input_data[:, -1]/input_data[:, 1]
                        else:
                            if exchange_data.get_widget_name() == "XCRYSTAL":
                                reflectivity_s = input_data[:, 5]
                                reflectivity_p = input_data[:, 6]
                            elif exchange_data.get_widget_name() == "MULTILAYER":
                                reflectivity_s = input_data[:, 1]
                                reflectivity_p = input_data[:, 2]
                            
                            reflectivity_s[numpy.where(numpy.isnan(reflectivity_s))] = 0
                            reflectivity_p[numpy.where(numpy.isnan(reflectivity_p))] = 0

                            data = numpy.zeros((input_data.shape[0], 2))
                            data[:, 0] = input_data[:, 0]
                            data[:, 1] = 0.5*(reflectivity_p + reflectivity_s)

                    write_file = True

                self.filters = data

                energies          = self.filters[:, 0]
                intensity_factors = self.filters[:, 1]

                if write_file:
                    file = open(FILTERS_FILE, "w")
                    file.write("Energy Filter")

                    for energy, intensity_factor in zip(energies, intensity_factors):
                        file.write("\n" + str(energy) + " " + str(intensity_factor))

                    file.flush()
                    file.close()

                    self.load_file_mode = 1 # back to automatic
                    self.set_load_file_mode()

                self.filter_plot.clear()
                self.filter_plot.addCurve(energies, intensity_factors, replace=True, legend="Intensity Factor")
                self.filter_plot.setGraphXLabel("Energy [eV]")
                self.filter_plot.setGraphYLabel("Intensity Factor")
                self.filter_plot.setGraphTitle("Filter on Flux")

                if self.autobinning==0:
                    if write_file: QMessageBox.information(self, "Info", "File filters.dat written on working directory, switch to Automatic binning to load it", QMessageBox.Ok)
                else:
                    if write_file: QMessageBox.information(self, "Info", "File filters.dat written on working directory", QMessageBox.Ok)

                if not self.spectrum_data is None:
                    calculated_data = DataExchangeObject(program_name="ShadowOui", widget_name="PowerLoopPoint")
                    calculated_data.add_content("spectrum_data", self.spectrum_data)

                    self.accept_energy_spectrum(calculated_data)

            except Exception as e:
                QMessageBox.critical(self, "Error", str(e), QMessageBox.Ok)

                if self.IS_DEVELOP: raise e
    
    @Inputs.energy_spectrum
    def accept_energy_spectrum(self, exchange_data):
        if not exchange_data is None:
            try:
                try:
                    data = exchange_data.get_content("spectrum_data")
                    write_file = False
                except:
                    try:    data = exchange_data.get_content("srw_data")
                    except: data = exchange_data.get_content("xoppy_data")
                    write_file = True

                self.spectrum_data = data.copy()

                energies                              = data[:, 0]
                flux_through_finite_aperture          = data[:, 1]
                flux_through_finite_aperture_filtered = flux_through_finite_aperture.copy()

                if not self.filters is None:
                    flux_through_finite_aperture_filtered *= numpy.interp(energies, self.filters[:, 0], self.filters[:, 1])
                    use_filters = True
                else:
                    use_filters = False

                if write_file:
                    file = open(AUTOBINNING_FILE, "w")
                    file.write("Energy Flux")

                    for energy, flux in zip(energies, flux_through_finite_aperture):
                        file.write("\n" + str(energy) + " " + str(flux))

                    file.flush()
                    file.close()

                    self.load_file_mode = 1 # back to automatic
                    self.set_load_file_mode()

                if self.autobinning==0:
                    if write_file: QMessageBox.information(self, "Info", "File autobinning.dat written on working directory, switch to Automatic binning to load it", QMessageBox.Ok)
                else:
                    if write_file: QMessageBox.information(self, "Info", "File autobinning.dat written on working directory", QMessageBox.Ok)

                    congruence.checkStrictlyPositiveNumber(self.auto_n_step, "(Auto) % Number of Steps")
                    congruence.checkStrictlyPositiveNumber(self.auto_perc_total_power, "(Auto) % Total Power")

                    energy_step = energies[1] - energies[0]

                    # last energy do not contribute to the total (the approximated integral of the power is out of the range)

                    power           = flux_through_finite_aperture * (1e3 * energy_step * codata.e)
                    cumulated_power = numpy.cumsum(power)
                    total_power     = cumulated_power[-1]

                    if use_filters:
                        power_filtered           = flux_through_finite_aperture_filtered * (1e3 * energy_step * codata.e)
                        cumulated_power_filtered = numpy.cumsum(power_filtered)
                        total_power_filtered     = cumulated_power_filtered[-1]

                    self.text_area.clear()

                    self.cumulated_power_plot.clear()
                    self.spectral_flux_plot.clear()

                    if use_filters:
                        total_power_binning                  = total_power_filtered
                        cumulated_power_binning              = cumulated_power_filtered
                        flux_through_finite_aperture_binning = flux_through_finite_aperture_filtered
                    else:
                        total_power_binning                  = total_power
                        cumulated_power_binning              = cumulated_power
                        flux_through_finite_aperture_binning = flux_through_finite_aperture

                    good = numpy.where(cumulated_power_binning <= self.auto_perc_total_power*0.01*total_power_binning)

                    energies                             = energies[good]
                    cumulated_power                      = cumulated_power[good]
                    cumulated_power_binning              = cumulated_power_binning[good]
                    flux_through_finite_aperture         = flux_through_finite_aperture[good]
                    flux_through_finite_aperture_binning = flux_through_finite_aperture_binning[good]

                    if use_filters:
                        cumulated_power_filtered = cumulated_power_filtered[good]
                        flux_through_finite_aperture_filtered = flux_through_finite_aperture_filtered[good]

                    if self.autobinning==1: # constant power
                        interpolated_cumulated_power = numpy.linspace(start=numpy.min(cumulated_power_binning), stop=numpy.max(cumulated_power_binning), num=self.auto_n_step+1)
                        interpolated_energies        = numpy.interp(interpolated_cumulated_power, cumulated_power_binning, energies)
                        energy_steps = numpy.ediff1d(interpolated_energies)

                        interpolated_energies        = interpolated_energies[:-1]
                        interpolated_cumulated_power = interpolated_cumulated_power[:-1]

                        power_steps  = numpy.ones(self.auto_n_step)*cumulated_power_binning[-1]/self.auto_n_step

                    elif self.autobinning==2: # constant energy
                        minimum_energy = energies[0]
                        maximum_energy = energies[-1]
                        energy_step = (maximum_energy-minimum_energy)/self.auto_n_step

                        interpolated_energies        = numpy.arange(minimum_energy, maximum_energy, energy_step)
                        interpolated_cumulated_power = numpy.interp(interpolated_energies, energies, cumulated_power_binning)

                        energy_steps = numpy.ones(self.auto_n_step)*energy_step
                        power_steps  = numpy.ediff1d(numpy.append(numpy.zeros(1), interpolated_cumulated_power))

                    flux_steps = numpy.interp(interpolated_energies, energies, flux_through_finite_aperture_binning)

                    self.energy_binnings = []
                    self.total_new_objects = 0

                    if not use_filters:
                        self.cumulated_power_plot.addCurve(energies, cumulated_power, replace=True, legend="Cumulated Power")
                    else:
                        self.cumulated_power_plot.addCurve(energies, cumulated_power, replace=True, linestyle="--", legend="Cumulated Power")
                        self.cumulated_power_plot.addCurve(energies, cumulated_power_filtered, replace=False, legend="Cumulated Power (Filtered)", color="#006400")
                    self.cumulated_power_plot.setGraphXLabel("Energy [eV]")
                    self.cumulated_power_plot.setGraphYLabel("Cumulated " + ("" if self.filters is None else " (Filtered)") + " Power" )
                    self.cumulated_power_plot.setGraphTitle("Total Power: " + str(round(power_steps.sum(), 2)) + " W")

                    if not use_filters:
                        self.spectral_flux_plot.addCurve(energies, flux_through_finite_aperture, replace=True, legend="Spectral Flux")
                    else:
                        self.spectral_flux_plot.addCurve(energies, flux_through_finite_aperture, replace=True, linestyle="--", legend="Spectral Flux")
                        self.spectral_flux_plot.addCurve(energies, flux_through_finite_aperture_filtered, replace=False, legend="Spectral Flux (Filtered)", color="#006400")
                    self.spectral_flux_plot.setGraphXLabel("Energy [eV]")
                    self.spectral_flux_plot.setGraphYLabel("Flux [ph/s/.1%bw]")
                    self.spectral_flux_plot.setGraphTitle("Spectral Flux" + ("" if use_filters else " (Filtered)"))

                    self.cumulated_power_plot.addCurve(interpolated_energies, interpolated_cumulated_power, replace=False, legend="Energy Binning", color="red", linestyle=" ", symbol="+")
                    self.spectral_flux_plot.addCurve(interpolated_energies, flux_steps, replace=False, legend="Energy Binning", color="red", linestyle=" ", symbol="+")

                    self.cumulated_power_plot.getLegendsDockWidget().setVisible(True)
                    self.spectral_flux_plot.getLegendsDockWidget().setVisible(True)

                    text = ""
                    for energy_value, energy_step, power_step in zip(interpolated_energies, energy_steps, power_steps):
                        energy_binning = EnergyBinning(energy_value=round(energy_value, 3),
                                                       energy_step=round(energy_step, 3),
                                                       power_step=round(power_step, 4))

                        text += str(round(energy_value, 3)) + ", " + \
                                str(round(energy_step, 3))  + ", " + \
                                str(round(power_step, 4)) + "\n"

                        self.energy_binnings.append(energy_binning)
                        self.total_new_objects += 1

                    self.text_area.setText(text)

                    self.external_binning = True
            except Exception as e:
                QMessageBox.critical(self, "Error", str(e), QMessageBox.Ok)

                if self.IS_DEVELOP: raise e
        else:
            self.energy_binnings = None
            self.total_new_objects = 0
            self.external_binning = False
            self.text_area.setText("")

    def calculate_energy_binnings(self):
        if not self.external_binning:
            self.total_new_objects = 0

            rows = self.energies.split("\n")
            for row in rows:
                data = row.split(",")
                if len(data) == 3:
                    if self.energy_binnings is None: self.energy_binnings = []

                    energy_from = float(data[0].strip())
                    energy_to   = float(data[1].strip())
                    energy_step = float(data[2].strip())

                    energy_binning = EnergyBinning(energy_value=energy_from,
                                                   energy_value_to=energy_to,
                                                   energy_step=energy_step)
                    self.energy_binnings.append(energy_binning)

                    self.total_new_objects += int((energy_to - energy_from) / energy_step)

    def calculate_number_of_new_objects(self):
        if len(self.energy_binnings) > 0:
            if self.external_binning:
                self.number_of_new_objects = 1
            else:
                energy_binning = self.energy_binnings[self.current_energy_binning]

                self.number_of_new_objects = int((energy_binning.energy_value_to - energy_binning.energy_value) / energy_binning.energy_step)
        else:
            self.number_of_new_objects = 0

    def reset_values(self):
        self.current_new_object = 0
        self.total_current_new_object = 0
        self.current_energy_value = NONE
        self.current_energy_step = None
        self.current_energy_binning = -1
        self.current_power_step = None

        if not self.external_binning: self.energy_binnings = None

    def start_loop(self):
        try:
            if self.spectrum_data is None: raise ValueError("Initialize Loop point from Spectrum Data (Reload or from Widget)")

            self.calculate_energy_binnings()

            self.current_new_object = 1
            self.total_current_new_object = 1
            self.current_energy_binning = 0
            self.current_energy_value             = round(self.energy_binnings[0].energy_value, 8)
            self.current_energy_step              = round(self.energy_binnings[0].energy_step, 8)
            self.current_power_step               = None if self.energy_binnings[0].power_step is None else (None if self.send_power_step==0 else round(self.energy_binnings[0].power_step, 8))
            self.calculate_number_of_new_objects()

            self.start_button.setEnabled(False)
            self.text_area.setEnabled(False)
            self.setStatusMessage("Running " + self.get_object_name() + " " + str(self.total_current_new_object) + " of " + str(self.total_new_objects))
            self.Outputs.trigger.send(TriggerOut(new_object=True,
                                                 additional_parameters={"energy_value"   : self.current_energy_value,
                                                                        "energy_step"    : self.current_energy_step,
                                                                        "power_step"     : -1 if self.current_power_step is None else self.current_power_step,
                                                                        "seed_increment" : self.seed_increment,
                                                                        "current_step"   : self.total_current_new_object,
                                                                        "total_steps"    : self.total_new_objects,
                                                                        "start_event"    : True}))
        except Exception as e:
            if self.IS_DEVELOP : raise e
            else: pass

    def stop_loop(self):
        try:
            if ConfirmDialog.confirmed(parent=self, message="Confirm Interruption of the Loop?"):
                self._run_loop = False
                self.reset_values()
                self.setStatusMessage("Interrupted by user")
        except Exception as e:
            if self.IS_DEVELOP : raise e
            else: pass

    def suspend_loop(self):
        try:
            if ConfirmDialog.confirmed(parent=self, message="Confirm Suspension of the Loop?"):
                self._run_loop = False
                self._suspend_loop = True
                self.stop_button.setEnabled(False)
                self.re_start_button.setEnabled(True)
                self.setStatusMessage("Suspended by user")
        except Exception as e:
            if self.IS_DEVELOP : raise e
            else: pass

    def restart_loop_from_crash(self):
        try:
            if self.energy_binnings is None: raise Exception("Reload Spectrum (and Filters, if present) before restarting the loop")

            self.current_energy_binning   = self.recovery_last_energy_step + 1
            self.total_current_new_object = self.recovery_last_energy_step + 1

            if self.current_energy_binning < len(self.energy_binnings):
                energy_binning = self.energy_binnings[self.current_energy_binning]

                self.current_new_object = 1
                self.calculate_number_of_new_objects()
                self.current_energy_value = round(energy_binning.energy_value, 8)
                self.current_power_step = None if energy_binning.power_step is None else (None if self.send_power_step == 0 else round(energy_binning.power_step, 8))

                self.setStatusMessage("Running " + self.get_object_name() + " " + str(self.total_current_new_object) + " of " + str(self.total_new_objects))
                self.start_button.setEnabled(False)
                self.text_area.setEnabled(False)
                self.Outputs.trigger.send(TriggerOut(new_object=True,
                                                additional_parameters={"energy_value": self.current_energy_value,
                                                                       "energy_step": energy_binning.energy_step,
                                                                       "power_step": -1 if self.current_power_step is None else self.current_power_step,
                                                                       "current_step": self.total_current_new_object,
                                                                       "total_steps": self.total_new_objects,
                                                                       "seed_increment": self.seed_increment,
                                                                       "start_event": False}))
        except Exception as e:
            QMessageBox.critical(self, "Error", str(e), QMessageBox.Ok)

            if self.IS_DEVELOP: raise e

    def restart_loop(self):
        try:
            self._run_loop = True
            self._suspend_loop = False
            self.stop_button.setEnabled(True)
            self.re_start_button.setEnabled(False)
            self.pass_trigger(TriggerIn(new_object=True))
        except Exception as e:
            if self.IS_DEVELOP : raise e
            else: pass

    def get_object_name(self):
        return "Shadow4 Beam"

    @Inputs.trigger
    def pass_trigger(self, trigger):
        if self._run_loop:
            if trigger:
                if trigger.interrupt:
                    self.reset_values()
                    self.start_button.setEnabled(True)
                    self.text_area.setEnabled(True)
                    self.setStatusMessage("")
                    self.Outputs.trigger.send(TriggerOut(new_object=False))
                elif trigger.new_object:
                    if self.energy_binnings is None: self.calculate_energy_binnings()

                    if self.current_energy_binning == -1:
                        QMessageBox.critical(self, "Error", "Power Loop has to be started properly: press the button Start", QMessageBox.Ok)
                        return

                    if self.current_energy_binning < len(self.energy_binnings):
                        energy_binning = self.energy_binnings[self.current_energy_binning]

                        self.total_current_new_object += 1

                        if self.current_new_object < self.number_of_new_objects:
                            if self.current_energy_value == NONE:
                                self.current_new_object = 1
                                self.calculate_number_of_new_objects()
                                self.current_energy_value = round(energy_binning.energy_value, 8)
                            else:
                                self.current_new_object += 1
                                self.current_energy_value = round(self.current_energy_value + energy_binning.energy_step, 8)

                            self.current_power_step = None if energy_binning.power_step is None else (None if self.send_power_step==0 else round(energy_binning.power_step, 8))

                            self.setStatusMessage("Running " + self.get_object_name() + " " + str(self.total_current_new_object) + " of " + str(self.total_new_objects))
                            self.start_button.setEnabled(False)
                            self.text_area.setEnabled(False)
                            self.Outputs.trigger.send(TriggerOut(new_object=True,
                                                                 additional_parameters={"energy_value"   : self.current_energy_value,
                                                                                        "energy_step"    : energy_binning.energy_step,
                                                                                        "power_step"     : -1 if self.current_power_step is None else self.current_power_step,
                                                                                        "current_step"   : self.total_current_new_object,
                                                                                        "total_steps"    : self.total_new_objects,
                                                                                        "seed_increment" : self.seed_increment,
                                                                                        "start_event"    : False}))
                        else:
                            self.current_energy_binning += 1

                            if self.current_energy_binning < len(self.energy_binnings):
                                energy_binning = self.energy_binnings[self.current_energy_binning]

                                self.current_new_object = 1
                                self.calculate_number_of_new_objects()
                                self.current_energy_value = round(energy_binning.energy_value, 8)
                                self.current_power_step = None if energy_binning.power_step is None else (None if self.send_power_step==0 else round(energy_binning.power_step, 8))

                                self.setStatusMessage("Running " + self.get_object_name() + " " + str(self.total_current_new_object) + " of " + str(self.total_new_objects))
                                self.start_button.setEnabled(False)
                                self.text_area.setEnabled(False)
                                self.Outputs.trigger.send(TriggerOut(new_object=True,
                                                                     additional_parameters={"energy_value"   : self.current_energy_value,
                                                                                            "energy_step"    : energy_binning.energy_step,
                                                                                            "power_step"     : -1 if self.current_power_step is None else self.current_power_step,
                                                                                            "current_step"   : self.total_current_new_object,
                                                                                            "total_steps"    : self.total_new_objects,
                                                                                            "seed_increment" : self.seed_increment,
                                                                                            "start_event"    : False}))
                            else:
                                self.reset_values()
                                self.start_button.setEnabled(True)
                                self.text_area.setEnabled(True)
                                self.setStatusMessage("")
                                self.Outputs.trigger.send(TriggerOut(new_object=False))
                    else:
                        self.reset_values()
                        self.start_button.setEnabled(True)
                        self.text_area.setEnabled(True)
                        self.setStatusMessage("")
                        self.Outputs.trigger.send(TriggerOut(new_object=False))
        else:
            if not self._suspend_loop:
                self.reset_values()
                self.start_button.setEnabled(True)
                self.text_area.setEnabled(True)

            self.Outputs.trigger.send(TriggerOut(new_object=False))
            self.setStatusMessage("")
            self._suspend_loop = False
            self._run_loop = True


add_widget_parameters_to_module(__name__)

