__author__ = 'labx'

import os
import random
import sys
import copy

import numpy
import h5py
import re
import time

from scipy import optimize
from numpy import asarray
import scipy.constants as codata


from AnyQt.QtCore import QSettings
from AnyQt.QtWidgets import QWidget, QGridLayout

from matplotlib import rcParams
from matplotlib.patches import FancyArrowPatch, ArrowStyle

from orangewidget.gui import OWComponent

from oasys2.widget import gui
from oasys2.widget.util import congruence
from oasys2.widget.gui import Styles
from oasys2.widget.util.widget_objects import TriggerIn, TriggerOut

from srxraylib.util.histograms import get_sigma, get_fwhm, get_average

from shadow4.beam.s4_beam import S4Beam

from orangecontrib.shadow4.util import materials_library as ml


class ShadowCongruence():
    @classmethod
    def check_empty_beam(cls, input_beam):
        if input_beam is None: return False
        elif not hasattr(input_beam, "rays"): return False
        elif len(input_beam.rays) == 0: return False
        else: return True

    @classmethod
    def check_empty_data(cls, input_data):
        if input_data is None:  return False
        else:                   return cls.check_empty_beam(input_data.beam)

    @classmethod
    def check_good_beam(cls, input_beam):
        return len(input_beam.rays[numpy.where(input_beam.rays[:, 9] == 1)]) > 0

    @classmethod
    def checkBraggFile(cls, file_name):
        file = open(file_name, "r")

        try:
            rows = file.readlines()

            if len(rows) < 10: raise Exception("Bragg file malformed, please check input")

            if "# Bragg version," in rows[0]:
                pass # version 2
            else:
                first_row = ShadowCongruence.__get_numbers(rows[0].strip())

                if not len(first_row) == 3: raise Exception("Bragg file malformed, please check input")

                second_row = ShadowCongruence.__get_numbers(rows[1].strip())
                if not len(second_row) == 3: raise Exception("Bragg file malformed, please check input")

                if not (rows[2].strip().startswith("(") and \
                                rows[3].strip().startswith("(") and \
                                rows[4].strip().startswith("(") and \
                                rows[5].strip().startswith("(")):
                    raise Exception("Bragg file malformed, please check input")

                seventh_row = ShadowCongruence.__get_numbers(rows[6].strip())
                if not len(seventh_row) == 3: raise Exception("Bragg file malformed, please check input")

                eighth_row = ShadowCongruence.__get_numbers(rows[7].strip())
                if not len(eighth_row) == 3: raise Exception("Bragg file malformed, please check input")

                nineth_row = ShadowCongruence.__get_numbers(rows[8].strip())
                if not len(nineth_row) == 1: raise Exception("Bragg file malformed, please check input")
        except Exception as e:
            file.close()

            raise e

    @classmethod
    def checkPreReflFile(cls, file_name):
        file = open(file_name, "r")

        try:
            rows = file.readlines()

            if len(rows) < 3: raise Exception("PreRefl file malformed, please check input")

            first_row = ShadowCongruence.__get_numbers(rows[0].strip())
            if not len(first_row) == 4: raise Exception("PreRefl file malformed, please check input")

            second_row = ShadowCongruence.__get_numbers(rows[1].strip())
            if not len(second_row) == 1: raise Exception("PreRefl file malformed, please check input")

            try:
                elements = int(second_row[0])
            except:
                raise Exception("PreRefl file malformed, please check input")

            if len(rows) != (elements*2) + 2: raise Exception("PreRefl file malformed, please check input")
        except Exception as e:
            file.close()

            raise e

    @classmethod
    def checkPreMLayerFile(cls, file_name):
        file = open(file_name, "r")

        try:
            rows = file.readlines()

            if len(rows) < 2: raise Exception("PreMLayer file malformed, please check input")

            first_row = ShadowCongruence.__get_numbers(rows[0].strip())
            if not len(first_row) == 1: raise Exception("PreMLayer file malformed, please check input")

            try:
                elements = int(first_row[0])
            except:
                raise Exception("PreRefl file malformed, please check input")

            second_row = ShadowCongruence.__get_numbers(rows[1].strip())
            if not len(second_row) == int(elements): raise Exception("PreMLayer file malformed, please check input")

            try:
                separator_row = ShadowCongruence.__get_numbers(rows[2 + elements*3].strip())
                if not len(separator_row) == 1: raise Exception("PreMLayer file malformed, please check input")
            except:
                raise Exception("PreRefl file malformed, please check input")

            next_row = ShadowCongruence.__get_numbers(rows[2 + elements*3 + 1].strip())
            if not len(next_row) == 4: raise Exception("PreMLayer file malformed, please check input")
        except Exception as e:
            file.close()

            raise e

    @classmethod
    def check2ColumnFormatFile(cls, file_name, specific_name):
        try:
            if file_name.startswith('/'):
                values = numpy.loadtxt(os.path.abspath(file_name))
            else:
                values = numpy.loadtxt(os.path.abspath(os.path.join(os.path.curdir, file_name)))
        except:
            raise Exception(specific_name + " file malformed (should be 2 or more columns of numbers, separated by spaces), please check input")

        if len(values) < 2: raise Exception(specific_name + " file malformed (should be 2 or more columns of numbers, separated by spaces), please check input")

    @classmethod
    def checkErrorProfileFile(cls, file_name):
        file = open(file_name, "r")

        try:
            rows = file.readlines()

            if len(rows) < 2: raise Exception("Surface Error file malformed, please check input")

            first_row = ShadowCongruence.__get_numbers(rows[0].strip())
            if not len(first_row) == 2: raise Exception("Surface Error file malformed, please check input")

            n_x = int(first_row[0])

            if n_x > 500:
                raise Exception("Malformed file: maximum allowed point in X direction is 500")

        except Exception as e:
            file.close()

            raise e

    @classmethod
    def __get_numbers(cls, string):
        values = string.strip().split(" ")

        numbers = []
        for value in values:
            if not value == "":
                try:
                    numbers.append(value)
                except:
                    pass

        return numbers

class ShadowStatisticData:
    intensity = 0.0
    total_number_of_rays = 0
    total_good_rays = 0
    total_lost_rays = 0

    def __init__(self, intensity = 0.0,
                 total_number_of_rays = 0,
                 total_good_rays = 0,
                 total_lost_rays = 0):
        self.intensity = intensity
        self.total_number_of_rays = total_number_of_rays
        self.total_good_rays = total_good_rays
        self.total_lost_rays = total_lost_rays

class ShadowHistoData(ShadowStatisticData):
    fwhm = 0.0
    x_fwhm_i = 0.0
    x_fwhm_f = 0.0
    y_fwhm = 0.0

    def __init__(self, intensity = 0.0,
                 total_number_of_rays = 0,
                 total_good_rays = 0,
                 total_lost_rays = 0,
                 fwhm = 0.0,
                 x_fwhm_i = 0.0,
                 x_fwhm_f = 0.0,
                 y_fwhm = 0.0):
        super().__init__(intensity, total_number_of_rays, total_good_rays, total_lost_rays)
        self.fwhm = fwhm
        self.x_fwhm_i = x_fwhm_i
        self.x_fwhm_f = x_fwhm_f
        self.y_fwhm = y_fwhm

class ShadowPlotData(ShadowStatisticData):
    fwhm_h = 0.0
    fwhm_v = 0.0

    def __init__(self, intensity = 0.0,
                 total_number_of_rays = 0,
                 total_good_rays = 0,
                 total_lost_rays = 0,
                 fwhm_h = 0.0,
                 fwhm_v = 0.0):
        super().__init__(intensity, total_number_of_rays, total_good_rays, total_lost_rays)
        self.fwhm_h = fwhm_h
        self.fwhm_v = fwhm_v

try:
    class ShadowPlot:

        _is_conversione_active = True

        #########################################################################################
        #
        # FOR TEMPORARY USE: FIX AN ERROR IN PYMCA.PLOT.IMAGEWIEW
        #
        #########################################################################################


        @classmethod
        def set_conversion_active(cls, is_active=True):
            ShadowPlot._is_conversione_active = is_active

        @classmethod
        def get_conversion_active(cls):
            return ShadowPlot._is_conversione_active

        """Sample code to add 2D dataset saving as text to ImageView."""


        #########################################################################################
        #
        # WIDGET FOR DETAILED PLOT
        #
        #########################################################################################

        class InfoBoxWidget(QWidget, OWComponent):
            intensity_field = ""
            flux_field = ""
            total_rays_field = ""
            total_good_rays_field = ""
            total_lost_rays_field = ""
            fwhm_h_field = ""
            fwhm_v_field = ""
            sigma_h_field = ""
            sigma_v_field = ""
            centroid_h_field = ""
            centroid_v_field = ""

            def __init__(self, x_scale_factor = 1.0, y_scale_factor = 1.0, is_2d=True):
                super(ShadowPlot.InfoBoxWidget, self).__init__()

                info_box_inner=gui.widgetBox(self, "Info")
                info_box_inner.setFixedHeight(int(518*y_scale_factor))
                info_box_inner.setFixedWidth(int(230*x_scale_factor))

                self.flux_box = gui.widgetBox(info_box_inner, "", addSpace=False, orientation="horizontal")
                self.flux     = gui.lineEdit(self.flux_box, self, "flux_field", "\u03a6 [ph/s/0.1%BW]", tooltip="Flux", labelWidth=115, valueType=str, orientation="horizontal")
                self.flux_box.setVisible(False)

                self.intensity = gui.lineEdit(info_box_inner, self, "intensity_field", "Intensity", tooltip="Intensity", labelWidth=115, valueType=str, orientation="horizontal")
                self.total_rays = gui.lineEdit(info_box_inner, self, "total_rays_field", "Total Rays", tooltip="Total Rays", labelWidth=115, valueType=str, orientation="horizontal")
                self.total_good_rays = gui.lineEdit(info_box_inner, self, "total_good_rays_field", "Total Good Rays", tooltip="Total Good Rays", labelWidth=115, valueType=str, orientation="horizontal")
                self.total_lost_rays = gui.lineEdit(info_box_inner, self, "total_lost_rays_field", "Total Lost Rays", tooltip="Total Lost Rays", labelWidth=115, valueType=str, orientation="horizontal")

                label_h_style = "color: blue"
                label_v_style = "color: red"

                label_box_1 = gui.widgetBox(info_box_inner, "", addSpace=False, orientation="horizontal")
                self.label_h = gui.widgetLabel(label_box_1, "FWHM", labelWidth=115)
                self.label_h.setStyleSheet(label_h_style)
                label_box_1.layout().addWidget(self.label_h)
                self.fwhm_h = gui.lineEdit(label_box_1, self, "fwhm_h_field", "", tooltip="FWHM", labelWidth=115, valueType=str, orientation="horizontal")

                if is_2d:
                    label_box_2 = gui.widgetBox(info_box_inner, "", addSpace=False, orientation="horizontal")
                    self.label_v = gui.widgetLabel(label_box_2, "FWHM", labelWidth=115)
                    self.label_v.setStyleSheet(label_v_style)
                    label_box_2.layout().addWidget(self.label_v)
                    self.fwhm_v = gui.lineEdit(label_box_2, self, "fwhm_v_field", "", tooltip="FWHM", labelWidth=115, valueType=str, orientation="horizontal")

                label_box_1 = gui.widgetBox(info_box_inner, "", addSpace=False, orientation="horizontal")
                self.label_s_h = gui.widgetLabel(label_box_1, "\u03c3", labelWidth=115)
                self.label_s_h.setStyleSheet(label_h_style)
                label_box_1.layout().addWidget(self.label_s_h)
                self.sigma_h = gui.lineEdit(label_box_1, self, "sigma_h_field", "", tooltip="Sigma", labelWidth=115, valueType=str, orientation="horizontal")

                if is_2d:
                    label_box_2 = gui.widgetBox(info_box_inner, "", addSpace=False, orientation="horizontal")
                    self.label_s_v = gui.widgetLabel(label_box_2, "\u03c3", labelWidth=115)
                    self.label_s_v.setStyleSheet(label_v_style)
                    label_box_2.layout().addWidget(self.label_s_v)
                    self.sigma_v = gui.lineEdit(label_box_2, self, "sigma_v_field", "", tooltip="Sigma", labelWidth=115, valueType=str, orientation="horizontal")

                label_box_1 = gui.widgetBox(info_box_inner, "", addSpace=False, orientation="horizontal")
                self.label_c_h = gui.widgetLabel(label_box_1, "centroid", labelWidth=115)
                self.label_c_h.setStyleSheet(label_h_style)
                label_box_1.layout().addWidget(self.label_c_h)
                self.centroid_h = gui.lineEdit(label_box_1, self, "centroid_h_field", "", tooltip="Centroid", labelWidth=115, valueType=str, orientation="horizontal")

                if is_2d:
                    label_box_2 = gui.widgetBox(info_box_inner, "", addSpace=False, orientation="horizontal")
                    self.label_c_v = gui.widgetLabel(label_box_2, "centroid", labelWidth=115)
                    self.label_c_v.setStyleSheet(label_v_style)
                    label_box_2.layout().addWidget(self.label_c_v)
                    self.centroid_v = gui.lineEdit(label_box_2, self, "centroid_v_field", "", tooltip="Sigma", labelWidth=115, valueType=str, orientation="horizontal")

                read_only_style = Styles.line_edit_read_only

                self.intensity.setReadOnly(True)
                self.intensity.setStyleSheet(read_only_style)
                self.flux.setReadOnly(True)
                self.flux.setStyleSheet(read_only_style)
                self.total_rays.setReadOnly(True)
                self.total_rays.setStyleSheet(read_only_style)
                self.total_good_rays.setReadOnly(True)
                self.total_good_rays.setStyleSheet(read_only_style)
                self.total_lost_rays.setReadOnly(True)
                self.total_lost_rays.setStyleSheet(read_only_style)
                self.fwhm_h.setReadOnly(True)
                self.fwhm_h.setStyleSheet(read_only_style)
                self.sigma_h.setReadOnly(True)
                self.sigma_h.setStyleSheet(read_only_style)
                self.centroid_h.setReadOnly(True)
                self.centroid_h.setStyleSheet(read_only_style)

                if is_2d:
                    self.fwhm_v.setReadOnly(True)
                    self.fwhm_v.setStyleSheet(read_only_style)
                    self.sigma_v.setReadOnly(True)
                    self.sigma_v.setStyleSheet(read_only_style)
                    self.centroid_v.setReadOnly(True)
                    self.centroid_v.setStyleSheet(read_only_style)

            def set_flux(self, flux=None):
                if flux is None:
                    self.flux.setText("0.0")
                    self.flux_box.setVisible(False)
                else:
                    self.flux.setText('%.3E' % flux)
                    self.flux_box.setVisible(True)

            def clear(self):
                self.intensity.setText("0.0")
                self.flux.setText("0.0")
                self.flux_box.setVisible(False)
                self.total_rays.setText("0")
                self.total_good_rays.setText("0")
                self.total_lost_rays.setText("0")
                self.fwhm_h.setText("0.0000")
                if hasattr(self, "fwhm_v"):  self.fwhm_v.setText("0.0000")
                self.sigma_h.setText("0.0000")
                if hasattr(self, "sigma_v"):  self.sigma_v.setText("0.0000")
                self.centroid_h.setText("0.0000")
                if hasattr(self, "centroid_v"):  self.centroid_v.setText("0.0000")
                self.boundary_h.setText("")
                if hasattr(self, "boundary_v"):  self.centroid_v.setText("")

        class DetailedHistoWidget(QWidget, OWComponent):

            def __init__(self, x_scale_factor = 1.0, y_scale_factor = 1.0):
                super(ShadowPlot.DetailedHistoWidget, self).__init__()

                self.plot_canvas = gui.plotWindow(roi=False, control=False, position=True, logScale=True, fit=True)
                self.plot_canvas.setDefaultPlotLines(True)
                self.plot_canvas.setActiveCurveStyle(color='blue')
                self.plot_canvas.setMinimumWidth(int(590 * x_scale_factor))
                self.plot_canvas.setMaximumWidth(int(590 * x_scale_factor))

                self.info_box = ShadowPlot.InfoBoxWidget(x_scale_factor, y_scale_factor, is_2d=False)

                layout = QGridLayout()

                layout.addWidget(   self.info_box, 0, 1, 1, 1)
                layout.addWidget(self.plot_canvas, 0, 0, 1, 1)

                layout.setColumnMinimumWidth(0, int(600 * x_scale_factor))
                layout.setColumnMinimumWidth(1, int(230 * x_scale_factor))

                self.setLayout(layout)

            def plot_histo(self, beam, col, nolost, xrange, ref, title, xtitle, ytitle, nbins = 100, xum="", ticket_to_add=None, flux=None):
                ticket = beam.histo1(col, nbins=nbins, xrange=xrange, nolost=nolost, ref=ref)
                if ref in [24, 25]: ticket['intensity'] = beam.get_column(ref, nolost=nolost).sum()

                # TODO: check congruence between tickets
                if not ticket_to_add is None:
                    last_ticket = copy.deepcopy(ticket)

                    if ticket['histogram'].size == ticket_to_add['histogram'].size:
                        ticket['histogram'] += ticket_to_add['histogram']
                        ticket['histogram_path'] += ticket_to_add['histogram_path']

                        ticket['intensity'] += ticket_to_add['intensity']
                        ticket['nrays'] += ticket_to_add['nrays']
                        ticket['good_rays'] += ticket_to_add['good_rays']

                ticket['fwhm'], ticket['fwhm_quote'], ticket['fwhm_coordinates'] = get_fwhm(ticket['histogram'], ticket['bin_center'], ret0=None)
                ticket['sigma']    = get_sigma(ticket['histogram'], ticket['bin_center'], ret0=numpy.nan)
                ticket['centroid'] = get_average(ticket['histogram'], ticket['bin_center'], ret0=numpy.nan)

                factor = ShadowPlot.get_factor(col)

                if ref != 0 and not ytitle is None:  ytitle = ytitle + ' weighted by ' + ShadowPlot.get_shadow_label(ref)

                histogram = ticket['histogram_path']
                bins      = ticket['bin_path']*factor

                if histogram.size > 1: self.plot_canvas.addCurve(bins, histogram, title, symbol='', color='blue', replace=True) #'+', '^', ','
                else:                  self.plot_canvas.clear()

                if not xtitle is None: self.plot_canvas.setGraphXLabel(xtitle)
                if not ytitle is None: self.plot_canvas.setGraphYLabel(ytitle)
                if not title is None: self.plot_canvas.setGraphTitle(title)
                self.plot_canvas.setInteractiveMode(mode='zoom')

                if ticket['fwhm'] is None: ticket['fwhm'] = 0.0
                if not ticket_to_add is None:
                    if last_ticket['fwhm'] is None: last_ticket['fwhm'] = 0.0

                n_patches = len(self.plot_canvas._backend.ax.patches)
                if (n_patches > 0): self.plot_canvas._backend.ax.patches[n_patches-1].remove()

                if not ticket['fwhm'] == 0.0:
                    x_fwhm_i, x_fwhm_f = ticket['fwhm_coordinates']
                    x_fwhm_i, x_fwhm_f = x_fwhm_i*factor, x_fwhm_f*factor
                    y_fwhm             = ticket['fwhm_quote']

                    self.plot_canvas._backend.ax.add_patch(FancyArrowPatch([x_fwhm_i, y_fwhm],
                                                                           [x_fwhm_f, y_fwhm],
                                                                           arrowstyle=ArrowStyle.CurveAB(head_width=2, head_length=4),
                                                                           color='b',
                                                                           linewidth=1.5))

                if histogram.size > 1:
                    if min(histogram) < 0: self.plot_canvas.setGraphYLimits(min(histogram), max(histogram))
                    else: self.plot_canvas.setGraphYLimits(0, max(histogram))

                self.plot_canvas.replot()

                self.info_box.intensity.setText("{:4.3f}".format(ticket['intensity']))
                self.info_box.set_flux(flux)
                self.info_box.total_rays.setText(str(ticket['nrays']))
                self.info_box.total_good_rays.setText(str(ticket['good_rays']))
                self.info_box.total_lost_rays.setText(str(ticket['nrays']-ticket['good_rays']))
                self.info_box.fwhm_h.setText("{:5.4f}".format(ticket['fwhm']*factor))
                self.info_box.label_h.setText("FWHM " + xum)
                self.info_box.sigma_h.setText("{:5.4f}".format(ticket['sigma']*factor))
                self.info_box.label_s_h.setText("\u03c3 " + xum)
                self.info_box.centroid_h.setText("{:5.4f}".format(ticket['centroid']*factor))
                self.info_box.label_c_h.setText("centroid " + xum)

                if not ticket_to_add is None:
                    return ticket, last_ticket
                else:
                    return ticket, None

            def clear(self):
                self.plot_canvas.clear()
                self.info_box.clear()

        class DetailedPlotWidget(QWidget, OWComponent):
            def __init__(self, x_scale_factor = 1.0, y_scale_factor = 1.0):
                super(ShadowPlot.DetailedPlotWidget, self).__init__()

                self.x_scale_factor = x_scale_factor
                self.y_scale_factor = y_scale_factor

                self.plot_canvas = gui.imageWiew(parent=self)

                self.plot_canvas.setColormap({"name":"temperature", "normalization":"linear", "autoscale":True, "vmin":0, "vmax":0, "colors":256})
                self.plot_canvas.setMinimumWidth(int(590 * x_scale_factor))
                self.plot_canvas.setMaximumWidth(int(590 * y_scale_factor))

                self.info_box = ShadowPlot.InfoBoxWidget(x_scale_factor, y_scale_factor)

                layout = QGridLayout()

                layout.addWidget(self.info_box,    0, 1, 1, 1)
                layout.addWidget(self.plot_canvas, 0, 0, 1, 1)

                layout.setColumnMinimumWidth(0, int(600 * x_scale_factor))
                layout.setColumnMinimumWidth(1, int(230 * x_scale_factor))

                self.setLayout(layout)

            def plot_xy(self, beam, var_x, var_y, title, xtitle, ytitle, xrange=None, yrange=None, nolost=1, nbins=100, nbins_h=None, nbins_v=None, xum="", yum="", ref=23, is_footprint=False, ticket_to_add=None, flux=None, flip_h=False, flip_v=False):
                rcParams['axes.formatter.useoffset']='False'

                if nbins_h is None: nbins_h = nbins
                if nbins_v is None: nbins_v = nbins

                ticket = beam.histo2(var_x, var_y, nbins=nbins, nbins_h=nbins_h, nbins_v=nbins_v, xrange=xrange, yrange=yrange, nolost=nolost, ref=ref)
                if ref in [24, 25]: ticket['intensity'] = beam.get_column(ref, nolost=nolost).sum()

                # TODO: check congruence between tickets
                if not ticket_to_add is None:
                    last_ticket = copy.deepcopy(ticket)

                    if ticket['histogram'].shape == ticket_to_add['histogram'].shape:
                        ticket['histogram'] += ticket_to_add['histogram']
                        ticket['histogram_h'] += ticket_to_add['histogram_h']
                        ticket['histogram_v'] += ticket_to_add['histogram_v']

                        ticket['intensity'] += ticket_to_add['intensity']
                        ticket['nrays'] += ticket_to_add['nrays']
                        ticket['good_rays'] += ticket_to_add['good_rays']

                ticket['fwhm_h'], ticket['fwhm_quote_h'], ticket['fwhm_coordinates_h'] = get_fwhm(ticket['histogram_h'], ticket['bin_h_center'], ret0=None)
                ticket['fwhm_v'], ticket['fwhm_quote_v'], ticket['fwhm_coordinates_v'] = get_fwhm(ticket['histogram_v'], ticket['bin_v_center'], ret0=None)
                ticket['sigma_h']    = get_sigma(ticket['histogram_h'], ticket['bin_h_center'], ret0=numpy.nan)
                ticket['sigma_v']    = get_sigma(ticket['histogram_v'], ticket['bin_v_center'], ret0=numpy.nan)
                ticket['centroid_h'] = get_average(ticket['histogram_h'], ticket['bin_h_center'], ret0=numpy.nan)
                ticket['centroid_v'] = get_average(ticket['histogram_v'], ticket['bin_v_center'], ret0=numpy.nan)

                factor1 = 1.0 if is_footprint else ShadowPlot.get_factor(var_x)
                factor2 = 1.0 if is_footprint else ShadowPlot.get_factor(var_y)

                xx = ticket['bin_h_edges']
                yy = ticket['bin_v_edges']

                if ticket['histogram'].size > 1:
                    xmin, xmax = xx.min(), xx.max()
                    ymin, ymax = yy.min(), yy.max()

                    origin = (xmin*factor1, ymin*factor2)
                    scale = (abs((xmax-xmin)/nbins_h)*factor1, abs((ymax-ymin)/nbins_v)*factor2)

                    self.plot_canvas.setColormap({"name":QSettings().value("output/shadow-default-colormap", "temperature", str),
                                                  "normalization":"linear",
                                                  "autoscale":True,
                                                  "vmin":0,
                                                  "vmax":0,
                                                  "colors":256})

                    # PyMCA inverts axis!!!! histogram must be calculated reversed

                    data_2D = ticket['histogram'].T
                    if flip_h: data_2D = numpy.flip(data_2D, axis=0)
                    if flip_v: data_2D = numpy.flip(data_2D, axis=1)

                    self.plot_canvas.setImage(data_2D, origin=origin, scale=scale)
                else:
                    self.plot_canvas.clear()

                if xtitle is None: xtitle=ShadowPlot.get_shadow_label(var_x)
                if ytitle is None: ytitle=ShadowPlot.get_shadow_label(var_y)

                self.plot_canvas.setGraphXLabel(xtitle)
                self.plot_canvas.setGraphYLabel(ytitle)
                self.plot_canvas.setGraphTitle(title)

                self.plot_canvas._histoHPlot.setGraphYLabel('A.U.')

                self.plot_canvas._histoHPlot._backend.ax.xaxis.get_label().set_color('white')
                self.plot_canvas._histoHPlot._backend.ax.xaxis.get_label().set_fontsize(1)
                for label in self.plot_canvas._histoHPlot._backend.ax.xaxis.get_ticklabels():
                    label.set_color('white')
                    label.set_fontsize(1)

                self.plot_canvas._histoVPlot.setGraphXLabel('A.U.')

                self.plot_canvas._histoVPlot._backend.ax.yaxis.get_label().set_color('white')
                self.plot_canvas._histoVPlot._backend.ax.yaxis.get_label().set_fontsize(1)
                for label in self.plot_canvas._histoVPlot._backend.ax.yaxis.get_ticklabels():
                    label.set_color('white')
                    label.set_fontsize(1)

                if ticket['fwhm_h'] is None: ticket['fwhm_h'] = 0.0
                if ticket['fwhm_v'] is None: ticket['fwhm_v'] = 0.0
                if not ticket_to_add is None:
                    if last_ticket['fwhm_h'] is None: last_ticket['fwhm_h'] = 0.0
                    if last_ticket['fwhm_v'] is None: last_ticket['fwhm_v'] = 0.0

                n_patches = len(self.plot_canvas._histoHPlot._backend.ax.patches)
                if (n_patches > 0): self.plot_canvas._histoHPlot._backend.ax.patches[n_patches-1].remove()

                if not ticket['fwhm_h'] == 0.0:
                    x_fwhm_i, x_fwhm_f = ticket['fwhm_coordinates_h']
                    x_fwhm_i, x_fwhm_f = x_fwhm_i*factor1, x_fwhm_f*factor1
                    y_fwhm = ticket['fwhm_quote_h']

                    self.plot_canvas._histoHPlot._backend.ax.add_patch(FancyArrowPatch([x_fwhm_i, y_fwhm],
                                                                         [x_fwhm_f, y_fwhm],
                                                                         arrowstyle=ArrowStyle.CurveAB(head_width=2, head_length=4),
                                                                         color='b',
                                                                         linewidth=1.5))

                n_patches = len(self.plot_canvas._histoVPlot._backend.ax.patches)
                if (n_patches > 0): self.plot_canvas._histoVPlot._backend.ax.patches[n_patches-1].remove()

                if not ticket['fwhm_v'] == 0.0:
                    y_fwhm_i, y_fwhm_f = ticket['fwhm_coordinates_v']
                    y_fwhm_i, y_fwhm_f = y_fwhm_i*factor2, y_fwhm_f*factor2
                    x_fwhm = ticket['fwhm_quote_v']

                    self.plot_canvas._histoVPlot._backend.ax.add_patch(FancyArrowPatch([x_fwhm, y_fwhm_i],
                                                                         [x_fwhm, y_fwhm_f],
                                                                         arrowstyle=ArrowStyle.CurveAB(head_width=2, head_length=4),
                                                                         color='r',
                                                                         linewidth=1.5))

                self.plot_canvas._histoHPlot.replot()
                self.plot_canvas._histoVPlot.replot()
                self.plot_canvas.replot()

                self.info_box.intensity.setText("{:4.3f}".format(ticket['intensity']))
                self.info_box.set_flux(flux)
                self.info_box.total_rays.setText(str(ticket['nrays']))
                self.info_box.total_good_rays.setText(str(ticket['good_rays']))
                self.info_box.total_lost_rays.setText(str(ticket['nrays']-ticket['good_rays']))
                self.info_box.fwhm_h.setText("{:5.4f}".format(ticket['fwhm_h'] * factor1))
                self.info_box.fwhm_v.setText("{:5.4f}".format(ticket['fwhm_v'] * factor2))
                self.info_box.label_h.setText("FWHM " + xum)
                self.info_box.label_v.setText("FWHM " + yum)
                self.info_box.sigma_h.setText("{:5.4f}".format(ticket['sigma_h'] * factor1))
                self.info_box.sigma_v.setText("{:5.4f}".format(ticket['sigma_v'] * factor2))
                self.info_box.label_s_h.setText("\u03c3 " + xum)
                self.info_box.label_s_v.setText("\u03c3 " + yum)
                self.info_box.centroid_h.setText("{:5.4f}".format(ticket['centroid_h'] * factor1))
                self.info_box.centroid_v.setText("{:5.4f}".format(ticket['centroid_v'] * factor2))
                self.info_box.label_c_h.setText("centroid " + xum)
                self.info_box.label_c_v.setText("centroid " + yum)

                if not ticket_to_add is None:
                    return ticket, last_ticket
                else:
                    return ticket, None

            def clear(self):
                self.plot_canvas.clear()

                self.plot_canvas._histoHPlot.clear()
                self.plot_canvas._histoVPlot.clear()

                self.plot_canvas._histoHPlot._backend.ax.xaxis.get_label().set_color('white')
                self.plot_canvas._histoHPlot._backend.ax.xaxis.get_label().set_fontsize(1)
                for label in self.plot_canvas._histoHPlot._backend.ax.xaxis.get_ticklabels():
                    label.set_color('white')
                    label.set_fontsize(1)

                self.plot_canvas._histoVPlot._backend.ax.yaxis.get_label().set_color('white')
                self.plot_canvas._histoVPlot._backend.ax.yaxis.get_label().set_fontsize(1)
                for label in self.plot_canvas._histoVPlot._backend.ax.yaxis.get_ticklabels():
                    label.set_color('white')
                    label.set_fontsize(1)

                self.plot_canvas._histoHPlot.setGraphYLabel('A.U.')
                self.plot_canvas._histoVPlot.setGraphXLabel('A.U.')

                self.plot_canvas._histoHPlot.replot()
                self.plot_canvas._histoVPlot.replot()

                self.info_box.clear()

        #########################################################################################

        @classmethod
        def plotxy_preview(cls, plot_window, beam, var_x, var_y, nolost=0, title='PLOTXY', xtitle=None, ytitle=None, is_footprint=False):
            rcParams['axes.formatter.useoffset']='False'

            col1 = beam.get_column(var_x, nolost=nolost)
            col2 = beam.get_column(var_y, nolost=nolost)

            factor1 = 1.0 if is_footprint else ShadowPlot.get_factor(var_x)
            factor2 = 1.0 if is_footprint else ShadowPlot.get_factor(var_y)

            if xtitle is None: xtitle=ShadowPlot.get_shadow_label(var_x)
            if ytitle is None: ytitle=ShadowPlot.get_shadow_label(var_y)

            plot_window.addCurve(col1*factor1, col2*factor2, title, symbol='.', color='blue', replace=True) #'+', '^', ','

            if not xtitle is None: plot_window.setGraphXLabel(xtitle)
            if not ytitle is None: plot_window.setGraphYLabel(ytitle)
            if not title is None: plot_window.setGraphTitle(title)
            plot_window.setInteractiveMode(mode='zoom')

        @classmethod
        def plot_histo_preview(cls, plot_window, beam, col, nolost, ref, title, xtitle, ytitle, conv=1.0):
            rcParams['axes.formatter.useoffset']='False'

            factor = ShadowPlot.get_factor(col)

            ticket = beam.histo1(col, nbins=100, xrange=None, nolost=nolost, ref=ref)

            if ref != 0 and not ytitle is None:  ytitle = ytitle + ' weighted by ' + ShadowPlot.get_shadow_label(ref)

            histogram = ticket['histogram_path']
            bins = ticket['bin_path']*factor

            plot_window.addCurve(bins, histogram, title, symbol='', color='blue', replace=True) #'+', '^', ','
            if not xtitle is None: plot_window.setGraphXLabel(xtitle)
            if not ytitle is None: plot_window.setGraphYLabel(ytitle)
            if not title is None: plot_window.setGraphTitle(title)
            plot_window.setInteractiveMode(mode='zoom')
            if min(histogram) < 0:
                plot_window.setGraphYLimits(min(histogram), max(histogram))
            else:
                plot_window.setGraphYLimits(0, max(histogram))
            plot_window.replot()

        @classmethod
        def get_factor(cls, var):
            if ShadowPlot.get_conversion_active() and var in [1, 2, 3, 4, 5, 6]: return 1e6 # m to micron
            else:                                                                return 1.0

        @classmethod
        def get_shadow_label(cls, var):
            return S4Beam.column_names_formatted()[var-1]
            # return (stp.getLabel(var-1))[0]


        #########################################################################################
        #
        # SAVE/LOAD FILES in HDF5 Format
        #
        #########################################################################################


        class PlotXYHdf5File(h5py.File):
            def __init__(self, file_name, mode="w"):
                try:
                    super(ShadowPlot.PlotXYHdf5File, self).__init__(name=file_name, mode=mode)
                except OSError as e:
                    if "already open" in str(e) and mode=="w":
                        super(ShadowPlot.PlotXYHdf5File, self).__init__(name=file_name, mode="a")
                        self.close()
                        super(ShadowPlot.PlotXYHdf5File, self).__init__(name=file_name, mode="w")

                if mode != "r":
                    self.coordinates = self.create_group("coordinates")
                    self.plots = self.create_group("xy_plots")
                    self.additional_data = self.create_group("additional_data")
                    self.last_plot = self.plots.create_group("last_plot")

                    self.has_last_plot = False
                    self.has_coordinate = False

                    self.attrs["default"]          = "coordinates/X"
                    self.attrs["file_name"]        = file_name
                    self.attrs["file_time"]        = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())
                    self.attrs["creator"]          = "PlotXYHdf5File.__init__"
                    self.attrs["code"]             = "ShadowOui"
                    self.attrs["HDF5_Version"]     = h5py.version.hdf5_version
                    self.attrs["h5py_version"]     = h5py.version.version

            def get_last_plot(self, dataset_name="intensity"):
                return self.get_plot_xy(dataset_name=dataset_name)

            def get_coordinates(self):
                bin_h_center = self["coordinates/X"][()]
                h_label = self["coordinates"].attrs["x_label"]
                bin_v_center = self["coordinates/Y"][()]
                v_label = self["coordinates"].attrs["y_label"]

                return bin_h_center, bin_v_center, h_label, v_label

            def get_plot_xy(self, plot_name="last_plot", dataset_name="intensity"):
                histogram = self["/xy_plots/" + plot_name + "/" + dataset_name][()]
                histogram_h = self["/xy_plots/" + plot_name + "/histogram_h"][()]
                histogram_v = self["/xy_plots/" + plot_name + "/histogram_v"][()]

                return histogram, histogram_h, histogram_v, self["/xy_plots/" + plot_name].attrs

            def write_coordinates(self, ticket):
                if not self.has_coordinate:
                    self.x = self.coordinates.create_dataset("X", data=ticket["bin_h_center"])
                    self.y = self.coordinates.create_dataset("Y", data=ticket["bin_v_center"])
                    self.has_coordinate = True
                else:
                    self.x[...] = ticket["bin_h_center"]
                    self.y[...] = ticket["bin_v_center"]

                try:
                    self.coordinates.attrs["x_label"] = ShadowPlot.get_shadow_label(ticket["col_h"])
                    self.coordinates.attrs["y_label"] = ShadowPlot.get_shadow_label(ticket["col_v"])
                except:
                    self.coordinates.attrs["x_label"] = ticket["h_label"]
                    self.coordinates.attrs["y_label"] = ticket["v_label"]


            def add_plot_xy(self, ticket, plot_name="last_plot", dataset_name="intensity", attributes={}):
                    if plot_name is None or plot_name.strip() == "" or plot_name.strip() == "last_plot":
                        if not self.has_last_plot:
                            self.lp_histogram   = self.last_plot.create_dataset(dataset_name, data=ticket['histogram'])
                            self.lp_histogram_h = self.last_plot.create_dataset("histogram_h", data=ticket['histogram_h'])
                            self.lp_histogram_v = self.last_plot.create_dataset("histogram_v", data=ticket['histogram_v'])
                            self.has_last_plot = True
                        else:
                            if self.lp_histogram.name != "/xy_plots/last_plot/" + dataset_name:
                                self.last_plot.move(self.lp_histogram.name, "/xy_plots/last_plot/" + dataset_name)

                            self.lp_histogram[...]   = ticket['histogram']
                            self.lp_histogram_h[...] = ticket['histogram_h']
                            self.lp_histogram_v[...] = ticket['histogram_v']

                        self.last_plot.attrs["intensity"] = ticket["intensity"]
                        self.last_plot.attrs["total_rays"] = ticket["nrays"]
                        self.last_plot.attrs["good_rays"] = ticket["good_rays"]
                        self.last_plot.attrs["lost_rays"] = ticket["nrays"]-ticket["good_rays"]

                        if not attributes is None:
                            for key in attributes.keys():
                                self.last_plot.attrs[key] = attributes[key]

                    else:
                        plot = self.plots.create_group(plot_name)

                        plot.create_dataset(dataset_name, data=ticket['histogram'])
                        plot.create_dataset("histogram_h", data=ticket['histogram_h'])
                        plot.create_dataset("histogram_v", data=ticket['histogram_v'])
                        plot.attrs["intensity"] = ticket["intensity"]
                        plot.attrs["total_rays"] = ticket["nrays"]
                        plot.attrs["good_rays"] = ticket["good_rays"]
                        plot.attrs["lost_rays"] = ticket["nrays"]-ticket["good_rays"]

                        if not attributes is None:
                            for key in attributes.keys():
                                plot.attrs[key] = attributes[key]

            def add_attribute(self, attribute_name, attribute_value, dataset_name=None):
                if not dataset_name is None:
                    self[dataset_name].attrs[attribute_name] = attribute_value
                else:
                    self.attrs[attribute_name] = attribute_value

            def get_attribute(self,  attribute_name, dataset_name=None):
                if not dataset_name is None:
                    return self[dataset_name].attrs[attribute_name]
                else:
                    return self.attrs[attribute_name]

        class HistogramHdf5File(h5py.File):
            def __init__(self, file_name, mode="w"):
                super(ShadowPlot.HistogramHdf5File, self).__init__(name=file_name, mode=mode)

                self.coordinates = self.create_group("coordinates")
                self.plots = self.create_group("histogram_plots")
                self.last_plot = self.plots.create_group("last_plot")
                self.has_last_plot = False
                self.has_coordinate = False

                self.attrs["default"]          = "coordinates/X"
                self.attrs["file_name"]        = file_name
                self.attrs["file_time"]        = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())
                self.attrs["creator"]          = "PlotXYHdf5File.__init__"
                self.attrs["code"]             = "ShadowOui"
                self.attrs["HDF5_Version"]     = h5py.version.hdf5_version
                self.attrs["h5py_version"]     = h5py.version.version

            def write_coordinates(self, ticket):
                if not self.has_coordinate:
                    self.x = self.coordinates.create_dataset("X", data=ticket["bin_center"])
                    self.has_coordinate = True
                else:
                    self.x[...] = ticket["bin_center"]

                self.coordinates.attrs["x_label"] = ShadowPlot.get_shadow_label(ticket["col"])

            def add_histogram(self, ticket, plot_name="last_plot", dataset_name="intensity", attributes={}):
                    if plot_name is None or plot_name.strip() == "" or plot_name.strip() == "last_plot":
                        if not self.has_last_plot:
                            self.lp_histogram  = self.last_plot.create_dataset(dataset_name, data=ticket['histogram'])
                            self.has_last_plot = True
                        else:
                            if self.lp_histogram.name != "/histogram_plots/last_plot/" + dataset_name:
                                self.last_plot.move(self.lp_histogram.name, "/histogram_plots/last_plot/" + dataset_name)

                            self.lp_histogram[...] = ticket['histogram']

                        self.last_plot.attrs["intensity"] = ticket["intensity"]
                        self.last_plot.attrs["total_rays"] = ticket["nrays"]
                        self.last_plot.attrs["good_rays"] = ticket["good_rays"]
                        self.last_plot.attrs["lost_rays"] = ticket["nrays"]-ticket["good_rays"]

                        if not attributes is None:
                            for key in attributes.keys():
                                self.last_plot.attrs[key] = attributes[key]
                    else:
                        plot = self.plots.create_group(plot_name)

                        plot.create_dataset(dataset_name, data=ticket['histogram'])
                        plot.attrs["intensity"]  = ticket["intensity"]
                        plot.attrs["total_rays"] = ticket["nrays"]
                        plot.attrs["good_rays"]  = ticket["good_rays"]
                        plot.attrs["lost_rays"]  = ticket["nrays"]-ticket["good_rays"]

                        if not attributes is None:
                            for key in attributes.keys():
                                plot.attrs[key] = attributes[key]
except:
    pass

class ShadowPreProcessor:

    @classmethod
    def read_surface_error_file(cls, filename):
        file = open(congruence.checkFile(filename), "r")

        rows = file.readlines()

        dimensions = rows[0].split()
        n_x = int(dimensions[0])
        n_y = int(dimensions[1])

        if n_x > 500:
            raise Exception("Malformed file: maximum allowed point in X direction is 500")

        x_coords = numpy.zeros(0)
        y_coords = numpy.zeros(0)
        z_values = numpy.zeros((n_x, n_y))


        index = 1
        dim_y_row = len(rows[index].split())
        is_ycoord = True
        first_x_row_index = 0

        while(is_ycoord):
            y_values = rows[index].split()

            if len(y_values) == dim_y_row:
                for y_value in y_values:
                    y_coords = numpy.append(y_coords, float(y_value))
            else:
                first_x_row_index = index
                is_ycoord = False

            index +=1

        first_x_row = rows[first_x_row_index].split()

        if len(first_x_row) == 2:
            x_index = 0
            z_index = 0

            for index in range(first_x_row_index, len(rows)):
                if z_index == 0:
                    values = rows[index].split()
                    x_coords = numpy.append(x_coords, float(values[0]))
                    z_value = float(values[1])
                else:
                    z_value = float(rows[index])

                z_values[x_index, z_index] = z_value
                z_index += 1

                if z_index == n_y:
                    x_index += 1
                    z_index = 0
        else:
            x_rows = []

            for index in range(2, len(rows)):

                x_row = rows[index].split("\t")

                if len(x_row) != 1 + n_y:
                    x_row = rows[index].split()

                if len(x_row) != 1 + n_y:
                    raise Exception("Malformed file: check format")

                x_rows.append(x_row)

            for x_index in range(0, len(x_rows)):
                x_coords = numpy.append(x_coords, float(x_rows[x_index][0]))

                for z_index in range(0, len(x_rows[x_index]) - 1):
                    z_value = float(x_rows[x_index][z_index + 1])

                    z_values[x_index, z_index] = z_value

        return x_coords, y_coords, z_values

class ShadowMath:

    @classmethod
    def gaussian_fit(cls, data_x, data_y):
        x = asarray(data_x)
        y = asarray(data_y)
        y_norm = y/sum(y)

        mean = sum(x*y_norm)
        sigma = numpy.sqrt(sum(y_norm*(x-mean)**2)/len(x))
        amplitude = max(y)

        parameters, covariance_matrix = optimize.curve_fit(ShadowMath.gaussian_function, x, y, p0 = [amplitude, mean, sigma])
        parameters.resize(4)
        parameters[3] = 2.355*parameters[2]# FWHM

        return parameters, covariance_matrix

    @classmethod
    def gaussian_function(cls, x, A, x0, sigma):
        return A*numpy.exp(-(x-x0)**2/(2*sigma**2))

    @classmethod
    def pseudovoigt_fit(cls, data_x, data_y):
        x = asarray(data_x)
        y = asarray(data_y)
        y_norm = y/sum(y)

        amplitude = max(data_y)
        mean = sum(x*y_norm)
        fwhm = numpy.sqrt(sum(y_norm*(x-mean)**2)/len(x))*2.355
        mixing = 0.1

        parameters, covariance_matrix = optimize.curve_fit(ShadowMath.pseudovoigt_function,
                                                           x, y,
                                                           p0 = [amplitude, mean, fwhm, mixing],
                                                           bounds = ([0.1*amplitude, 0.9*mean,  0.1*fwhm, 1e-3],
                                                                     [10.0*amplitude, 1.1*mean, 10.0*fwhm, 1.0]))

        return parameters, covariance_matrix

    @classmethod
    def pseudovoigt_function(cls, x, A, x0, fwhm, mixing):
        sigma = fwhm/2.355
        gamma = fwhm/2

        return A*(mixing*numpy.exp(-(x-x0)**2/(2*sigma**2)) + (1-mixing)*((gamma**2)/((x-x0)**2 + gamma**2)))

    @classmethod
    def caglioti_broadening_fit(cls, data_x, data_y):
        x = asarray(data_x)
        y = asarray(data_y)

        parameters, covariance_matrix = optimize.curve_fit(ShadowMath.caglioti_broadening_function,
                                                           x, y,
                                                           p0=[0.0001, 0.0001, 0.0001],
                                                           bounds = ([ -1.0, -1.0, -1.0],
                                                                     [ 1.0,  1.0,  1.0]))

        return parameters, covariance_matrix

    @classmethod
    def caglioti_broadening_function(cls, x, U, V, W):
        return numpy.sqrt(W + V * (numpy.tan(x*numpy.pi/360)) + U * (numpy.tan(x*numpy.pi/360))**2)

    @classmethod
    def caglioti_shape_fit(cls, data_x, data_y):
        x = asarray(data_x)
        y = asarray(data_y)

        parameters, covariance_matrix = optimize.curve_fit(ShadowMath.caglioti_shape_function,
                                                           x, y,
                                                           p0=[0.1, 0.0, 0.0],
                                                           bounds = ([ 0.0, -1.0, -1.0],
                                                                     [ 1.0,  1.0,  1.0]))

        return parameters, covariance_matrix

    @classmethod
    def caglioti_shape_function(cls, x, a, b, c):
        return a + b*(x*numpy.pi/360) + c*(x*numpy.pi/360)**2


    @classmethod
    def vectorial_product(cls, vector1, vector2):
        result = [0.0, 0.0, 0.0]

        result[0] = vector1[1]*vector2[2] - vector1[2]*vector2[1]
        result[1] = -(vector1[0]*vector2[2] - vector1[2]*vector2[0])
        result[2] = vector1[0]*vector2[1] - vector1[1]*vector2[0]

        return result

    @classmethod
    def scalar_product(cls, vector1, vector2):
        return vector1[0]*vector2[0] + vector1[1]*vector2[1] + vector1[2]*vector2[2]

    @classmethod
    def vector_modulus(cls, vector):
        return numpy.sqrt(cls.scalar_product(vector, vector))

    @classmethod
    def vector_multiply(cls, vector, constant):
        result = [0.0, 0.0, 0.0]

        result[0] = vector[0] * constant
        result[1] = vector[1] * constant
        result[2] = vector[2] * constant

        return result

    @classmethod
    def vector_divide(cls, vector, constant):
        result = [0.0, 0.0, 0.0]

        result[0] = vector[0] / constant
        result[1] = vector[1] / constant
        result[2] = vector[2] / constant

        return result

    @classmethod
    def vector_normalize(cls, vector):
        return cls.vector_divide(vector, cls.vector_modulus(vector))

    @classmethod
    def vector_sum(cls, vector1, vector2):
        result = [0.0, 0.0, 0.0]

        result[0] = vector1[0] + vector2[0]
        result[1] = vector1[1] + vector2[1]
        result[2] = vector1[2] + vector2[2]

        return result

    @classmethod
    def vector_difference(cls, vector1, vector2):
        result = [0.0, 0.0, 0.0]

        result[0] = vector1[0] - vector2[0]
        result[1] = vector1[1] - vector2[1]
        result[2] = vector1[2] - vector2[2]

        return result

    ##########################################################################
    # Rodrigues Formula:
    #
    # rotated = vector * cos(rotation_angle) + 
    #           (rotation_axis x vector) * sin(rotation_angle) + 
    #           rotation_axis*(rotation_axis . vector)(1 - cos(rotation_angle))
    #
    # rotation_angle in radians
    #
    ##########################################################################
    @classmethod
    def vector_rotate(cls, rotation_axis, rotation_angle, vector):

        result_temp_1 = ShadowMath.vector_multiply(vector, numpy.cos(rotation_angle))
        result_temp_2 = ShadowMath.vector_multiply(ShadowMath.vectorial_product(rotation_axis, vector),
                                                   numpy.sin(rotation_angle))
        result_temp_3 = ShadowMath.vector_multiply(ShadowMath.vector_multiply(rotation_axis, ShadowMath.scalar_product(rotation_axis, vector)), (1 - numpy.cos(rotation_angle)))

        result = ShadowMath.vector_sum(result_temp_1,
                                       ShadowMath.vector_sum(result_temp_2, result_temp_3))
        return result

    @classmethod
    def point_distance(cls, point1, point2):
        return cls.vector_modulus(cls.vector_difference(point1, point2))

class ShadowPhysics:

    A2EV = (codata.h*codata.c/codata.e)*1e+10
    K2EV = 2*numpy.pi/(codata.h*codata.c/codata.e*1e+2)

    @classmethod
    def getWavelengthFromShadowK(cls, k_mod): # in cm
        return (2*numpy.pi/k_mod)*1e+8 # in Angstrom

    @classmethod
    def getShadowKFromWavelength(cls, wavelength): # in A
        return (2*numpy.pi/wavelength)*1e+8 # in cm

    @classmethod
    def getWavelengthFromEnergy(cls, energy): #in eV
        return cls.A2EV/energy # in Angstrom

    @classmethod
    def getEnergyFromWavelength(cls, wavelength): # in Angstrom
        return cls.A2EV/wavelength # in eV

    @classmethod
    def getEnergyFromShadowK(cls, k_mod): # in cm
        return  k_mod/cls.K2EV # in eV

    @classmethod
    def getShadowKFromEnergy(cls, energy): # in A
        return cls.K2EV*energy # in cm

    @classmethod
    def calculateBraggAngle(cls, wavelength, h, k, l, a):

        # lambda = 2 pi / |k| = 2 d sen(th)
        #
        # sen(th) = lambda / (2  d)
        #
        # d = a / sqrt(h\u00b2 + k^2 + l^2)
        #
        # sen(th) = (sqrt(h^2 + k^2 + l^2) * lambda)/(2 a)

        theta_bragg = -1

        argument = wavelength*numpy.sqrt(h**2+k**2+l**2)/(2*a)
        if argument <= 1:
            result = numpy.arcsin(argument)
            if result > 0: theta_bragg = result

        return theta_bragg

    @classmethod
    def checkCompoundName(cls, compound_name):
        if compound_name is None: raise Exception("Compound Name is Empty")
        if str(compound_name.strip()) == "": raise Exception("Compound Name is Empty")

        compound_name = compound_name.strip()

        try:
            ml.CompoundParser(compound_name)
            return compound_name
        except:
            raise Exception("Compound Name is not correct")

    @classmethod
    def getMaterialDensity(cls, material_name):
        if material_name is None: return 0.0
        if str(material_name.strip()) == "": return 0.0

        try:
            compoundData = ml.CompoundParser(material_name)

            n_elements = compoundData["nElements"]
            if n_elements == 1:
                return ml.ElementDensity(compoundData["Elements"][0])
            else:
                density = 0.0
                mass_fractions = compoundData["massFractions"]
                elements = compoundData["Elements"]
                for i in range(n_elements): density += ml.ElementDensity(elements[i]) * mass_fractions[i]
                return density
        except:
            return 0.0

    @classmethod
    def ConstatoBackgroundNoised(cls, constant_value=0, n_sigma=1.0, random_generator=random.Random()):
        sigma = numpy.sqrt(constant_value) # poisson statistic
        noise = (n_sigma*sigma)*random_generator.random()
        sign_marker = random_generator.random()

        if sign_marker > 0.5:
            return int(round(constant_value+noise, 0))
        else:
            return int(round(constant_value-noise, 0))

    @classmethod
    def Chebyshev(cls, n, x):
        if n==0: return 1
        elif n==1: return x
        else: return 2*x*cls.Chebyshev(n-1, x)-cls.Chebyshev(n-2, x)

    @classmethod
    def ChebyshevBackground(cls, coefficients=[0,0,0,0,0,0], twotheta=0):
        coefficients_set = range(0, len(coefficients))
        background = 0

        for index in coefficients_set:
            background += coefficients[index]*cls.Chebyshev(index, twotheta)

        return background

    @classmethod
    def ChebyshevBackgroundNoised(cls, coefficients=[0,0,0,0,0,0], twotheta=0.0, n_sigma=1.0, random_generator=random.Random()):
        background = cls.ChebyshevBackground(coefficients, twotheta)
        sigma = numpy.sqrt(background) # poisson statistic

        noise = (n_sigma*sigma)*random_generator.random()
        sign_marker = random_generator.random()

        if sign_marker > 0.5:
            return int(round(background+noise, 0))
        else:
            return int(round(background-noise, 0))

    @classmethod
    def ExpDecay(cls, h, x):
      return numpy.exp(-h*x)

    @classmethod
    def ExpDecayBackground(cls, coefficients=[0,0,0,0,0,0], decayparams=[0,0,0,0,0,0], twotheta=0):
        coefficients_set = range(0, len(coefficients))
        background = 0

        for index in coefficients_set:
            background += coefficients[index]*cls.ExpDecay(decayparams[index], twotheta)

        return background

    @classmethod
    def ExpDecayBackgroundNoised(cls, coefficients=[0,0,0,0,0,0], decayparams=[0,0,0,0,0,0], twotheta=0, n_sigma=1, random_generator=random.Random()):
        background = cls.ExpDecayBackground(coefficients, decayparams, twotheta)
        sigma = numpy.sqrt(background) # poisson statistic

        noise = (n_sigma*sigma)*random_generator.random()
        sign_marker = random_generator.random()

        if sign_marker > 0.5:
            return int(round(background+noise, 0))
        else:
            return int(round(background-noise, 0))


from orangewidget.widget import Input, Output
from orangecontrib.shadow4.util.shadow4_objects import ShadowData

class TriggerToolsDecorator(object):
    @classmethod
    def get_trigger_input(cls):
        return Input("Trigger", TriggerOut, default=True, auto_summary=False)

    @classmethod
    def get_trigger_output(cls):
       return Output("Trigger", TriggerIn, default=True, auto_summary=False)

    def set_trigger_parameters_for_sources(self, trigger):
        if trigger and trigger.new_object == True:
            if trigger.has_additional_parameter("seed_increment"):
                self.seed += trigger.get_additional_parameter("seed_increment")

            if trigger.has_additional_parameter("variable_name"):
                variable_name         = trigger.get_additional_parameter("variable_name").strip()
                variable_display_name = trigger.get_additional_parameter("variable_display_name").strip()
                variable_value        = trigger.get_additional_parameter("variable_value")
                variable_um           = trigger.get_additional_parameter("variable_um")

                def check_number(x):
                    try:    return float(x)
                    except: return x

                if "," in variable_name:
                    variable_names = variable_name.split(",")

                    if isinstance(variable_value, str) and "," in variable_value:
                        variable_values = variable_value.split(",")
                        for variable_name, variable_value in zip(variable_names, variable_values):
                            setattr(self, variable_name.strip(), check_number(variable_value))
                            self.check_options(variable_name)
                    else:
                        for variable_name in variable_names:
                            setattr(self, variable_name.strip(), check_number(variable_value))
                            self.check_options(variable_name)
                else:
                    setattr(self, variable_name, check_number(variable_value))
                    self.check_options(variable_name)

                scanning_data = ShadowData.ScanningData(variable_name, variable_value, variable_display_name, variable_um)
            else:
                scanning_data = None

            self.run_shadow4(scanning_data=scanning_data)

    def check_options(self, variable_name):
        pass

    def set_trigger_parameters_for_optics(self, trigger): # TODO: complete
        if ShadowCongruence.check_empty_data(self.input_data):
            if trigger and trigger.new_object == True:
                if trigger.has_additional_parameter("variable_name"):
                    variable_name         = trigger.get_additional_parameter("variable_name").strip()
                    variable_display_name = trigger.get_additional_parameter("variable_display_name").strip()
                    variable_value        = trigger.get_additional_parameter("variable_value")
                    variable_um           = trigger.get_additional_parameter("variable_um")

                    def check_number(x):
                        try:    return float(x)
                        except: return x

                    if "," in variable_name:
                        variable_names = variable_name.split(",")

                        if isinstance(variable_value, str) and "," in variable_value:
                            variable_values = variable_value.split(",")
                            for variable_name, variable_value in zip(variable_names, variable_values):
                                setattr(self, variable_name.strip(), check_number(variable_value))
                                self.check_options(variable_name)
                        else:
                            for variable_name in variable_names:
                                setattr(self, variable_name.strip(), check_number(variable_value))
                                self.check_options(variable_name)
                    else:
                        setattr(self, variable_name, check_number(variable_value))
                        self.check_options(variable_name)

                    scanning_data = ShadowData.ScanningData(variable_name, variable_value, variable_display_name, variable_um)
                else:
                    scanning_data = None

                self.run_shadow4(scanning_data=scanning_data)

class Properties(object):
    def __init__(self, props=None):
        self._props = {}
        self._origprops = {}
        self._keymap = {}
        self.othercharre = re.compile(r'(?<!\\)(\s*\=)|(?<!\\)(\s*\:)')
        self.othercharre2 = re.compile(r'(\s*\=)|(\s*\:)')
        self.bspacere = re.compile(r'\\(?!\s$)')

    def __str__(self):
        s='{'
        for key,value in self._props.items():
            s = ''.join((s,key,'=',value,', '))

        s=''.join((s[:-2],'}'))
        return s

    def __parse(self, lines):
        # Every line in the file must consist of either a comment
        # or a key-value pair. A key-value pair is a line consisting
        # of a key which is a combination of non-white space characters
        # The separator character between key-value pairs is a '=',
        # ':' or a whitespace character not including the newline.
        # If the '=' or ':' characters are found, in the line, even
        # keys containing whitespace chars are allowed.
        # A line with only a key according to the rules above is also
        # fine. In such case, the value is considered as the empty string.
        # In order to include characters '=' or ':' in a key or value,
        # they have to be properly escaped using the backslash character.

        # Some examples of valid key-value pairs:
        #
        # key     value
        # key=value
        # key:value
        # key     value1,value2,value3
        # key     value1,value2,value3 \
        #         value4, value5
        # key
        # This key= this value
        # key = value1 value2 value3

        # Any line that starts with a '#' is considered a comment
        # and skipped. Also any trailing or preceding whitespaces
        # are removed from the key/value.

        # This is a line parser. It parses the
        # contents like by line.

        lineno=0
        i = iter(lines)

        for line in i:
            lineno += 1
            line = line.strip()

            if not line: continue
            if line[0] == '#': continue

            sepidx = -1

            m = self.othercharre.search(line)
            if m:
                first, last = m.span()
                start, end = 0, first
                wspacere = re.compile(r'(?<![\\\=\:])(\s)')
            else:
                if self.othercharre2.search(line):
                    wspacere = re.compile(r'(?<![\\])(\s)')

                start, end = 0, len(line)

            m2 = wspacere.search(line, start, end)
            if m2:
                first, last = m2.span()
                sepidx = first
            elif m:
                first, last = m.span()
                sepidx = last - 1

            while line[-1] == '\\':
                nextline = i.next()
                nextline = nextline.strip()
                lineno += 1
                line = line[:-1] + nextline

            if sepidx != -1:
                key, value = line[:sepidx], line[sepidx+1:]
            else:
                key,value = line,''

            self.processPair(key, value)

    def processPair(self, key, value):
        oldkey = key
        oldvalue = value

        keyparts = self.bspacere.split(key)

        strippable = False
        lastpart = keyparts[-1]

        if lastpart.find('\\ ') != -1:
            keyparts[-1] = lastpart.replace('\\','')

        elif lastpart and lastpart[-1] == ' ':
            strippable = True

        key = ''.join(keyparts)
        if strippable:
            key = key.strip()
            oldkey = oldkey.strip()

        oldvalue = self.unescape(oldvalue)
        value = self.unescape(value)

        self._props[key] = value.strip()

        if self._keymap.__contains__(key):
            oldkey = self._keymap.get(key)
            self._origprops[oldkey] = oldvalue.strip()
        else:
            self._origprops[oldkey] = oldvalue.strip()
            self._keymap[key] = oldkey

    def escape(self, value):
        newvalue = value.replace(':','\\:')
        newvalue = newvalue.replace('=','\\=')

        return newvalue

    def unescape(self, value):
        newvalue = value.replace('\\:',':')
        newvalue = newvalue.replace('\\=','=')

        return newvalue

    def load(self, stream):
        if not hasattr(stream, 'read'):
            raise TypeError('Argument should be a file object!')
        if stream.mode != 'r':
            raise ValueError ('Stream should be opened in read-only mode!')

        try:
            lines = stream.readlines()
            self.__parse(lines)
        except IOError as e:
            raise e

    def getProperty(self, key):
        return self._props.get(key)

    def setProperty(self, key, value):
        if type(key) is str and type(value) is str:
            self.processPair(key, value)
        else:
            raise TypeError('both key and value should be strings!')

    def propertyNames(self):
        return self._props.keys()

    def list(self, out=sys.stdout):
        out.write('-- listing properties --\n')
        for key,value in self._props.items():
            out.write(''.join((key,'=',value,'\n')))

    def store(self, out, header=""):
        if out.mode[0] != 'w':
            raise ValueError('Steam should be opened in write mode!')

        try:
            out.write(''.join(('#',header,'\n')))
            tstamp = time.strftime('%a %b %d %H:%M:%S %Z %Y', time.localtime())
            out.write(''.join(('#',tstamp,'\n')))
            for prop, val in self._origprops.items():
                out.write(''.join((prop,'=',self.escape(val),'\n')))

            out.close()
        except IOError as e:
            raise e

    def getPropertyDict(self):
        return self._props

    def __getitem__(self, name):
        return self.getProperty(name)

    def __setitem__(self, name, value):
        self.setProperty(name, value)

    def __getattr__(self, name):
        try:
            return self.__dict__[name]
        except KeyError:
            if hasattr(self._props,name):
                return getattr(self._props, name)

try:
    from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
    from matplotlib.figure import Figure
    from mpl_toolkits.mplot3d import Axes3D  # necessario per caricare i plot 3D
except:
    pass

try:
    from AnyQt.QtWidgets import QApplication, QVBoxLayout
    from AnyQt.QtCore import QCoreApplication

    class MathTextLabel(QWidget):
        def __init__(self, mathText, size=None, parent=None, **kwargs):
            QWidget.__init__(self, parent, **kwargs)

            l=QVBoxLayout(self)
            l.setContentsMargins(0,0,0,0)

            r,g,b,a=self.palette().base().color().getRgbF()

            self._figure=Figure(edgecolor=(r,g,b), facecolor=(r,g,b))
            self._canvas=FigureCanvas(self._figure)
            l.addWidget(self._canvas)

            self._figure.clear()

            if not size:
                size = QCoreApplication.instance().font().pointSize()

            text=self._figure.suptitle(
                mathText,
                x=0.0,
                y=1.0,
                horizontalalignment='left',
                verticalalignment='top',
                size=size)
            self._canvas.draw()

            (x0,y0),(x1,y1)=text.get_window_extent().get_points()
            w=x1-x0; h=y1-y0

            self._figure.set_size_inches(w/80, h/80)
            self.setFixedSize(w,h)
except:
    pass


if __name__ == "__main__":

    try:
        import matplotlib
        import matplotlib.pyplot as plt
        from matplotlib import cm

    except ImportError:
        print(sys.exc_info()[1])
        pass

    #print(congruence.checkFileName("pippo.dat"))
    #print(congruence.checkFileName("Files/pippo.dat"))
    #print(congruence.checkFileName("Files/pippo.dat"))
    #print(congruence.checkFileName("/Users/labx/Desktop/pippo.dat"))

    s = " 5   8095683980.2420149       3.34799999999999994E-008"

    print(s.strip().split(" "))


    print("Bragg")
    ShadowCongruence.checkBraggFile("/Users/labx/Oasys/bragg.dat")
    print("PreRefl")
    ShadowCongruence.checkPreReflFile("/Users/labx/Oasys/reflec.dat")
    print("PreMLayer")
    ShadowCongruence.checkPreMLayerFile("/Users/labx/Oasys/mlayer.dat")

    #ShadowCongruence.checkXOPDiffractionProfileFile("/Users/labx/Oasys/mlayer.dat")

    '''
    print(ShadowPhysics.A2EV)

    print(ShadowPhysics.Chebyshev(4, 21))
    print(ShadowPhysics.Chebyshev(0, 35))

    coefficients = [5.530814e+002, 2.487256e+000, -2.004860e-001, 2.246427e-003, -1.044517e-005, 1.721576e-008]
    random_generator=random.Random()

    print(ShadowPhysics.ChebyshevBackgroundNoised(coefficients, 10, random_generator=random_generator))
    print(ShadowPhysics.ChebyshevBackgroundNoised(coefficients, 11, random_generator=random_generator))
    print(ShadowPhysics.ChebyshevBackgroundNoised(coefficients, 12, random_generator=random_generator))
    print(ShadowPhysics.ChebyshevBackgroundNoised(coefficients, 13, random_generator=random_generator))
    print(ShadowPhysics.ChebyshevBackgroundNoised(coefficients, 14, random_generator=random_generator))
    print(ShadowPhysics.ChebyshevBackgroundNoised(coefficients, 15, random_generator=random_generator))
    print(ShadowPhysics.ChebyshevBackgroundNoised(coefficients, 16, random_generator=random_generator))
    print(ShadowPhysics.ChebyshevBackgroundNoised(coefficients, 17, random_generator=random_generator))
    print(ShadowPhysics.ChebyshevBackgroundNoised(coefficients, 18, random_generator=random_generator))
    print(ShadowPhysics.ChebyshevBackgroundNoised(coefficients, 19, random_generator=random_generator))
    print(ShadowPhysics.ChebyshevBackgroundNoised(coefficients, 20, random_generator=random_generator))
    '''
    '''
    import matplotlib.pyplot as plt

    x_coords, y_coords, z_values = ShadowPreProcessor.read_surface_error_file("/Users/labx/Oasys/mirror.dat")

    fig = plt.figure()
    ax = fig.gca(projection='3d')
    X, Y = numpy.meshgrid(x_coords, y_coords)
    surf = ax.plot_surface(X, Y, z_values.T, rstride=1, cstride=1, cmap=cm.coolwarm,
            linewidth=0, antialiased=False)
    #ax.set_zlim(-1.01, 1.01)

    fig.colorbar(surf, shrink=0.5, aspect=5)

    plt.show()

    app = QApplication(sys.argv)

    widget = QWidget()
    widget.setLayout(QVBoxLayout())

    figure = Figure(figsize=(100, 100))
    figure.patch.set_facecolor('white')

    axis = figure.add_subplot(111, projection='3d')

    axis.set_xlabel("X (cm)")
    axis.set_ylabel("Y (cm)")
    axis.set_zlabel("Z (cm)")

    figure_canvas = FigureCanvasQTAgg(figure)
    widget.layout().addWidget(figure_canvas)
    figure_canvas.setFixedWidth(500)
    figure_canvas.setFixedHeight(450)

    x_coords, y_coords, z_values = ShadowPreProcessor.read_surface_error_file("/Users/labx/Oasys/mirror.dat")

    x_to_plot, y_to_plot = numpy.meshgrid(x_coords, y_coords)

    axis.plot_surface(x_to_plot, y_to_plot, z_values.T,
                      rstride=1, cstride=1, cmap=cm.autumn, linewidth=0.5, antialiased=True)

    figure_canvas.draw()
    figure_canvas.show()
    widget.show()

    app.exec()
    '''

