#pylint: disable=no-init,invalid-name
from mantid.kernel import *
from mantid.api import *
import math

class EnginXCalibrateFull(PythonAlgorithm):
    def category(self):
        return "Diffraction\Engineering;PythonAlgorithms"

    def name(self):
        return "EnginXCalibrateFull"

    def summary(self):
        return "Calibrates every pixel position by performing single peak fitting."

    def PyInit(self):
        self.declareProperty(FileProperty("Filename", "", FileAction.Load),\
    		"Calibration run to use")

        self.declareProperty(ITableWorkspaceProperty("DetectorPositions", "", Direction.Output),\
    		"A table with the detector IDs and calibrated detector positions in V3P format.")

        self.declareProperty(FloatArrayProperty("ExpectedPeaks", ""),\
    		"A list of dSpacing values where peaks are expected.")

        self.declareProperty("Bank", 1, "Which bank to calibrate")

    def PyExec(self):

        import EnginXUtils

        ws = self._loadCalibrationRun()

        ws = self._prepareWsForFitting(ws)

        positionTable = self._createPositionsTable()

        indices = EnginXUtils.getWsIndicesForBank(self.getProperty('Bank').value, ws)

        prog = Progress(self, 0, 1, len(indices))

        for i in indices:

            _, difc = self._fitPeaks(ws, i)

            det = ws.getDetector(i)

            newPos = self._getCalibratedDetPos(difc, det, ws)

            positionTable.addRow([det.getID(), newPos])

            prog.report()

        self.setProperty("DetectorPositions", positionTable)

    def _loadCalibrationRun(self):
        """ Loads specified calibration run
    	"""
        alg = self.createChildAlgorithm('Load')
        alg.setProperty('Filename', self.getProperty('Filename').value)
        alg.execute()
        return alg.getProperty('OutputWorkspace').value

    def _prepareWsForFitting(self, ws):
        """ Rebins the workspace and converts it to distribution
    	"""
        rebinAlg = self.createChildAlgorithm('Rebin')
        rebinAlg.setProperty('InputWorkspace', ws)
        rebinAlg.setProperty('Params', '-0.0005') # The value is borrowed from OG routines
        rebinAlg.execute()
        result = rebinAlg.getProperty('OutputWorkspace').value

        if result.isDistribution()==False:
            convertAlg = self.createChildAlgorithm('ConvertToDistribution')
            convertAlg.setProperty('Workspace', result)
            convertAlg.execute()

        return result

    def _createPositionsTable(self):
        """ Creates an empty table for storing detector positions
    	"""
        alg = self.createChildAlgorithm('CreateEmptyTableWorkspace')
        alg.execute()
        table = alg.getProperty('OutputWorkspace').value

        table.addColumn('int', 'Detector ID')
        table.addColumn('V3D', 'Detector Position')

        return table

    def _fitPeaks(self, ws, wsIndex):
        """ Fits expected peaks to the spectrum, and returns calibrated zero and difc values.
    	"""
        alg = self.createChildAlgorithm('EnginXFitPeaks')
        alg.setProperty('InputWorkspace', ws)
        alg.setProperty('WorkspaceIndex', wsIndex) # There should be only one index anyway
        alg.setProperty('ExpectedPeaks', self.getProperty('ExpectedPeaks').value)
        alg.execute()

        difc = alg.getProperty('Difc').value
        zero = alg.getProperty('Zero').value

        return (zero, difc)

    def _getCalibratedDetPos(self, newDifc, det, ws):
        """ Returns a detector position which corresponds to the newDifc value.

    		The two_theta and phi of the detector are preserved, L2 is changed.
    	"""
        detL2 = det.getDistance(ws.getInstrument().getSample())
        detTwoTheta = ws.detectorTwoTheta(det)
        detPhi = det.getPhi()

        newL2 = (newDifc / (252.816 * 2 * math.sin(detTwoTheta / 2.0))) - 50

        newPos = self._V3DFromSpherical(newL2, detTwoTheta, detPhi)

        return newPos

    def _V3DFromSpherical(self, R, polar, azimuth):
        """ Returns a cartesian 3D vector for the given spherical coordinates.

    		Borrowed from V3D::spherical (C++).
    	"""
        z = R*math.cos(polar)
        ct=R*math.sin(polar)
        x=ct*math.cos(azimuth)
        y=ct*math.sin(azimuth)

        return V3D(x,y,z)



AlgorithmFactory.subscribe(EnginXCalibrateFull)