###
# Copyright 2008-2011 Diamond Light Source Ltd.
# This file is part of Diffcalc.
#
# Diffcalc is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Diffcalc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Diffcalc.  If not, see <http://www.gnu.org/licenses/>.
###

try:
    from gda.device.scannable import ScannableMotionBase
except ImportError:
    from diffcalc.gdasupport.minigda.scannable import \
        ScannableBase as ScannableMotionBase

from diffcalc.util import getMessageFromException

# TODO: Split into a base class when making other scannables


class DiffractometerScannableGroup(ScannableMotionBase):
    """"
    Wraps up a scannableGroup of axis to tweak the way the resulting
    object is displayed and to add a simulate move to method.

    The scannable group should have the same geometry as that expected
    by the diffractometer hardware geometry used in the diffraction
    calculator.

    The optional parameter slaveDriver can be used to provide a
    slave_driver. This is useful for triggering a move of an incidental
    axis whose position depends on that of the diffractometer, but whose
    position need not be included in the DiffractometerScannableGroup
    itself. This parameter is exposed as a field and can be set or
    cleared to null at will without effecting the core calculation code.
    """

    def __init__(self, name, diffcalc_module, scannableGroup,
                 slave_driver=None, hint_generator=None):
        # if motorList is None, will create a dummy __group
        self.diffcalc_module = diffcalc_module
        self.__group = scannableGroup
        self.slave_driver = slave_driver
        self.setName(name)
        self.hint_generator = hint_generator

    def getInputNames(self):
        return self.__group.getInputNames()

    def getExtraNames(self):
        if self.slave_driver is None:
            return []
        else:
            return self.slave_driver.getScannableNames()

    def getOutputFormat(self):
        if self.slave_driver is None:
            slave_formats = []
        else:
            slave_formats = self.slave_driver.getScannableNames()
        return list(self.__group.getOutputFormat()) + slave_formats

    def asynchronousMoveTo(self, position):
        self.__group.asynchronousMoveTo(position)
        if self.slave_driver is not None:
            self.slave_driver.triggerAsynchronousMove(position)

    def getPosition(self):
        if self.slave_driver is None:
            slave_positions = []
        else:
            slave_positions = self.slave_driver.getPositions()
        return list(self.__group.getPosition()) + list(slave_positions)

    def isBusy(self):
        if self.slave_driver is None:
            return self.__group.isBusy()
        else:
            return self.__group.isBusy() or self.slave_driver.isBusy()

    def waitWhileBusy(self):
        self.__group.waitWhileBusy()
        if self.slave_driver is not None:
            self.slave_driver.waitWhileBusy()

    def simulateMoveTo(self, pos):
        if len(pos) != len(self.getInputNames()):
            raise ValueError('Wrong number of inputs')
        try:
            (hkl, params) = self.diffcalc_module.angles_to_hkl(pos)
        except Exception, e:
            return "Error: %s" % getMessageFromException(e)
        width = max(len(k) for k in params)

        lines = (['  ' + 'hkl'.rjust(width) + ' : % 9.4f  %.4f  %.4f' %
                  (hkl[0], hkl[1], hkl[2])])
        lines[-1] = lines[-1] + '\n'
        fmt = '  %' + str(width) + 's : % 9.4f'
        for k in sorted(params):
            lines.append(fmt % (k, params[k]))
        return '\n'.join(lines)

    def __repr__(self):
        position = self.getPosition()
        names = list(self.getInputNames()) + list(self.getExtraNames())
        if self.hint_generator is None:
            hint_list = [''] * len(self.getInputNames())
        else:
            hint_list = self.hint_generator()
        
        lines = [self.name + ':']
        width = max(len(k) for k in names)
        fmt = '  %' + str(width) + 's : % 9.4f   %s'
        for name, pos, hint in zip(names, position, hint_list):
            lines.append(fmt % (name, pos, hint))
        lines[len(self.getInputNames())] += '\n'
        return '\n'.join(lines)
