###################################################################################################\
# Diffcalc utilities
###################################################################################################

###################################################################################################\
# Installing  
###################################################################################################

#1- Download from: https://github.com/DiamondLightSource/diffcalc/archive/v2.1.zip
#2- Extract the contents to {script}/Lib/diffcalc
#3- Download http://central.maven.org/maven2/gov/nist/math/jama/1.0.3/jama-1.0.3.jar 
#   to the extensions folder.
#4- On {script}/Lib/diffcalc/diffcalc/gdasupport/you.py, the line " wl.asynchronousMoveTo(1)"
#   must be commented for the energy not to move when the library is loaded.  

###################################################################################################\
# Library loading and Hardware setup
###################################################################################################

#1- Create a MotorGroup with the diffractometer motors 
#   e.g.   'sixc', containing mu, delta, gam, eta, chi, phi motors         (gam = nu)
#   or     'fivec', containing delta, gam, eta, chi, phi motors
#   or     'fourc', containing delta, eta, chi, phi motors
#2- Create  positioner to read/set the energy in kEv, e.g. named 'en'
#3- Execute: run("diffutils")
#4- Execute: setup_diff(sixc, en)


###################################################################################################\
# API
###################################################################################################

# Orientation commands defined in  https://github.com/DiamondLightSource/diffcalc#id19 are 
# defined heren with identical signatures, and so the constraint commands.
# Motion command names were changed because thge original can collide with other globals:
# hklci, hklca, hklwh, hklget, hklmv and hklsim(hkl).


from __future__ import absolute_import
import traceback
import os


import Jama.Matrix
diffcalc_path = os.path.abspath(expand_path("{script}/Lib/diffcalc"))
if not diffcalc_path in sys.path:
    sys.path.append(diffcalc_path)

import diffcalc
import math
from diffcalc import settings
from diffcalc.hkl.you.geometry import YouGeometry,SixCircle, FiveCircle, FourCircle, YouPosition
from diffcalc.hardware import HardwareAdapter
from diffcalc.ub.persistence import UBCalculationJSONPersister, UbCalculationNonPersister
from diffcalc.gdasupport.minigda.scannable import ScannableBase, ScannableGroup
#from diffcalc.gdasupport.minigda import command
import diffcalc.hkl.you.calc as you_calc


import ch.psi.pshell.device.PositionerConfig as PositionerConfig
import ch.psi.pshell.device.RegisterConfig as RegisterConfig
import ch.psi.pshell.device.Register as Register

_difcalc_names = {}

#
# Disable error handling designed for interactive use
#diffcalc.util.DEBUG = True
# Disable console bold charcters
diffcalc.util.COLOURISE_TERMINAL_OUTPUT = False

###################################################################################################
# Device mapping to difcalc
###################################################################################################
class PositionerScannable(ScannableBase):
    def __init__(self, positioner, name = None):
        self.positioner = positioner
        self.name = positioner.name if name is None else name
        self.inputNames = [self.name]
        self.outputFormat = ['% 6.4f']
        self.level = 3

    def isBusy(self):
        return self.positioner.state == State.Busy

    def waitWhileBusy(self):
        self.positioner.waitReady(-1)

    def asynchronousMoveTo(self, new_position):
        #print "Moving " , self.name, " to: ", new_position 
        self.positioner.moveAsync(float(new_position), -1)

    def getPosition(self):
        return self.positioner.getPosition()

def _get_diffcalc_axis_names():
    nu_name=diffcalc.hkl.you.constraints.NUNAME
    return ("mu", "delta", nu_name, "eta", "chi", "phi")

class PositionerScannableGroup(ScannableGroup):
    def __init__(self, name, motors, diffcalc_axis_names=None):
        self.name = name
        global _difcalc_names
        _difcalc_names = {}
        positioners = []
        if diffcalc_axis_names is None:
            if len(motors)   == 6: diffcalc_axis_names = _get_diffcalc_axis_names()
            elif len(motors) == 5: diffcalc_axis_names = ("delta", "gam", "eta", "chi", " phi")
            elif len(motors) == 4: diffcalc_axis_names = ("delta", "eta", "chi", " phi")
        self.diffcalc_axis_names = diffcalc_axis_names
        for i in range(len(motors)):
            _difcalc_names[motors[i]] = diffcalc_axis_names[i]
            exec('self.' + diffcalc_axis_names[i] + ' = PositionerScannable(' + motors[i].name  + ', "' +diffcalc_axis_names[i] + '")')
            exec('positioners.append(self.' + diffcalc_axis_names[i] + ')' ) 
        #for m in motors: 
        #    exec('self.' + m.name + ' = PositionerScannable(' + m.name + ', "' + m.name + '")')
        #    exec('positioners.append(self.' + m.name + ')' ) 
        ScannableGroup.__init__(self, self.name, positioners)        
             
class MotorGroupScannable(PositionerScannableGroup):        
    def __init__(self, motor_group, diffcalc_axis_names=None, simultaneous_move=False):        
        self.simultaneous_move = simultaneous_move
        self.motor_group = motor_group
        PositionerScannableGroup.__init__(self, motor_group.name, motor_group.motors, diffcalc_axis_names)
        self.motor_group.restoreSpeedAfterMove = self.simultaneous_move

    #Make sync moves (default implementation trigger each motor individually)
    def asynchronousMoveTo(self, position):
        if self.simultaneous_move:
            position = [(float('nan') if v is None else v) for v in position]
            self.motor_group.write(position)
        else:
            PositionerScannableGroup.asynchronousMoveTo(self, position)        
      

class ScannableAdapter(HardwareAdapter):
    def __init__(self, diffractometer, energy, energy_multiplier_to_kev=1):
        self.diffractometer = diffractometer
        self.energy = energy
        self.energy_multiplier_to_kev = energy_multiplier_to_kev    
        input_names = diffractometer.getInputNames()
        HardwareAdapter.__init__(self, input_names)

    #Returns the current physical POSITIONS
    def get_position(self):
        """
        pos = getDiffractometerPosition() -- returns the current physical
        diffractometer position as a list in degrees
        """
        return self.diffractometer.getPosition()

    #returns energy in kEv 
    def get_energy(self):
        """energy = get_energy() -- returns energy in kEv (NOT eV!) """
        multiplier = self.energy_multiplier_to_kev
        energy = self.energy.getPosition() * multiplier
        if energy is None:
            raise DiffcalcException("Energy has not been set")
        return energy

    def get_motor(self,name):
        global _motor_group
        global _difcalc_names
        for m in _difcalc_names.keys():
            if _difcalc_names[m] == name:
                return m
        for m in _motor_group.motors:
            if m.name == name:
                return m
        raise Exception("Invalid axis name: " + str(name))
            

    def get_lower_limit(self, name):
        '''returns lower limits by axis name. Limit may be None if not set
        '''
        m = self.get_motor(name)
        ret = m.getMinValue()
        if ret == float("NaN"): ret = None
        return ret

    def get_upper_limit(self, name):
        '''returns upper limit by axis name. Limit may be None if not set
        '''
        m = self.get_motor(name)
        ret = m.getMaxValue()
        if ret == float("NaN"): ret = None
        return ret

    def set_lower_limit(self, name, value):
        """value may be None to remove limit"""
        if value is None: value = float("NaN")
        m = self.get_motor(name)
        m.config.minValue =value
        
    def set_upper_limit(self, name, value):
        """value may be None to remove limit"""
        if value is None: value = float("NaN")
        m = self.get_motor(name)
        m.config.maxValue =value

    def is_axis_value_within_limits(self, axis_name, value):
        m = self.get_motor(axis_name)   
        upper = self.get_upper_limit(axis_name)  
        lower = self.get_lower_limit(axis_name)  
        if (upper is None) or (math.isnan(upper)): upper = sys.float_info.max
        if (lower is None) or (math.isnan(lower)): lower = -sys.float_info.max
        return lower <= value <= upper 

    @property
    def name(self):
        return self.diffractometer.getName()

class MotorGroupAdapter(ScannableAdapter):
    def __init__(self, diffractometer, energy, energy_multiplier_to_kev=1, diffcalc_axis_names=None, simultaneous_move=False):
        self.diffractometer = MotorGroupScannable(diffractometer, diffcalc_axis_names, simultaneous_move)
        self.energy = PositionerScannable(energy)
        self.energy.level = 3    
        ScannableAdapter.__init__(self, self.diffractometer, self.energy, energy_multiplier_to_kev)

class Wavelength(RegisterBase):    
    def doRead(self):
        try:
            return get_wavelength().getPosition()
        except:
            return None

    def doWrite(self, val):
        get_wavelength().asynchronousMoveTo(val)
                

###################################################################################################
# HKL Pseudo-devices
###################################################################################################
class HklPositoner (PositionerBase):
    def __init__(self, name, index, hkl_group):
         PositionerBase.__init__(self, name, PositionerConfig())
         self.setParent(hkl_group)
         self.index  = index

    def isReady(self):
        return PositionerBase.isReady(self) and self.getParent().isReady()
        
    def doRead(self):
        return self.getParent()._setpoint[self.index]

    def doWrite(self, value): 
        #print "Setting " , self.getName(), "to: ", value
        pos = [None, None, None]
        pos[self.index] = value
        self.getParent().write(pos)
        
    def doReadReadback(self):
        if java.lang.Thread.currentThread() != self.getParent()._updating_thread:
             self.getParent().update()
        return self.getParent()._readback[self.index]

class HklGroup(RegisterBase, Register.RegisterArray):
    def __init__(self, name):        
        RegisterBase.__init__(self, name, RegisterConfig())
        self.hkl=get_hkl()
        self.h, self.k, self.l = HklPositoner("h", 0, self), HklPositoner("k", 1, self), HklPositoner("l", 2, self)
        add_device(self.h, True)
        add_device(self.k, True)
        add_device(self.l, True)           
        self._setpoint = self.doRead()
        self._updating = False
        
    def getSize(self):
        return 3
                
    def doRead(self):   
        try:
            self._readback = self.hkl.getPosition()  
            self._updating_thread = java.lang.Thread.currentThread()       
            self.h.update()
            self.k.update()
            self.l.update()
        except:
            #traceback.print_exc()    
            self._readback = (None, None, None)
        finally:
            self._updating_thread = None
        return self._readback 

    def doWrite(self, pos):
        self._setpoint = None if (pos is None) else [(None if v is None else float(v)) for v in pos]
        #print "Moving to: " + str(pos)
        self.hkl.asynchronousMoveTo(pos) 

    def sim(self, pos):
        return self.hkl.simulateMoveTo(pos)
    
###################################################################################################
# System setup
###################################################################################################
you = None
dc, ub, hardware, hkl = None, None, None, None
_motor_group = None
def setup_diff(diffractometer= None, energy= None, diffcalc_axis_names = None, geometry=None, persist_ub=True, simultaneous_move=False):
    """
        configure diffractometer. Display configuration if no parameter is given
        diffractometer: Diffraction motor group 
        energy: Positioner having energy in kev
        geometry: YouGeometry extension. If none, uses default
        diffcalc_axis_names: if None use defaults:
            - mu, delta, gam, eta, chi, phi (six circle)
            - delta, gam, eta, chi, phi (ficve circle)
            - delta, eta, chi, phi (four circle)        
    """
    global you, dc, ub, hardware, hkl, _motor_group 
    if diffractometer is not None:
        _motor_group = diffractometer
        you = None
        if geometry is not None:
            settings.geometry = geometry
        elif diffcalc_axis_names is not None:
            class CustomGeometry(YouGeometry):
                def __init__(self):
                    self.all_axis_names = _get_diffcalc_axis_names()
                    self.my_axis_names = diffcalc_axis_names
                    fixed_constraints = {}
                    for axis in self.all_axis_names:
                        if not axis in self.my_axis_names:
                            fixed_constraints[axis] = 0
                    YouGeometry.__init__(self, diffractometer.name, fixed_constraints)                        
                def physical_angles_to_internal_position(self, physical_angle_tuple):
                    pos=[]
                    index = 0
                    for axis in self.all_axis_names:
                        pos.append(physical_angle_tuple[index] if (axis in self.my_axis_names) else 0)
                        index = index+1
                    pos.append("DEG")#units
                    return YouPosition(*pos)               
                def internal_position_to_physical_angles(self, internal_position):
                    pos = internal_position.clone()
                    pos.changeToDegrees()
                    pos = pos.totuple()
                    ret = []
                    for i in range (len(self.all_axis_names)):                                    
                        if self.all_axis_names[i] in self.my_axis_names:                       
                            ret.append(pos[i])
                    return tuple(ret)
            settings.geometry = CustomGeometry()
        elif len(diffractometer.motors) == 6:
            settings.geometry = SixCircle()
        elif len(diffractometer.motors) == 5:
            settings.geometry = FiveCircle()
        elif len(diffractometer.motors) == 4:
            settings.geometry = FourCircle()
        else:    
            raise Exception("Invalid motor group")
        settings.hardware = MotorGroupAdapter(diffractometer, energy, 1, diffcalc_axis_names, simultaneous_move)
        
        if persist_ub:
            settings.persistence_path = os.path.abspath(expand_path("{config}/diffcalc"))
            if not os.path.exists(settings.persistence_path):
                os.makedirs(settings.persistence_path) 
            print "UB calculations persistence path: " + settings.persistence_path
            settings.ubcalc_persister = UBCalculationJSONPersister(settings.persistence_path)
        else:
            print "UB calculations are not persisteds"
            settings.ubcalc_persister = UbCalculationNonPersister()
        settings.axes_scannable_group = settings.hardware.diffractometer
        settings.energy_scannable = settings.hardware.energy
        settings.ubcalc_strategy = you_calc.YouUbCalcStrategy()
        settings.angles_to_hkl_function = you_calc.youAnglesToHkl
        from diffcalc.gdasupport import you
        reload(you)
        
        # These must be imported AFTER the settings have been configured
        from diffcalc.dc import dcyou as dc
        from diffcalc.ub import ub
        from diffcalc import hardware
        from diffcalc.hkl.you import hkl

        add_device(HklGroup("hkl_group"), True)
        add_device(Wavelength("wavelength", 6), True)
        hkl_group.polling = 250
        wavelength.polling = 250

    if settings.hardware is not None:
        print "Diffractometer defined with:"
        print " \t" + "Motor group: " + str(settings.hardware.diffractometer.name) 
        print " \t" + "Energy: " + str(settings.hardware.energy.name) 
        print "\nDiffcalc axis names:"
        for m in _difcalc_names.keys(): 
            print " \t Motor " + m.name + " = Axis " + _difcalc_names[m]        
    else:
        print "Diffractometer is not defined\n"
    print
    
def setup_axis(motor = None, min=None, max=None, cut=None):
    """
    configure axis range and cut.
    displays ranges if motor is None
    """
    if motor is not None:
        name = get_axis_name(motor)
        if min is not None: hardware.setmin(name, min)
        if max is not None: hardware.setmax(name, max)
        if cut is not None: hardware.setcut(name, cut) 
    else:
        print "Axis range configuration:"
        hardware.hardware()
        print

###################################################################################################
# Acceess functions
###################################################################################################
def get_diff():
    return settings.hardware.diffractometer

def get_energy():
    return settings.hardware.energy
    
def get_adapter():
    return settings.hardware
    
def get_motor_group():
    return _motor_group

def get_wavelength():
    return you.wl

def get_hkl():
    return you.hkl 

def get_axis_name(motor):
    if is_string(motor):
        motor = get_adapter().get_motor(motor)
    return _difcalc_names[motor]    

###################################################################################################
# Orientation Commands
###################################################################################################


# State

def newub(name):
    """
    start a new ub calculation name
    """
    try:
        rmub(name)
    except:
        pass
    try:        
        return ub.newub(name)
    finally:
        save_exp_context()
def loadub(name_or_num):
    """
    load an existing ub calculation
    """
    try:
        return ub.loadub(name_or_num)
    finally:
        save_exp_context()
        
def lastub():
    """
    load the last used ub calculation
    """
    try:
        return ub.lastub()
    finally:
        save_exp_context()        
    
def listub():
    """
    list the ub calculations available to load
    """
    return ub.listub()

def rmub(name_or_num):
    """
    remove existing ub calculation
    """
    return ub.rmub(name_or_num)    

def saveubas(name):
    """
    save the ub calculation with a new name
    """
    try:    
        return ub.saveubas(name)
    finally:
        save_exp_context()    

# Lattice

def setlat(name=None, *args):
    """
    set lattice parameters (Angstroms and Deg)
    setlat  -- interactively enter lattice parameters (Angstroms and Deg)
    setlat name a -- assumes cubic
    setlat name a b -- assumes tetragonal
    setlat name a b c -- assumes ortho
    setlat name a b c gamma -- assumes mon/hex with gam not equal to 90
    setlat name a b c alpha beta gamma -- arbitrary
    """
    return ub.setlat(name, *args)

def c2th(hkl, en=None):
    """
    calculate two-theta angle for reflection
    """
    return ub.c2th(hkl, en)

def hklangle(hkl1, hkl2):
    """
    calculate angle between [h1 k1 l1] and [h2 k2 l2] crystal planes
    """
    return ub.hklangle(hkl1, hkl2)


# Reference (surface)

def setnphi(xyz = None):
    """
    sets or displays (xyz=None) n_phi reference
    """
    return ub.setnphi(xyz)    
 

def setnhkl(hkl = None):
    """
    sets or displays (hkl=None)  n_hkl reference
    """
    return ub.setnhkl(hkl)    

# Reflections
        
def showref():
    """
    shows full reflection list
    """
    return ub.showref()   

def addref(*args):
    """
    Add reflection
    addref    --  add reflection interactively
    addref [h k l] {'tag'}    --  add reflection with current position and energy
    addref [h k l] (p1, .., pN) energy {'tag'}    --  add arbitrary reflection
    """
    return ub.addref(*args)       

def editref(idx):
    """
    interactively edit a reflection (idx is tag or index numbered from 1)
    """
    return ub.editref(idx)   

def delref(idx):
    """
    deletes a reflection (idx is tag or index numbered from 1)
    """
    return ub.delref(idx)   


def clearref():
    """
    deletes all the reflections
    """
    return ub.clearref()   

def swapref(idx1=None, idx2=None):
    """
    swaps two reflections
    swapref -- swaps first two reflections used for calculating U matrix
    swapref {num1 | 'tag1'} {num2 | 'tag2'} -- swaps two reflections
    """
    return ub.swapref(idx1, idx2)

        
# Crystal Orientations

def showorient():
    """
    shows full list of crystal orientations
    """
    #TODO: Workaround of bug on Diffcalc (str_lines needs parameter)
    if ub.ubcalc._state.orientlist:
        print '\n'.join(ub.ubcalc._state.orientlist.str_lines(None))   
        return 
    return ub.showorient()  

def addorient(*args):
    """
    addorient -- add crystal orientation interactively
    addorient [h k l] [x y z] {'tag'}	-- add crystal orientation in laboratory frame
    """
    return ub.addorient(*args)  

def editorient(idx):
    """
    interactively edit a crystal orientation (idx is tag or index numbered from 1)
    """
    return ub.editorient(tag_or_num)  

def delorient(idx):
    """
    deletes a crystal orientation (idx is tag or index numbered from 1)
    """
    return ub.delorient(tag_or_num)  

def clearorient():
    """
    deletes all the crystal orientations
    """
    return ub.clearorient()  
    
def swaporient(idx1=None, idx2=None):
    """
    swaps two swaporient
    swaporient -- swaps first two crystal orientations used for calculating U matrix
    swaporient {num1 | 'tag1'} {num2 | 'tag2'} -- swaps two crystal orientations
    """
    return ub.swaporient(idx1, idx2)


# UB Matrix
def showub():
    """
    show the complete state of the ub calculation
    NOT A DIFFCALC COMMAND
    """
    return ub.ub()  

def checkub():
    """
    show calculated and entered hkl values for reflections
    """
    return ub.checkub()  

def setu(U=None):
    """
    manually set U matrix
    setu -- set U matrix interactively
    setu [[..][..][..]] -- manually set U matrix
    """
    return ub.setu(U)  

def setub(UB=None):
    """
    manually set UB matrix
    setub -- set UB matrix interactively
    setub [[..][..][..]] -- manually set UB matrix
    """
    return ub.setub(UB)      

def getub():
    """
    returns current UB matrix
    NOT A DIFFCALC COMMAND
    """    
    return None if ub.ubcalc._UB is None else ub.ubcalc._UB.tolist()  

def calcub(idx1=None, idx2=None):
    """
    (re)calculate u matrix 
    calcub -- (re)calculate U matrix from the first two reflections and/or orientations.
    calcub idx1 idx2 -- (re)calculate U matrix from reflections and/or orientations referred by indices and/or tags idx1 and idx2.
    """
    return ub.calcub(idx1, idx2)  

def trialub(idx=1):
    """
    (re)calculate u matrix using one reflection only
    Use indice or tags idx1. Default: use first reflection.
    """
    return ub.trialub(idx)      

def refineub(*args):
    """
    refine unit cell dimensions and U matrix to match diffractometer angles for a given hkl value
    refineub -- interactively
    refineub [h k l] {pos}  
    """
    return ub.refineub(*args)     

def fitub(*args):
    """
    fitub ref1, ref2, ref3... -- fit UB matrix to match list of provided reference reflections.
    """
    return ub.fitub(*args)         

def addmiscut(angle, xyz=None):
    """
    apply miscut to U matrix using a specified miscut angle in degrees and a rotation axis (default: [0 1 0])
    """
    return ub.addmiscut(angle, xyz)     
        
def setmiscut(angle, xyz=None):
    """
    manually set U matrix using a specified miscut angle in degrees and a rotation axis (default: [0 1 0])
    """
    return ub.setmiscut(angle, xyz)  



###################################################################################################
# Motion Commands
###################################################################################################

#Constraints

def con(*args):
    """
    list or set available constraints and values
    con -- list available constraints and values
    con <name> {val} -- constrains and optionally sets one constraint
    con <name> {val} <name> {val} <name> {val} -- clears and then fully constrains    
    """
    try:
        ret = hkl.con(*args)
    finally:
        save_exp_context()
    return ret

def uncon(name):
    """
    remove constraint
    """
    try:
        ret = hkl.uncon(name)
    finally:
        save_exp_context()
    return ret


# HKL
def allhkl(_hkl, wavelength=None):
    """
    print all hkl solutions ignoring limits
    """
    return hkl.allhkl(_hkl, wavelength)   


#Hardware

def setmin(axis, val=None):
    """
    set lower limits used by auto sector code (nan to clear)
    """
    name = get_axis_name(axis)
    try:
        hardware.setmin(name, val)
    finally:
        save_exp_context()

def setmax(axis, val=None):
    """
    set upper limits used by auto sector code (nan to clear)
    """
    name = get_axis_name(axis)
    try:
        return hardware.setmax(name, val)
    finally:
        save_exp_context()

def setcut(axis, val):
    """
    sets cut angle
    """
    name = get_axis_name(axis)
    try:
        return hardware.setcut(name, val)        
    finally:
        save_exp_context()
        
###################################################################################################
# Motion commands: not standard Diffcalc names
###################################################################################################
   

def hklci(positions, energy=None):
    """
    converts positions of motors to reciprocal space coordinates (H K L)
    """ 
    return dc.angles_to_hkl(positions, energy)

def hklca(hkl, energy=None):
    """
    converts reciprocal space coordinates (H K L) to positions of motors.
    """ 
    return dc.hkl_to_angles(hkl[0], hkl[1], hkl[2], energy)

def hklwh():
    """
    prints the current reciprocal space coordinates (H K L) and positions of motors.
    """ 
    hkl = hklget()
    print "HKL: " + str(hkl)
    for m in _difcalc_names.keys(): 
        print _difcalc_names[m] + " [" + m.name + "] :" + str(m.take()) 
    
def hklget():       
    """
    get current hkl position
    """           
    return hkl_group.read()  
    
def hklmv(hkl):  
    """
    move to hkl position
    """             
    hkl_group.write(hkl)

def hklsim(hkl):
    """
    simulates moving diffractometer
    """
    return hkl_group.sim(hkl)
  

###################################################################################################
# HKL Combined Scan
###################################################################################################
def hklscan(vector, readables,latency = 0.0,  passes = 1, **pars):
    """
    HKL Scan: 

    Args:
        vector(list of lists): HKL values to be scanned
        readables(list of Readable): Sensors to be sampled on each step.
        latency(float, optional): settling time for each step before readout, defaults to 0.0.
        passes(int, optional): number of passes
        pars(keyworded variable length arguments, optional): scan optional named arguments:
            - title(str, optional): plotting window name.     
            - hidden(bool, optional): if true generates no effects on user interface.     
            - before_read (function, optional): callback on each step, before sampling. Arguments: positions, scan
            - after_read (function, optional): callback on each step, after sampling. Arguments: record, scan.
            - before_pass (function, optional): callback before each scan pass execution. Arguments: pass_num, scan.
            - after_pass (function, optional): callback after each scan pass execution. Arguments: pass_num, scan.
            - Aditional arguments defined by set_exec_pars.
    Returns:
        ScanResult object.

    """
    readables=to_list(string_to_obj(readables))
    pars["initial_move"]  = False
    scan = ManualScan([h,k,l], readables ,vector[0], vector[-1], [len(vector)-1] * 3, dimensions = 1)
    if not "domain_axis" in pars.keys(): 
        pars["domain_axis"] = "Index"
    processScanPars(scan, pars)
    scan.start()
    try:
        for pos in vector:
            #print "Writing ", pos
            hkl_group.write(pos)
            time.sleep(0.1) #Make sure is busy
            get_motor_group().update()
            get_motor_group().waitReady(-1)
            time.sleep(latency)
            hkl_group.update()
            if scan.before_read: scan.before_read(pos,scan)
            scan.append ([h.take(), k.take(), l.take()], [h.getPosition(), k.getPosition(), l.getPosition()], [readable.read() for readable in readables ])
            if scan.after_read: scan.after_read(scan.currentRecord,scan)
    finally:
        scan.end()    
    return scan.result

def get_constraints():
    constraints={}
    from diffcalc.hkl.you.constraints import valueless_constraints
    all_constraints=hkl.hklcalc.constraints.all
    for name in all_constraints:
        if not hkl.hklcalc.constraints.is_constraint_fixed(name):    
            value = hkl.hklcalc.constraints.get_constraint(name)    
            if name in valueless_constraints:
                constraints[name] = None
            elif value is not None:      
                constraints[name] = value
    return constraints

    
def set_constraints(constraints):
    for name in constraints.keys(): 
        try:
            value = constraints[name]
            if value is None:
                con(name)
            else:
                con(name, value)
        except:
            print sys.exc_info()[1]    
                
def get_limits():
    limits={}
    for name in settings.hardware.get_axes_names():
        axis = {}
        axis["lower_limit"] = settings.hardware.get_lower_limit(name)
        axis["upper_limit"] = settings.hardware.get_upper_limit(name)
        axis["cut"] = settings.hardware.get_cuts()[name]
        limits[name]=axis
    return limits

def set_limits(limits):
    for name in limits.keys():
        try:
            axis = limits[name]
            if axis.get("lower_limit") is not None: setmin (name, axis["lower_limit"])
            if axis.get("upper_limit") is not None: setmax (name, axis["upper_limit"])
            if axis.get("cut") is not None: setcut (name, axis["cut"])
        except:
            print sys.exc_info()[1]   

def get_exp_context():
    context = {}
    try:       
        context["limits"] = get_limits()
    except:
        context["limits"] = None
    try:       
        context["constraints"] = get_constraints()     
    except:
        context["constraints"] = None
    try:       
        context["ub"] = ub.ubcalc._state.name       
    except:
        context["ub"] = None
    return context    

def set_exp_context(context):
    try:       
        if context.get("limits") is not None:
            set_limits(context["limits"])
    except:
        print sys.exc_info()[1]
    try:       
        if context.get("constraints") is not None:
            set_constraints(context["constraints"])
    except:
        print sys.exc_info()[1]
    try:       
        if context.get("ub") is not None:
           loadub(str(context["ub"]))
    except:
        print sys.exc_info()[1]  


EXPERIMENT_CONTEXT_FILE = expand_path("{context}/diff_exp_context.json")
def save_exp_context():
    """
    Saves experiment context (constraints, ub and hw limits)
    """
    try:
        c = get_exp_context()
        with open(EXPERIMENT_CONTEXT_FILE, 'w') as json_file:
            json.dump(c, json_file)
    except:
        print "Cannot save experiment context: ", sys.exc_info()[1]

def load_exp_context():
    """
    Loads experiment context (constraints, ub and hw limits)
    """
    try:
        with open(EXPERIMENT_CONTEXT_FILE) as json_file:
            c = json.load(json_file)     
        set_exp_context(c) 
    except:
        print "Cannot load experiment context: ", sys.exc_info()[1]



###################################################################################################
# Experiment context
###################################################################################################


def test_diffcalc():
    print "Start test"
    energy.move(20.0)
    delta.config.maxSpeed = 50.0
    delta.speed = 50.0
    delta.move(1.0)
    
    #Setup
    setup_diff(sixc, energy)
    setup_axis('gam', 0, 179)
    setup_axis('delta', 0, 179)
    setup_axis('delta', min=0)
    setup_axis('phi', cut=-180.0)
    setup_axis()
    
    #Orientation
    listub()
    # Create a new ub calculation and set lattice parameters
    newub('test')
    setlat('cubic', 1, 1, 1, 90, 90, 90)
    # Add 1st reflection (demonstrating the hardware adapter)
    settings.hardware.wavelength = 1
    c2th([1, 0, 0])                 # energy from hardware
    settings.hardware.position = 0, 60, 0, 30, 0, 0
    addref([1, 0, 0])# energy and position from hardware
    # Add 2nd reflection (this time without the harware adapter)
    c2th([0, 1, 0], 12.39842)
    addref([0, 1, 0], [0, 60, 0, 30, 0, 90], 12.39842)
    # check the state
    showub()
    checkub()
    
    #Constraints
    con('qaz', 90)
    con('a_eq_b')
    con('mu', 0)
    con() 
    
    #Motion
    print  hklci((0., 60., 0., 30., 0., 0.))
    print hklca((1, 0, 0))
    sixc.write([0, 60, 0, 30, 90, 0])
    print "sixc=" , sixc.position
    wavelength.write(1.0)
    print "wavelength = ", wavelength.read()
    lastub()
    setu ([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
    showref()
    swapref(1,2)
    hklwh()
    hklsim([0.0,1.0,1.0])
    hklmv([0.0,1.0,1.0])
    
    #Scans
    lscan(l, [sin], 1.0, 1.5, 0.1)
    ascan([k,l], [sin], [1.0, 1.0], [1.2, 1.3], [0.1, 0.1], zigzag=True, parallel_positioning = False) 
    vector = [[1.0,1.0,1.0], [1.0,1.0,1.1], [1.0,1.0,1.2], [1.0,1.0,1.4]]
    hklscan(vector, [sin, arr], 0.9)