###################################################################################################
#  Global definitions and built-in functions
###################################################################################################

import sys
import time
import math
import os.path
from operator import add, mul, sub, truediv
from time import sleep
from array import array
import jarray

import java.lang.Class as Class
import java.lang.Object as Object
import java.beans.PropertyChangeListener
import java.util.concurrent.Callable
import java.util.List
import java.lang.reflect.Array
import java.lang.Thread
import java.awt.image.BufferedImage as BufferedImage
import java.awt.Color as Color
import java.awt.Dimension as Dimension
import java.awt.Font as Font
import org.python.core.PyArray as PyArray
import org.python.core.PyFunction as PyFunction
import org.python.core.PyGenerator as PyGenerator

import ch.psi.utils.Threading as Threading
import ch.psi.utils.State as State
import ch.psi.utils.Convert as Convert
import ch.psi.utils.Arr as Arr
import ch.psi.utils.Chrono as Chrono
import ch.psi.pshell.core.CommandSource as CommandSource
import ch.psi.pshell.core.ContextAdapter as ContextListener
import ch.psi.pshell.core.Context
import ch.psi.pshell.core.InlineDevice as InlineDevice
import ch.psi.pshell.data.PlotDescriptor as PlotDescriptor
import ch.psi.pshell.data.Table as Table
import ch.psi.pshell.device.Device as Device
import ch.psi.pshell.device.DeviceBase as DeviceBase
import ch.psi.pshell.device.DeviceConfig as DeviceConfig
import ch.psi.pshell.device.RegisterBase as RegisterBase
import ch.psi.pshell.device.ProcessVariableBase as ProcessVariableBase
import ch.psi.pshell.device.ControlledVariableBase as ControlledVariableBase
import ch.psi.pshell.device.PositionerBase as PositionerBase
import ch.psi.pshell.device.MotorBase as MotorBase
import ch.psi.pshell.device.DiscretePositionerBase as DiscretePositionerBase
import ch.psi.pshell.device.MotorGroupBase as MotorGroupBase
import ch.psi.pshell.device.MotorGroupDiscretePositioner as MotorGroupDiscretePositioner
import ch.psi.pshell.device.ReadonlyRegisterBase as ReadonlyRegisterBase
import ch.psi.pshell.device.ReadonlyAsyncRegisterBase as ReadonlyAsyncRegisterBase
import ch.psi.pshell.device.Register as Register
import ch.psi.pshell.device.RegisterCache as RegisterCache
import ch.psi.pshell.device.ReadonlyRegister.ReadonlyRegisterArray as ReadonlyRegisterArray
import ch.psi.pshell.device.ReadonlyRegister.ReadonlyRegisterMatrix as ReadonlyRegisterMatrix
import ch.psi.pshell.device.DummyPositioner as DummyPositioner
import ch.psi.pshell.device.DummyMotor as DummyMotor
import ch.psi.pshell.device.DummyRegister as DummyRegister
import ch.psi.pshell.device.Timestamp as Timestamp
import ch.psi.pshell.device.Interlock as Interlock
import ch.psi.pshell.device.Readable as Readable
import ch.psi.pshell.device.Readable.ReadableArray as ReadableArray
import ch.psi.pshell.device.Readable.ReadableMatrix as ReadableMatrix
import ch.psi.pshell.device.Readable.ReadableCalibratedArray as ReadableCalibratedArray
import ch.psi.pshell.device.Readable.ReadableCalibratedMatrix as ReadableCalibratedMatrix
import ch.psi.pshell.device.ArrayCalibration as ArrayCalibration
import ch.psi.pshell.device.MatrixCalibration as MatrixCalibration
import ch.psi.pshell.device.Writable as Writable
import ch.psi.pshell.device.Writable.WritableArray as WritableArray
import ch.psi.pshell.device.Stoppable as Stoppable
import ch.psi.pshell.device.Averager as Averager
import ch.psi.pshell.device.ArrayAverager as ArrayAverager
import ch.psi.pshell.device.Delta as Delta
import ch.psi.pshell.device.DeviceAdapter as DeviceListener
import ch.psi.pshell.device.ReadbackDeviceAdapter as ReadbackDeviceListener
import ch.psi.pshell.device.MotorAdapter as MotorListener
import ch.psi.pshell.device.MoveMode as MoveMode
import ch.psi.pshell.device.SettlingCondition as SettlingCondition
import ch.psi.pshell.epics.Epics as Epics
import ch.psi.pshell.epics.EpicsScan as EpicsScan
import ch.psi.pshell.epics.ChannelSettlingCondition as ChannelSettlingCondition
import ch.psi.pshell.imaging.Source as Source
import ch.psi.pshell.imaging.SourceBase as SourceBase
import ch.psi.pshell.imaging.DirectSource as DirectSource
import ch.psi.pshell.imaging.RegisterMatrixSource as RegisterMatrixSource
import ch.psi.pshell.imaging.ImageListener as ImageListener
import ch.psi.pshell.plot.LinePlotSeries as LinePlotSeries
import ch.psi.pshell.plot.LinePlotErrorSeries as LinePlotErrorSeries
import ch.psi.pshell.plot.MatrixPlotSeries as MatrixPlotSeries
import ch.psi.pshell.scan.ScanBase as ScanBase
import ch.psi.pshell.scan.LineScan
import ch.psi.pshell.scan.ContinuousScan
import ch.psi.pshell.scan.AreaScan
import ch.psi.pshell.scan.VectorScan
import ch.psi.pshell.scan.ManualScan
import ch.psi.pshell.scan.HardwareScan
import ch.psi.pshell.scan.RegionScan
import ch.psi.pshell.scan.TimeScan
import ch.psi.pshell.scan.MonitorScan
import ch.psi.pshell.scan.BinarySearch
import ch.psi.pshell.scan.HillClimbingSearch
import ch.psi.pshell.scan.ScanResult
import ch.psi.pshell.bs.BsScan
import ch.psi.pshell.bs.Stream as Stream
import ch.psi.pshell.scripting.ViewPreference as Preference
import ch.psi.pshell.scripting.ScriptUtils as ScriptUtils

def get_context():
    return ch.psi.pshell.core.Context.getInstance()

def on_command_started(info):
    pass

def on_command_finished(info):
    pass

###################################################################################################
#Type conversion and checking
###################################################################################################

def to_array(obj, type = 'o'):
    """Convert Python list to Java array.

    Args:
        obj(list): Original data.
        type(str): array type 'b' = byte, 'h' = short, 'i' = int, 'l' = long,  'f' = float, 'd' = double,
                              'c' = char, 'z' = boolean, 's' = String,  'o' = Object
    Returns:
        Java array.

    """
    if type[0] == '[':
        type = type[1:]
    arrayType = ScriptUtils.getType("["+type)

    if obj is None:
        return None
    if isinstance(obj,java.util.List):
        obj = obj.toArray()
        if type != 'o':
            obj = Convert.toPrimitiveArray(obj, ScriptUtils.getType(type))
    if isinstance(obj,PyArray):
        if type != 'o':
            if (Arr.getRank(obj)== 1) and (obj.typecode != type):
                ret = java.lang.reflect.Array.newInstance(ScriptUtils.getType(type), len(obj))
                if type == 's':
                    for i in range(len(obj)): ret[i] = str(obj[i])
                elif type == 'c':
                    for i in range(len(obj)): ret[i] = chr(obj[i])
                else:
                    for i in range(len(obj)): ret[i] = obj[i]
                obj = ret
            if type not in ['o', 's']:
                obj = Convert.toPrimitiveArray(obj)
        return obj
    if is_list(obj):
        if type=='o' or  type== 's':
            ret = java.lang.reflect.Array.newInstance(ScriptUtils.getType(type), len(obj))
            for i in range (len(obj)):
                if is_list(obj[i]):
                    ret[i] = to_array(obj[i],type)
                elif type == 's':
                    ret[i] = str(obj[i])
                else:
                    ret[i] = obj[i]
            return ret

        if len(obj)>0 and is_list(obj[0]):
            if  len(obj[0])>0 and is_list(obj[0][0]):
                    ret = java.lang.reflect.Array.newInstance(arrayType,len(obj),len(obj[0]))
                    for i in range(len(obj)):
                        ret[i]=to_array(obj[i], type)
                    return ret
            else:
                ret = java.lang.reflect.Array.newInstance(arrayType,len(obj))
                for i in range(len(obj)):
                    ret[i]=to_array(obj[i], type)
                return ret
        return jarray.array(obj,type)
    return obj

def to_list(obj):
    """Convert an object into a Python List.

    Args:
        obj(tuple or array or ArrayList): Original data.
    Returns:
        List.

    """
    if obj is None:
        return None
    if isinstance(obj,tuple) or isinstance(obj,java.util.ArrayList) :
        return list(obj)
    #if isinstance(obj,PyArray):
    #    return obj.tolist()
    if not isinstance(obj,list):
        return [obj,]
    return obj

def is_list(obj):
    return isinstance(obj,tuple) or isinstance(obj,list) or isinstance (obj, java.util.ArrayList)

def is_string(obj):
    return (type(obj) is str) or (type(obj) is unicode)


###################################################################################################
#Standard scan commands
###################################################################################################

def on_before_scan_readout(scan, pos):
    try:
        if scan.before_read != None:
            arguments = scan.before_read.func_code.co_argcount
            if arguments == 0:
                scan.before_read()
            elif arguments==1:
                scan.before_read(pos.tolist())
            elif arguments==2:
                scan.before_read(pos.tolist(), scan)
    except AttributeError:
        pass

def on_after_scan_readout(scan, record):
    try:
        if scan.after_read != None:
            arguments = scan.after_read.func_code.co_argcount
            if  arguments == 0:
                scan.after_read()
            elif arguments==1:
                scan.after_read(record)
            elif arguments==2:
                scan.after_read(record, scan)
    except AttributeError:
        pass

def on_before_scan_pass(scan, num_pass):
    try:
        if scan.before_pass != None:
            arguments = scan.before_pass.func_code.co_argcount
            if arguments == 0:
                scan.before_pass()
            elif arguments==1:
                scan.before_pass(num_pass)
            elif arguments==2:
                scan.before_pass(num_pass, scan)
    except AttributeError:
        pass

def on_after_scan_pass(scan, num_pass):
    try:
        if scan.after_pass != None:
            arguments = scan.after_pass.func_code.co_argcount
            if  arguments == 0:
                scan.after_pass()
            elif arguments==1:
                scan.after_pass(num_pass)
            elif arguments==2:
                scan.after_pass(num_pass, scan)
    except AttributeError:
        pass

class LineScan(ch.psi.pshell.scan.LineScan):
    def onBeforeReadout(self, pos):
        on_before_scan_readout(self, pos)

    def onAfterReadout(self, record):
        on_after_scan_readout(self, record)

    def onBeforePass(self, num_pass):
        on_before_scan_pass(self, num_pass)

    def onAfterPass(self, num_pass):
        on_after_scan_pass(self, num_pass)

class ContinuousScan(ch.psi.pshell.scan.ContinuousScan):
    def onBeforeReadout(self, pos):
        on_before_scan_readout(self, pos)

    def onAfterReadout(self, record):
        on_after_scan_readout(self, record)

    def onBeforePass(self, num_pass):
        on_before_scan_pass(self, num_pass)

    def onAfterPass(self, num_pass):
        on_after_scan_pass(self, num_pass)

class AreaScan(ch.psi.pshell.scan.AreaScan):
    def onBeforeReadout(self, pos):
        on_before_scan_readout(self, pos)

    def onAfterReadout(self, record):
        on_after_scan_readout(self, record)

    def onBeforePass(self, num_pass):
        on_before_scan_pass(self, num_pass)

    def onAfterPass(self, num_pass):
        on_after_scan_pass(self, num_pass)

class RegionScan(ch.psi.pshell.scan.RegionScan):
    def onBeforeReadout(self, pos):
        on_before_scan_readout(self, pos)

    def onAfterReadout(self, record):
        on_after_scan_readout(self, record)

    def onBeforePass(self, num_pass):
        on_before_scan_pass(self, num_pass)

    def onAfterPass(self, num_pass):
        on_after_scan_pass(self, num_pass)

class VectorScan(ch.psi.pshell.scan.VectorScan):
    def onBeforeReadout(self, pos):
        on_before_scan_readout(self, pos)

    def onAfterReadout(self, record):
        on_after_scan_readout(self, record)

    def onBeforePass(self, num_pass):
        on_before_scan_pass(self, num_pass)

    def onAfterPass(self, num_pass):
        on_after_scan_pass(self, num_pass)

class ContinuousScan(ch.psi.pshell.scan.ContinuousScan):
    def onBeforeReadout(self, pos):
        on_before_scan_readout(self, pos)

    def onAfterReadout(self, record):
        on_after_scan_readout(self, record)

    def onBeforePass(self, num_pass):
        on_before_scan_pass(self, num_pass)

    def onAfterPass(self, num_pass):
        on_after_scan_pass(self, num_pass)

class TimeScan(ch.psi.pshell.scan.TimeScan):
    def onBeforeReadout(self, pos):
        on_before_scan_readout(self, pos)

    def onAfterReadout(self, record):
        on_after_scan_readout(self, record)

    def onBeforePass(self, num_pass):
        on_before_scan_pass(self, num_pass)

    def onAfterPass(self, num_pass):
        on_after_scan_pass(self, num_pass)

class MonitorScan(ch.psi.pshell.scan.MonitorScan):
    def onBeforeReadout(self, pos):
        on_before_scan_readout(self, pos)

    def onAfterReadout(self, record):
        on_after_scan_readout(self, record)

    def onBeforePass(self, num_pass):
        on_before_scan_pass(self, num_pass)

    def onAfterPass(self, num_pass):
        on_after_scan_pass(self, num_pass)

class BsScan(ch.psi.pshell.bs.BsScan):
    def onBeforeReadout(self, pos):
        on_before_scan_readout(self, pos)

    def onAfterReadout(self, record):
        on_after_scan_readout(self, record)

    def onBeforePass(self, num_pass):
        on_before_scan_pass(self, num_pass)

    def onAfterPass(self, num_pass):
        on_after_scan_pass(self, num_pass)

class ManualScan (ch.psi.pshell.scan.ManualScan):
    def __init__(self, writables, readables, start = None, end = None, steps = None, relative = False, dimensions = None):
        ch.psi.pshell.scan.ManualScan.__init__(self, writables, readables, start, end, steps, relative)
        self._dimensions = dimensions

    def append(self,setpoints, positions, values, timestamps=None):
        ch.psi.pshell.scan.ManualScan.append(self, to_array(setpoints), to_array(positions), to_array(values), None if (timestamps is None) else to_array(timestamps))

    def getDimensions(self):
        if self._dimensions == None:
            return ch.psi.pshell.scan.ManualScan.getDimensions(self)
        else:
            return self._dimensions

class BinarySearch(ch.psi.pshell.scan.BinarySearch):
    def onBeforeReadout(self, pos):
        on_before_scan_readout(self, pos)

    def onAfterReadout(self, record):
        on_after_scan_readout(self, record)

class HillClimbingSearch(ch.psi.pshell.scan.HillClimbingSearch):
    def onBeforeReadout(self, pos):
        on_before_scan_readout(self, pos)

    def onAfterReadout(self, record):
        on_after_scan_readout(self, record)

def processScanPars(scan, pars):
    scan.before_read = pars.pop("before_read",None)
    scan.after_read = pars.pop("after_read",None)
    scan.before_pass = pars.pop("before_pass",None)
    scan.after_pass =  pars.pop("after_pass",None)
    scan.setPlotTitle(pars.pop("title",None))
    scan.setHidden(pars.pop("hidden",False))
    scan.setSettleTimeout (pars.pop("settle_timeout",ScanBase.getScansSettleTimeout()))
    scan.setUseWritableReadback (pars.pop("use_readback",ScanBase.getScansUseWritableReadback()))
    scan.setInitialMove(pars.pop("initial_move",ScanBase.getScansTriggerInitialMove()))
    scan.setParallelPositioning(pars.pop("parallel_positioning",ScanBase.getScansParallelPositioning()))
    scan.setAbortOnReadableError(pars.pop("abort_on_error",ScanBase.getAbortScansOnReadableError()))
    scan.setRestorePosition (pars.pop("restore_position",ScanBase.getRestorePositionOnRelativeScans()))
    scan.setCheckPositions(pars.pop("check_positions",ScanBase.getScansCheckPositions()))


    get_context().setCommandPars(scan, pars)

def lscan(writables, readables, start, end, steps, latency=0.0, relative=False, passes=1, zigzag=False, **pars):
    """Line Scan: positioners change together, linearly from start to end positions.

    Args:
        writables(list of Writable): Positioners set on each step.
        readables(list of Readable): Sensors to be sampled on each step.
        start(list of float): start positions of writables.
        end(list of float): final positions of writables.
        steps(int or float or list of float): number of scan steps (int) or step size (float).
        relative (bool, optional): if true, start and end positions are relative to
            current at start of the scan
        latency(float, optional): settling time for each step before readout, defaults to 0.0.
        passes(int, optional): number of passes
        zigzag(bool, optional): if true writables invert direction on each pass.
        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.
            - settle_timeout(int, optional): timeout for each positioner get to position. Default (-1) waits forever.
            - initial_move (bool, optional): if true (default) perform move to initial position prior to scan start.
            - parallel_positioning (bool, optional): if true (default) all positioners are set in parallel.
            - abort_on_error (bool, optional): if true then aborts scan in sensor failures. Default is false.
            - restore_position (bool, optional): if true (default) then restore initial position after relative scans.
            - check_positions (bool, optional): if true (default) verifies if in correct positions after move finishes.
            - Aditional arguments defined by set_exec_pars.

    Returns:
        ScanResult object.

    """
    latency_ms=int(latency*1000)
    writables=to_list(string_to_obj(writables))
    readables=to_list(string_to_obj(readables))
    start=to_list(start)
    end=to_list(end)
    if type(steps) is float or is_list(steps):
        steps = to_list(steps)
    scan = LineScan(writables,readables, start, end , steps, relative, latency_ms, int(passes), zigzag)
    processScanPars(scan, pars)
    scan.start()
    return scan.getResult()

def vscan(writables, readables, vector, line = False, latency=0.0, relative=False, passes=1, zigzag=False, **pars):
    """Vector Scan: positioners change following values provided in a vector.

    Args:
        writables(list of Writable): Positioners set on each step.
        readables(list of Readable): Sensors to be sampled on each step.
        vector (generator (floats or lists of float)  or list of list of float): positioner values.
        line (bool, optional): if true, processs as line scan (1d)
        relative (bool, optional): if true, start and end positions are relative to current at
            start of the scan
        latency(float, optional): settling time for each step before readout, defaults to 0.0.
        passes(int, optional): number of passes (disregarded if vector is a generator).
        zigzag(bool, optional): if true writables invert direction on each pass (disregarded if vector is a generator).
        pars(keyworded variable length arguments, optional): scan optional named arguments:
            - title(str, optional): plotting window name.
            - 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.
            - settle_timeout(int, optional): timeout for each positioner get to position. Default (-1) waits forever.
            - initial_move (bool, optional): if true (default) perform move to initial position prior to scan start.
            - parallel_positioning (bool, optional): if true (default) all positioners are set in parallel.
            - abort_on_error (bool, optional): if true then aborts scan in sensor failures. Default is false.
            - restore_position (bool, optional): if true (default) then restore initial position after relative scans.
            - check_positions (bool, optional): if true (default) verifies if in correct positions after move finishes.
            - Aditional arguments defined by set_exec_pars.

    Returns:
        ScanResult object.

    """
    latency_ms=int(latency*1000)
    writables=to_list(string_to_obj(writables))
    readables=to_list(string_to_obj(readables))
    if type (vector) == PyGenerator:
        scan = VectorScan(writables,readables, vector, line, relative, latency_ms)
    else:
        if len(vector) == 0:
            vector.append([])
        elif (not is_list(vector[0])) and (not isinstance(vector[0],PyArray)):
            vector = [[x,] for x in vector]
        vector = to_array(vector, 'd')
        scan = VectorScan(writables,readables, vector, line, relative, latency_ms, int(passes), zigzag)
    processScanPars(scan, pars)
    scan.start()
    return scan.getResult()

def ascan(writables, readables, start, end, steps, latency=0.0, relative=False, passes=1, zigzag=False, **pars):
    """Area Scan: multi-dimentional scan, each positioner is a dimention.

    Args:
        writables(list of Writable): Positioners set on each step.
        readables(list of Readable): Sensors to be sampled on each step.
        start(list of float): start positions of writables.
        end(list of float): final positions of writables.
        steps(list of int or list of float): number of scan steps (int) or step size (float).
        latency(float, optional): settling time for each step before readout, defaults to 0.0.
        relative (bool, optional): if true, start and end positions are relative to current at
            start of the scan
        passes(int, optional): number of passes
        zigzag (bool, optional): if true writables invert direction on each row.
        pars(keyworded variable length arguments, optional): scan optional named arguments:
            - title(str, optional): plotting window name.
            - 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.
            - settle_timeout(int, optional): timeout for each positioner get to position. Default (-1) waits forever.
            - initial_move (bool, optional): if true (default) perform move to initial position prior to scan start.
            - parallel_positioning (bool, optional): if true (default) all positioners are set in parallel.
            - abort_on_error (bool, optional): if true then aborts scan in sensor failures. Default is false.
            - restore_position (bool, optional): if true (default) then restore initial position after relative scans.
            - check_positions (bool, optional): if true (default) verifies if in correct positions after move finishes.
            - Aditional arguments defined by set_exec_pars.

    Returns:
        ScanResult object.

    """
    latency_ms=int(latency*1000)
    writables=to_list(string_to_obj(writables))
    readables=to_list(string_to_obj(readables))
    start=to_list(start)
    end=to_list(end)
    if is_list(steps):
        steps = to_list(steps)
    scan = AreaScan(writables,readables, start, end , steps, relative, latency_ms, int(passes), zigzag)
    processScanPars(scan, pars)
    scan.start()
    return scan.getResult()


def rscan(writable, readables, regions, latency=0.0, relative=False, passes=1, zigzag=False, **pars):
    """Region Scan: positioner scanned linearly, from start to end positions, in multiple regions.

    Args:
        writable(Writable): Positioner set on each step, for each region.
        readables(list of Readable): Sensors to be sampled on each step.
        regions (list of tuples (float,float, int)   or (float,float, float)): each tuple define a scan region
                                (start, stop, steps) or (start, stop, step_size)
        relative (bool, optional): if true, start and end positions are relative to
            current at start of the scan
        latency(float, optional): settling time for each step before readout, defaults to 0.0.
        passes(int, optional): number of passes
        zigzag(bool, optional): if true writable invert direction on each pass.
        pars(keyworded variable length arguments, optional): scan optional named arguments:
            - title(str, optional): plotting window name.
            - 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.
            - settle_timeout(int, optional): timeout for each positioner get to position. Default (-1) waits forever.
            - initial_move (bool, optional): if true (default) perform move to initial position prior to scan start.
            - parallel_positioning (bool, optional): if true (default) all positioners are set in parallel.
            - abort_on_error (bool, optional): if true then aborts scan in sensor failures. Default is false.
            - restore_position (bool, optional): if true (default) then restore initial position after relative scans.
            - check_positions (bool, optional): if true (default) verifies if in correct positions after move finishes.
            - Aditional arguments defined by set_exec_pars.

    Returns:
        ScanResult object.

    """
    start=[]
    end=[]
    steps=[]
    for region in regions:
        start.append(region[0])
        end.append(region[1])
        steps.append(region[2])
    latency_ms=int(latency*1000)
    writable=string_to_obj(writable)
    readables=to_list(string_to_obj(readables))
    start=to_list(start)
    end=to_list(end)
    steps = to_list(steps)
    scan = RegionScan(writable,readables, start, end , steps, relative, latency_ms, int(passes), zigzag)
    processScanPars(scan, pars)
    scan.start()
    return scan.getResult()

def cscan(writables, readables, start, end, steps, latency=0.0, time=None, relative=False, passes=1, zigzag=False, **pars):
    """Continuous Scan: positioner change continuously from start to end position and readables are sampled on the fly.

    Args:
        writable(Speedable or list of Motor): A positioner with a  getSpeed method or
                    a list of motors.
        readables(list of Readable): Sensors to be sampled on each step.
        start(float or list of float): start positions of writables.
        end(float or list of float): final positions of writabless.
        steps(int or float or list of float): number of scan steps (int) or step size (float).
        latency(float, optional): sleep time in each step before readout, defaults to 0.0.
        time (float, seconds): if not None then writables is Motor array and speeds are
                    set according to time.
        relative (bool, optional): if true, start and end positions are relative to
            current at start of the scan
        passes(int, optional): number of passes
        pars(keyworded variable length arguments, optional): scan optional named arguments:
            - title(str, optional): plotting window name.
            - 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.
            - abort_on_error (bool, optional): if true then aborts scan in sensor failures. Default is false.
            - restore_position (bool, optional): if true (default) then restore initial position after relative scans.
            - Aditional arguments defined by set_exec_pars.

    Returns:
        ScanResult object.

    """
    latency_ms=int(latency*1000)
    readables=to_list(string_to_obj(readables))
    writables=to_list(string_to_obj(writables))
    start=to_list(start)
    end=to_list(end)
    #A single Writable with fixed speed
    if time is None:
        if is_list(steps): steps=steps[0]
        scan = ContinuousScan(writables[0],readables, start[0], end[0] , steps, relative, latency_ms, int(passes), zigzag)
    #A set of Writables with speed configurable
    else:
        if type(steps) is float or is_list(steps):
            steps = to_list(steps)
        scan = ContinuousScan(writables,readables, start, end , steps, time, relative, latency_ms, int(passes), zigzag)

    processScanPars(scan, pars)
    scan.start()
    return scan.getResult()

def hscan(config, writable, readables, start, end, steps, passes=1, zigzag=False, **pars):
    """Hardware Scan: values sampled by external hardware and received asynchronously.

    Args:
        config(dict): Configuration of the hardware scan. The "class" key provides the implementation class.
                      Other keys are implementation specific.
        writable(Writable): A positioner appropriated to the hardware scan type.
        readables(list of Readable): Sensors appropriated to the hardware scan type.
        start(float): start positions of writable.
        end(float): final positions of writables.
        steps(int or float): number of scan steps (int) or step size (float).
        passes(int, optional): number of passes
        pars(keyworded variable length arguments, optional): scan optional named arguments:
            - title(str, optional): plotting window name.
            - 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.
            - abort_on_error (bool, optional): if true then aborts scan in sensor failures. Default is false.
            - Aditional arguments defined by set_exec_pars.

    Returns:
        ScanResult object.

    """
    cls = Class.forName(config["class"])
    class HardwareScan(cls):
        def __init__(self, config, writable, readables, start, end, stepSize, passes, zigzag):
            cls.__init__(self, config, writable, readables, start, end, stepSize, passes, zigzag)
        def onAfterReadout(self, record):
            on_after_scan_readout(self, record)
        def onBeforePass(self, num_pass):
            on_before_scan_pass(self, num_pass)
        def onAfterPass(self, num_pass):
            on_after_scan_pass(self, num_pass)

    readables=to_list(string_to_obj(readables))
    scan = HardwareScan(config, writable,readables, start, end , steps, int(passes), zigzag)
    processScanPars(scan, pars)
    scan.start()
    return scan.getResult()

def bscan(stream, records, timeout = None, passes=1, **pars):
    """BS Scan: records all values in a beam synchronous stream.

    Args:
        stream(Stream): stream object or list of chanel names to build stream from
        records(int): number of records to store
        timeout(float, optional): maximum scan time in seconds.
        passes(int, optional): number of passes
        pars(keyworded variable length arguments, optional): scan optional named arguments:
            - title(str, optional): plotting window name.
            - 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.

    """
    timeout_ms=int(timeout*1000) if ((timeout is not None) and (timeout>=0)) else -1
    if not is_list(stream):
        stream=string_to_obj(stream)
    scan = BsScan(stream,int(records), timeout_ms, int(passes))
    processScanPars(scan, pars)
    scan.start()
    return scan.getResult()

def tscan(readables, points, interval, passes=1, **pars):
    """Time Scan: sensors are sampled in fixed time intervals.

    Args:
        readables(list of Readable): Sensors to be sampled on each step.
        points(int): number of samples.
        interval(float): time interval between readouts. Minimum temporization is 0.001s
        passes(int, optional): number of passes
        pars(keyworded variable length arguments, optional): scan optional named arguments:
            - title(str, optional): plotting window name.
            - 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.
            - abort_on_error (bool, optional): if true then aborts scan in sensor failures. Default is false.
            - Aditional arguments defined by set_exec_pars.

    Returns:
        ScanResult object.

    """
    interval= max(interval, 0.001)   #Minimum temporization is 1ms
    interval_ms=int(interval*1000)
    readables=to_list(string_to_obj(readables))
    scan = TimeScan(readables, points, interval_ms, int(passes))
    processScanPars(scan, pars)
    scan.start()
    return scan.getResult()

def mscan(trigger, readables, points, timeout = None, async=True, take_initial=False, passes=1, **pars):
    """Monitor Scan: sensors are sampled when received change event of the trigger device.

    Args:
        trigger(Device or list of Device): Source of the sampling triggering.
        readables(list of Readable): Sensors to be sampled on each step.
                                     If  trigger has cache and is included in readables, it is not read
                                     for each step, but the change event value is used.
        points(int): number of samples.
        timeout(float, optional): maximum scan time in seconds.
        async(bool, optional): if True then records are sampled and stored on event change callback. Enforce
                               reading only cached values of sensors.
                               If False, the scan execution loop waits for trigger cache update. Do not make
                               cache only access, but may loose change events.
        take_initial(bool, optional): if True include current values as first record (before first trigger).
        passes(int, optional): number of passes
        pars(keyworded variable length arguments, optional): scan optional named arguments:
            - title(str, optional): plotting window name.
            - 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.
            - abort_on_error (bool, optional): if true then aborts scan in sensor failures. Default is false.
            - Aditional arguments defined by set_exec_pars.

    Returns:
        ScanResult object.

    """
    timeout_ms=int(timeout*1000) if ((timeout is not None) and (timeout>=0)) else -1
    trigger = string_to_obj(trigger)
    readables=to_list(string_to_obj(readables))
    scan = MonitorScan(trigger, readables, points, timeout_ms, async, take_initial, int(passes))
    processScanPars(scan, pars)
    scan.start()
    return scan.getResult()

def escan(name, **pars):
    """Epics Scan: execute an Epics Scan Record.

    Args:
        name(str): Name of scan record.
        title(str, optional): plotting window name.
        pars(keyworded variable length arguments, optional): scan optional named arguments:
            - title(str, optional): plotting window name.
            - Aditional arguments defined by set_exec_pars.

    Returns:
        ScanResult object.

    """
    scan = EpicsScan(name)
    processScanPars(scan, pars)
    scan.start()
    return scan.getResult()


def bsearch(writables, readable, start, end, steps, maximum = True, strategy = "Normal", latency=0.0, relative=False, **pars):
    """Binary search: searches writables in a binary search fashion to find a local maximum for the readable.

    Args:
        writables(list of Writable): Positioners set on each step.
        readable(Readable): Sensor to be sampled.
        start(list of float): start positions of writables.
        end(list of float): final positions of writables.
        steps(float or list of float): resolution of search for each writable.
        maximum (bool , optional): if True (default) search maximum, otherwise minimum.
        strategy (str , optional): "Normal": starts search midway to scan range and advance in the best direction.
                                             Uses orthogonal neighborhood (4-neighborhood for 2d)
                                   "Boundary": starts search on scan range.
                                   "FullNeighborhood": Uses complete neighborhood (8-neighborhood for 2d)

        latency(float, optional): settling time for each step before readout, defaults to 0.0.
        relative (bool, optional): if true, start and end positions are relative to current at
            start of the scan
        pars(keyworded variable length arguments, optional): scan optional named arguments:
            - title(str, optional): plotting window name.
            - 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.
            - settle_timeout(int, optional): timeout for each positioner get to position. Default (-1) waits forever.
            - parallel_positioning (bool, optional): if true (default) all positioners are set in parallel.
            - abort_on_error (bool, optional): if true then aborts scan in sensor failures. Default is false.
            - restore_position (bool, optional): if true (default) then restore initial position after relative scans.
            - check_positions (bool, optional): if true (default) verifies if in correct positions after move finishes.
            - Aditional arguments defined by set_exec_pars.

    Returns:
        SearchResult object.

    """
    latency_ms=int(latency*1000)
    writables=to_list(string_to_obj(writables))
    readable=string_to_obj(readable)
    start=to_list(start)
    end=to_list(end)
    steps = to_list(steps)
    strategy = BinarySearch.Strategy.valueOf(strategy)
    scan = BinarySearch(writables,readable, start, end , steps, maximum, strategy, relative, latency_ms)
    processScanPars(scan, pars)
    scan.start()
    return scan.getResult()

def hsearch(writables, readable, range_min, range_max, initial_step, resolution, filter=1, maximum=True, latency=0.0, relative=False, **pars):
    """Hill Climbing search: searches writables in decreasing steps to find a local maximum for the readable.
    Args:
        writables(list of Writable): Positioners set on each step.
        readable(Readable): Sensor to be sampled.
        range_min(list of float): minimum positions of writables.
        range_max(list of float): maximum positions of writables.
        initial_step(float or list of float):initial step size for for each writable.
        resolution(float or list of float): resolution of search for each writable (minimum step size).
        filter(int): number of aditional steps to filter noise
        maximum (bool , optional): if True (default) search maximum, otherwise minimum.
        latency(float, optional): settling time for each step before readout, defaults to 0.0.
        relative (bool, optional): if true, range_min and range_max positions are relative to current at
            start of the scan
        pars(keyworded variable length arguments, optional): scan optional named arguments:
            - title(str, optional): plotting window name.
            - 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.
            - settle_timeout(int, optional): timeout for each positioner get to position. Default (-1) waits forever.
            - parallel_positioning (bool, optional): if true (default) all positioners are set in parallel.
            - abort_on_error (bool, optional): if true then aborts scan in sensor failures. Default is false.
            - restore_position (bool, optional): if true (default) then restore initial position after relative scans.
            - check_positions (bool, optional): if true (default) verifies if in correct positions after move finishes.
            - Aditional arguments defined by set_exec_pars.

    Returns:
        SearchResult object.

    """
    latency_ms=int(latency*1000)
    writables=to_list(string_to_obj(writables))
    readable=string_to_obj(readable)
    range_min=to_list(range_min)
    range_max=to_list(range_max)
    initial_step = to_list(initial_step)
    resolution = to_list(resolution)
    scan = HillClimbingSearch(writables,readable, range_min, range_max , initial_step, resolution, filter, maximum, relative, latency_ms)
    processScanPars(scan, pars)
    scan.start()
    return scan.getResult()


###################################################################################################
#Data plotting
###################################################################################################

def plot(data, name = None, xdata = None, ydata=None, title=None):
    """Request one or multiple plots of user data (1d, 2d or 3d).

    Args:
        data: array or list of values. For multiple plots, array of arrays or lists of values.
        name(str or list of str, optional): plot name or list of names (if multiple plots).
        xdata: array or list of values. For multiple plots, array of arrays or lists of values.
        ydata: array or list of values. For multiple plots, array of arrays or lists of values.
        title(str, optional): plotting window name.

    Returns:
        ArrayList of Plot objects.

    """
    if isinstance(data, ch.psi.pshell.data.Table):
        if is_list(xdata):
            xdata = to_array(xdata, 'd')
        return get_context().plot(data,xdata,name,title)

    if isinstance(data, ch.psi.pshell.scan.ScanResult):
        return get_context().plot(data,title)

    if (name is not None) and is_list(name):
        if len(name)==0:
            name=None;
        else:
            if (data==None):
                data = []
                for n in name:
                    data.append([])
        plots = java.lang.reflect.Array.newInstance(Class.forName("ch.psi.pshell.data.PlotDescriptor"), len(data))
        for i in range (len(data)):
            plotName = None if (name is None) else name[i]
            x = xdata
            if is_list(x) and len(x)>0 and (is_list(x[i]) or isinstance(x[i] , java.util.List) or isinstance(x[i],PyArray)):
                x = x[i]
            y = ydata
            if is_list(y) and len(y)>0 and (is_list(y[i]) or isinstance(y[i] , java.util.List) or isinstance(y[i],PyArray)):
                y = y[i]
            plots[i] =  PlotDescriptor(plotName , to_array(data[i], 'd'), to_array(x, 'd'), to_array(y, 'd'))
        return get_context().plot(plots,title)
    else:
        plot = PlotDescriptor(name, to_array(data, 'd'), to_array(xdata, 'd'), to_array(ydata, 'd'))
        return get_context().plot(plot,title)

def get_plots(title=None):
    """Return all current plots in the plotting window given by 'title'.

    Args:
        title(str, optional): plotting window name.

    Returns:
        ArrayList of Plot objects.

    """
    return get_context().getPlots(title)

def get_plot_snapshots(title = None, file_type = "png", size = None, temp_path = get_context().setup.getContextPath()):
    """Returns list with file names of plots snapshots from a plotting context.

    Args:
        title(str, optional): plotting window name.
        file_type(str, optional): "png", "jpg", "bmp" or "gif"
        size(array, optional): [width, height]
        temp_path(str, optional): path where the files will be generated.

    Returns:
        list of strings

    """
    time.sleep(0.1) #Give some time to plot to be finished - it is not sync  with acquisition
    ret = []
    if size != None:
        size = Dimension(size[0], size[1])
    plots = get_plots(title)
    for i in range(len(plots)):
        p = plots[i]
        name = p.getTitle()
        if name is None or name == "":
            name = str(i)
        file_name = os.path.abspath(temp_path + "/" + name + "." + file_type)
        p.saveSnapshot(file_name , file_type, size)
        ret.append(file_name)
    return ret


###################################################################################################
#Data access functions
###################################################################################################

def load_data(path, index=0, shape=None):
    """Read data from the current persistence context or from data files.

    Args:
        path(str): Path to group or dataset relative to the persistence context root.
                   If in the format 'root|path' then read from path given by 'root'.
        index(int or listr, optional):
                if integer, data depth (used for 3D datasets returning a 2d matrix)
                If a list, specifies the full coordinate for multidimensional datasets.
        shape(list, optional): only valid if index is a list, provides the shape of the data array.
                In this case return a flattened a one-dimensional array.

    Returns:
        Data array

    """
    if index is not None and is_list(index):
        slice = get_context().dataManager.getData(path, index, shape)
    else:
        slice = get_context().dataManager.getData(path, index)
    return slice.sliceData

def get_attributes(path):
    """Get the attributes from the current persistence context or from data files.

    Args:
        path(str): Path to group or dataset relative to the current persistence context root.
                   If in the format 'root|path' then read from path given by 'root'.
    Returns:
        Dictionary

    """
    return get_context().dataManager.getAttributes(path)

def save_dataset(path, data, type='d', unsigned=False, features=None):
    """Save data into a dataset within the current persistence context.

    Args:
        path(str): Path to dataset relative to the current persistence context root.
        type(str, optional): array type 'b' = byte, 'h' = short, 'i' = int, 'l' = long,  'f' = float,
                              'd' = double, 'c' = char, 's' = String,  'o' = Object
                   default: 'd' (convert data to array of doubles)
        data (array or list): data to be saved
        unsigned(boolean, optional): create a dataset of unsigned type.
        features(dictionary, optional): See create_dataset.

    Returns:
        Dictionary

    """
    data = to_array(data, type)
    get_context().dataManager.setDataset(path, data, unsigned, features)

def create_group(path):
    """Create an empty dataset within the current persistence context.

    Args:
        path(str): Path to group relative to the current persistence context root.
    Returns:
        None

    """
    get_context().dataManager.createGroup(path)

def create_dataset(path, type, unsigned=False, dimensions=None, features=None):
    """Create an empty dataset within the current persistence context.

    Args:
        path(str): Path to dataset relative to the current persistence context root.
        type(str): array type 'b' = byte, 'h' = short, 'i' = int, 'l' = long,  'f' = float,
                              'd' = double, 'c' = char, 's' = String,  'o' = Object
        unsigned(boolean, optional): create a dataset of unsigned type.
        dimensions(tuple of int, optional): a 0 value means variable length in that dimension.
        features(dictionary, optional): storage features for the dataset, format specific.
            Keys for HDF5: "layout": "compact", "contiguous" or "chunked"
                           "compression": True, "max" or deflation level from 1 to 9
                           "shuffle": Byte shuffle before compressing.
                           "chunk": tuple, setting the chunk size
            Default: No compression, contiguous for fixed size arrays, chunked for variable size, compact for scalars.
    Returns:
        None

    """
    get_context().dataManager.createDataset(path, ScriptUtils.getType(type), unsigned, dimensions, features)

def create_table(path, names, types=None, lengths=None, features=None):
    """Create an empty table (dataset of compound type) within the current persistence context.

    Args:
        path(str): Path to dataset relative to the current persistence context root.
        names(list of strings): name of each column
        types(array of str):  'b' = byte, 'h' = short, 'i' = int, 'l' = long,  'f' = float,
                              'd' = double, 'c' = char, 's' = String,  'o' = Object
                              Note:A '[' prefix on type name indicates an array type.
        lengths(list of int): the array length for each columns(0 for scalar types).
        features(dictionary, optional): See create_dataset.
    Returns:
        None

    """
    type_classes = []
    if (types is not None):
        for i in range (len(types)):
            type_classes.append(ScriptUtils.getType(types[i]))
    get_context().dataManager.createDataset(path, names, type_classes, lengths, features)

def append_dataset(path, data, index=None, type='d', shape=None):
    """Append data to dataset.

    Args:
        path(str): Path to dataset relative to the current persistence context root.
        data(number or array or list): name of each column.
        index(int or list, optional): if set then add the data in a specific position in the dataset.
                If integer is the index in an array (data must be 1 order lower than dataset)
                If a list, specifies the full coordinate for multidimensional datasets.
        type(str, optional): array type 'b' = byte, 'h' = short, 'i' = int, 'l' = long,  'f' = float,
                              'd' = double, 'c' = char, 's' = String,  'o' = Object
                   default: 'd' (convert data to array of doubles)
        shape(list, optional): only valid if index is a list, provides the shape of the data array.
                In this case data must be a flattened one-dimensional array.
    Returns:
        None

    """
    data = to_array(data, type)
    if index is None:
        get_context().dataManager.appendItem(path, data)
    else:
        if is_list(index):
            if shape is None:
                shape = [len(index)]
            get_context().dataManager.setItem(path, data, index, shape)
        else:
            get_context().dataManager.setItem(path, data, index)

def append_table(path, data):
    """Append data to a table (dataset of compound type)

    Args:
        path(str): Path to dataset relative to the current persistence context root.
        data(list): List of valus for each column of the table. Array types can be expressed as lists.
    Returns:
        None

    """
    if is_list(data):
        arr = java.lang.reflect.Array.newInstance(Class.forName("java.lang.Object"),len(data))
        for i in range (len(data)):
            if is_list(data[i]):
                arr[i] = to_array(data[i], 'd')
            else:
                arr[i] = data[i]
        data=arr
    get_context().dataManager.appendItem(path, data)

def flush_data():
    """Flush all data files immediately.

    Args:
        None
    Returns:
        None
    """
    get_context().dataManager.flush()

def set_attribute(path, name, value, unsigned = False):
    """Set an attribute to a group or dataset.

    Args:
        path(str): Path to dataset relative to the current persistence context root.
        name(str): name of the atttribute
        value(Object): the attribute value
        unsigned(bool, optional):  if applies, indicate if  value is unsigned.
    Returns:
        None
    """
    if is_list(value):
        value = Convert.toStringArray(to_array(value))
    get_context().dataManager.setAttribute(path, name, value, unsigned)

def log(log, data_file=None):
    """Writes a log to the system log and data context - if there is an ongoing scan or script execution.

    Args:
         log(str): Log string.
         data_file(bool, optional): if true logs to the data file, in addiction to the system logger.
                                    If None(default) appends to data file only if it has been created.

    Returns:
        None
    """
    get_context().scriptingLog(str(log))
    if data_file is None:
        data_file = get_exec_pars().open
    if data_file:
        try:
            get_context().dataManager.appendLog(str(log))
        except:
            #Do not generate exception if cannot write to data file
            pass

def set_exec_pars(**args):
    """  Configures the script execution parameters, overriding the system configuration.

    Args:
      args(optional arguments):
        name(str): value of the {name} tag. Default is the running script name.
        type(str): value of the {type} tag. Default is empty.
                             This field can be used to store data in  sub-folders of standard location.
        path(str):  If defined provides the full path name for data output root (overriding config))
                             The tag {data} can be used to enter a path relative to the standard data folder.
        layout(str): Overrides default data layout.
        format(str): Overrides default data format.
        depth_dim(int): dimension of 2d-matrixes in 3d datasets.
        save(bool): Overrides the configuration option to auto save scan data.
        flush(bool): Overrides the configuration option to flush file on each record.
        keep(bool): Overrides the configuration option to release scan records.
                                    If false disable accumulation of scan records to scan result.
        preserve(bool): Overrides the configuration option to preserve device types.
                                  If false all values are converted to double.
        compression(obj): True for enabling default compression, int for specifying deflation level.                                    
                                    Device or list of devices for specifying devices to be compressed.
        shuffle(obj): True for enabling shuffling before compression. 
                                Device or list of devices for specifying devices to be shuffled.
        contiguous(obj): True for setting contiguous datasets for all devices.
                                   Device or list of devices for specifying device datasets to be contiguous.
        open(bool): If true opens data output root (instead of only doing in the first data access call)
                              If false closes output root, if open.
        reset(bool): If true reset the scan counter - the {count} tag and set the timestamp to now.
        group(str): Overrides default layout group name for scans
        tag(str): Overrides default tag for scan names (affecting group or dataset name, according to layout)
        then, then_success, then_exception(str): Sets statement to be executed on the completion of current. 
        defaults(bool): If true restore the original execution parameters.

        Graphical preferences can also be set. Keys are equal to lowercase of Preference enum:
        "plot_disabled", "plot_layout", "table_disabled", "enabled_plots", "plot_types", "print_scan", "auto_range",
        "manual_range","manual_range_y", "domain_axis", "status". See set_preference for more information.

        Shortcut entries: "line_plots": list of devices with enforced line plots.
                          "range": "none", "auto", [min_x, max_x]  or [min_x, max_x, min_y, max_y] 
                          "display": if false disables scan data plotting and printing.
    """
    get_context().setExecutionPars(args)

def get_exec_pars():
    """ Returns script execution parameters.

    Returns:
        ExecutionParameters object. Fields:
            name (str): execution name - {name} tag.
            type (str): execution type - {type} tag.
            path (str): output data root.
            open (bool): true if the output data root has been opened.
            layout (str): data output layout. If None then using the configuration.
            save (bool): auto save scan data option.
            flush (bool): flush file on each record.
            index (int): current scan index.
            group (str): data group currently used for scan data storage.
                         if no ongoing scan return "/" if within a script, or else None if a console command.
            scanPath (str): dataset or group corresponding to current scan.
            scan (Scan): reference to current scan, if any
            source (CommandSource): return the source of the script or command.
            background (bool): return False if executing in main interpreter thread .
            aborted (bool): True if execution has been aborted
    """
    return get_context().getExecutionPars()


###################################################################################################
#EPICS channel access
###################################################################################################

def _adjust_channel_value(value, var_type=None):
    if (value is None):
        return value
    if (var_type is not None):
        if is_list(value):
            var_type = var_type.replace(',','').replace('[','')
            ret = []
            for item in value:
               ret.append(_adjust_channel_value(item), var_type)
            value = ret
        else:
            var_type = var_type.lower()
            if var_type=='b':
                value = byte(value)
            elif var_type=='i':
                value = short(value)
            elif var_type=='l':
                value = int(value)
            elif var_type=='f':
                value = float(value)
            elif var_type=='d':
                value = float(value)
            elif var_type=='s':
                value = str(value)

    if isinstance(value,tuple):
        value =  list(value)
    if isinstance(value,list):
        list_type = type(value[0])
        array_types = {
            int: "i",
            long: "l",
            float:"d",
            str:Class.forName("java.lang.String"),
        }
        array_type = array_types.get(type(value[0]),'d')
        array = PyArray(array_type)
        array.fromlist(value)
        value=array
    return value

def caget(name, type=None, size=None, meta = False ):
    """Reads an Epics PV.

    Args:
        name(str): PV name
        type(str, optional): type of PV. By default gets the PV standard field type.
            Scalar values: 'b', 'i', 'l', 'd', 's'.
            Array: values: '[b', '[i,', '[l', '[d', '[s'.
        size (int, optional): for arrays, number of elements to be read. Default read all.
        meta (bool, optional): if true gets channel value and metadata (timestamp, severity).

    Returns:
        PV value if meta is false, otherwise a dictionary containing PV value and metadata

    """
    if meta:
        return Epics.getMeta(name, Epics.getChannelType(type), size)
    return Epics.get(name, Epics.getChannelType(type), size)

def cawait(name, value, timeout=None, comparator=None, type=None, size=None):
    """Wait for a PV to have a given value.

    Args:
        name(str): PV name
        value (obj): value to compare to
        timeout(float, optional): time in seconds to wait. If None, waits forever.
        comparator(java.util.Comparator or float, optional): if None waits for equality.
            If a numeric value is provided, waits for channel to be in range.
        type(str, optional): type of PV. By default gets the PV standard field type.
            Scalar values: 'b', 'i', 'l', 'd', 's'.
            Array: values: '[b', '[i,', '[l', '[d', '[s'.
         size (int, optional): for arrays, number of elements to be read. Default read all.

    Returns:
        None
    """
    if (timeout is not None):
        timeout = int(timeout*1000)
    value = _adjust_channel_value(value)
    Epics.waitValue(name, value, comparator, timeout, Epics.getChannelType(type), size)

def caput(name, value, timeout = None):
    """Writes to an Epics PV.

    Args:
        name(str): PV name
        value(scalar, string or array): new PV value.
        timeout(int, optional): timeout in seconds to the write. If None waits forever to completion.

    Returns:
        None
    """
    value=_adjust_channel_value(value)
    if (timeout is not None):
        timeout = int(timeout*1000)
    return Epics.put(name, value, timeout)

def caputq(name, value):
    """Writes to an Epics PV and does not wait.

    Args:
        name(str): PV name
        value(scalar, string or array): new PV value.

    Returns:
        None
    """
    value=_adjust_channel_value(value)
    return Epics.putq(name, value)

def camon(name, type=None, size=None, wait = sys.maxint):
    """Install a monitor to an Epics PV and print value changes.

    Args:
        name(str): PV name
        type(str, optional): type of PV. By default gets the PV standard field type.
            Scalar values: 'b', 'i', 'l', 'd', 's'.
            Array: values: '[b', '[i,', '[l', '[d', '[s'.
        size (int, optional): for arrays, number of elements to be read. Default read all.
        wait (int, optional): blocking time for this function. By default blocks forever.
    Returns:
        None

    """
    val = lambda x: x.tolist() if isinstance(x,PyArray) else x

    class MonitorListener(java.beans.PropertyChangeListener):
        def propertyChange(self, pce):
            print val(pce.getNewValue())

    channel = create_channel(name, type, size)
    print val(channel.getValue())
    channel.setMonitored(True)
    channel.addPropertyChangeListener(MonitorListener())

    try:
        time.sleep(wait)
    finally:
        Epics.closeChannel(channel)

def create_channel_device(channel_name, type=None, size=None, device_name=None):
    """Create a device from an EPICS PV.

    Args:
        channel_name(str): PV name
        type(str, optional): type of PV. By default gets the PV standard field type.
            Scalar values: 'b', 'i', 'l', 'd', 's'.
            Array: values: '[b', '[i,', '[l', '[d', '[s'.
        size (int, optional): for arrays, number of elements to be read. Default read all.
        device_name (str, optional): device name (if  different from hannel_name.
    Returns:
        None

    """
    dev = Epics.newChannelDevice(channel_name if (device_name is None) else device_name , channel_name, Epics.getChannelType(type))
    if get_context().isSimulation():
        dev.setSimulated()
    dev.initialize()
    if (size is not None):
        dev.setSize(size)
    return dev

def create_channel(name, type=None, size=None):
    return Epics.newChannel(name, Epics.getChannelType(type), size)

class Channel(java.beans.PropertyChangeListener, Writable, Readable):
    def __init__(self, channel_name, type = None, size = None, callback=None, alias = None):
        """ Create an object that encapsulates an Epics PV connection.
        Args:
            channel_name(str):name of the channel
            type(str, optional): type of PV. By default gets the PV standard field type.
                Scalar values: 'b', 'i', 'l', 'd', 's'.
                Array: values: '[b', '[i,', '[l', '[d', '[s'.
            size(int, optional): the size of the channel
            callback(function, optional): The monitor callback.
            alias(str): name to be used on scans.
        """
        self.channel = create_channel(channel_name, type, size)
        self.callback = callback
        if alias is not None:
            set_device_alias(self, alias)
        else:
            set_device_alias(self, channel_name)

    def get_name(self):
        """Return the name of the channel.
        """
        return self.channel.name

    def get_size(self):
        """Return the size of the channel.
        """
        return self.channel.size

    def set_size(self, size):
        """Set the size of the channel.
        """
        self.channel.size = size

    def is_connected(self):
        """Return True if channel is connected.
        """
        return self.channel.connected

    def is_monitored(self):
        """Return True if channel is monitored
        """
        return self.channel.monitored

    def set_monitored(self, value):
        """Set a channel monitor to trigger the callback function defined in the constructor.
        """
        self.channel.monitored = value
        if (value):
            self.channel.addPropertyChangeListener(self)
        else:
            self.channel.removePropertyChangeListener(self)

    def propertyChange(self, pce):
        if pce.getPropertyName() == "value":
            if self.callback is not None:
                self.callback(pce.getNewValue())

    def put(self, value, timeout=None):
        """Write to channel and wait value change. In the case of a timeout throws a TimeoutException.
        Args:
            value(obj): value to be written
            timeout(float, optional): timeout in seconds. If none waits forever.
        """
        if (timeout==None):
            self.channel.setValue(value)
        else:
            self.channel.setValueAsync(value).get(int(timeout*1000), java.util.concurrent.TimeUnit.MILLISECONDS);

    def putq(self, value):
        """Write to channel and don't wait.
        """
        self.channel.setValueNoWait(value)

    def get(self, force = False):
        """Get channel value.
        """
        return self.channel.getValue(force)

    def wait_for_value(self, value, timeout=None, comparator=None):
        """Wait channel to reach a value, using a given comparator. In the case of a timeout throws a TimeoutException.
        Args:
            value(obj): value to be verified.
            timeout(float, optional): timeout in seconds. If None waits forever.
            comparator (java.util.Comparator, optional). If None, uses Object.equals.
        """
        if comparator is None:
            if timeout is None:
                self.channel.waitForValue(value)
            else:
                self.channel.waitForValue(value, int(timeout*1000))
        else:
            if timeout is None:
                self.channel.waitForValue(value, comparator)
            else:
                self.channel.waitForValue(value, comparator, int(timeout*1000))

    def close(self):
        """Close the channel.
        """
        self.channel.destroy()

    #Writable interface
    def write(self, value):
        self.put(value)

    #Readable interface
    def read(self):
        return self.get()


###################################################################################################
#Concurrent execution
###################################################################################################

class Callable(java.util.concurrent.Callable):
    def __init__(self, method, *args):
        self.method = method
        self.args = args
        self.thread = java.lang.Thread.currentThread()
    def call(self):
        try:
            get_context().startedChildThread(self.thread)
            return self.method(*self.args)
        #except:
        #    traceback.print_exc(file=sys.stderr)
        finally:
            get_context().finishedChildThread(self.thread)

def fork(*functions):
    """Start execution of functions in parallel.

    Args:
        *functions(function references)

    Returns:
        List of callable objects
    """
    callables = []
    for m in functions:
        if is_list(m):
            callables.append(Callable(m[0],*m[1]))
        else:
            callables.append(Callable(m))
    return Threading.fork(callables)

def join(futures):
    """Wait parallel execution of functions.

    Args:
        futures(list of Future) : as returned from fork

    Returns:
        None
"""
    try:
        return Threading.join(futures)
    except java.util.concurrent.ExecutionException, ex:
        raise ex.getCause()

def parallelize(*functions):
    """Equivalent to fork + join

    Args:
        *functions(function references)

    Returns:
        None
    """
    futures = fork(*functions)
    return join(futures)


###################################################################################################
#Script evaluation and background task control.
###################################################################################################

def run(script_name, args = None, locals = None):
    """Run script: can be absolute path, relative, or short name to be search in the path.
    Args:
        args(Dict ot List): Sets sys.argv (if list) or gobal variables(if dict) to the script.
        locals(Dict): If not none sets the locals()for the runing script.
                      If locals is used then script definitions will not go to global namespace.

    Returns:
        The script return value (if set with set_return)
    """
    script = get_context().scriptManager.library.resolveFile(script_name)
    if script is not None and os.path.isfile(script):
        info = get_context().startScriptExecution(script_name, args)
        try:
            set_return(None)
            if args is not None:
                if isinstance(args,list) or isinstance(args,tuple):
                    sys.argv =  list(args)
                    globals()["args"] = sys.argv
                else:
                    for arg in args.keys():
                        globals()[arg] = args[arg]
            if (locals is None):
                execfile(script, globals())
            else:
                execfile(script, globals(), locals)
            ret = get_return()
            get_context().finishScriptExecution(info, ret)
            return ret
        except Exception, ex:
            get_context().finishScriptExecution(info, ex)
            raise ex
    raise IOError("Invalid script: " + str(script_name))

def abort():
    """Abort the execution of ongoing task. It can be called from the script to quit.

    Args:
        None

    Returns:
        None
    """
    #Cannot be on script execution thread
    fork(get_context().abort)

def start_task(script, delay = 0.0, interval = -1):
    """Start a background task

    Args:
         script(str): Name of the script implementing the task
         delay(float, optional): time in seconds for the first execution.
                Default starts immediately.
         interval(float, optional): time in seconds for between execution.
                If negative (default), single-execution.

    Returns:
        None
    """
    delay_ms=int(delay*1000)
    interval_ms=int(interval*1000) if (interval>=0) else int(interval)
    get_context().taskManager.create(script, delay_ms, interval_ms)
    get_context().taskManager.start(script)

def stop_task(script, force = False):
    """Stop a background task

    Args:
         script(str): Name of the script implementing the task
         force(boolean, optional): interrupt current execution, if running

    Returns:
        None
    """
    get_context().taskManager.remove(script, force)

def set_return(value):
    """Sets the script return value. This value is returned by the "run" function.

    Args:
        value(Object): script return value.

    Returns:
        None
    """
    #In Jython, the output of last statement is not returned  when running a file
    if __name__ == "__main__":
        global __THREAD_EXEC_RESULT__
        if is_interpreter_thread():
            global _
            _=value
        __THREAD_EXEC_RESULT__[java.lang.Thread.currentThread()]=value         #Used when running file
    else:
        #if startup is imported, cannot set global
        caller = _get_caller()
        if is_interpreter_thread():
            caller.f_globals["_"]=value
        if not "__THREAD_EXEC_RESULT__" in caller.f_globals.keys():
            caller.f_globals["__THREAD_EXEC_RESULT__"] = {}
        caller.f_globals["__THREAD_EXEC_RESULT__"][java.lang.Thread.currentThread()]=value
    return value    #Used when parsing file

def get_return():
    if __name__ == "__main__":
        global __THREAD_EXEC_RESULT__
        return __THREAD_EXEC_RESULT__[java.lang.Thread.currentThread()]
    else:
        return _get_caller().f_globals["__THREAD_EXEC_RESULT__"][java.lang.Thread.currentThread()]

def is_interpreter_thread():
    return java.lang.Thread.currentThread().name == "Interpreter Thread"


###################################################################################################
#Versioning tools
###################################################################################################

def commit(message, force = False):
    """Commit the changes to the repository. If manual commit is not configured then there is no need to call this function: commits are made as needed.

    Args:
        message(str): commit message
        force(bool, optional): if False, raises exception if no change detected in repo

    Returns:
        None
    """
    get_context().commit(message, force)

def diff():
    """Return list of changes in the repository

    Args:
        None

    Returns:
        None
    """
    return get_context().diff()

def checkout_tag(tag):
    """Checkout a tag name.

    Args:
        tag(str): tag name.

    Returns:
        None
    """
    get_context().checkoutTag(tag)

def checkout_branch(tag):
    """Checkout a local branch name.

    Args:
        tag(str): branch name.

    Returns:
        None
    """
    get_context().checkoutLocalBranch(tag)

def pull_repository():
    """Pull from remote repository.

    """
    get_context().pullFromUpstream()

def push_repository(all_branches=True, force=False):
    """Push to remote repository.

    Args:
        all_branches(boolean, optional): all branches or just current.
        force(boolean, optional): force flag.

    Returns:
        None
    """
    get_context().pushToUpstream(all_branches, force)

def cleanup_repository():
    """Performs a repository cleanup.

    Args:
        None

    Returns:
        None
    """
    get_context().cleanupRepository()


###################################################################################################
#Device Pool functions
###################################################################################################

def get_device(device_name):
    """Returns a configured device (or imaging source) by its name.

    Args:
        device_name(str): name of the device.

    Returns:
        device
    """
    return get_context().devicePool.getByName(device_name)

def add_device(device, force = False):
    """Add  a device (or imaging source) to the device pool.

    Args:
        device(Device or Source): device object.
        force(boolean, optional): if true then dispose existing device with same name.
                    Otherwise will fail in case of name clash.

    Returns:
        True if device was added, false if was already in the pool, or exception in case of name clash.
    """
    if get_context().devicePool.contains(device):
        return False
    if force:
        dev = get_context().devicePool.getByName(device.getName())
        if dev is not None:
            remove_device(dev)
    return get_context().devicePool.addDevice(device)

def remove_device(device):
    """Remove a device (or imaging source) from the device pool.

    Args:
        device(Device or Source): device object.

    Returns:
        bool: true if device was removed.

    """
    return get_context().devicePool.removeDevice(device)

def set_device_alias(device, alias):
    """Set a device alias to be used in scans (datasets and plots).

    Args:
        device(Device): device object.
        alias(str): replace device name in scans.

    Returns:
        None
    """
    get_context().dataManager.setAlias(device, alias)

def stop():
    """Stop all devices implementing the Stoppable interface.

    Args:
        None

    Returns:
        None
    """
    get_context().stopAll()

def update():
    """Update all devices.

    Args:
        None

    Returns:
        None
    """
    get_context().updateAll()

def reinit(dev = None):
    """Re-initialize devices.

    Args:
        dev(Device, optional): Device to be re-initialized.
                               If  None re-initialize all devices not yet initialized.

    Returns:
        List with devices not initialized.
    """
    return to_list(get_context().reinit())

def create_device(url, parent=None):
    """Create a device form a definition string(see InlineDevice)

    Args:
        url(str or list of string): the device definition string (or list of strings)
        parent(bool, optional): parent device

    Returns:
        The created device (or list of devices)
    """
    return InlineDevice.create(url, parent)


def create_averager(dev, count, interval=0.0, name = None,  monitored = False):
    """Creates and initializes and averager for dev.

    Args:
        dev(Device): the source device
        count(int): number of samples
        interval(float, optional): sampling interval in seconds.
                                   If less than zero, sampling is made on data change event.
        name(str, optional): sets the name of the device (default is: <dev name> averager)
        monitored (bool, optional): if true then averager processes asynchronously.

    Returns:
        Averager device
    """
    dev = string_to_obj(dev)
    if isinstance(dev, ReadableArray):
        av = ArrayAverager(dev, count, int(interval*1000)) if (name is None) else ArrayAverager(name, dev, count, int(interval*1000))
    else:
        av = Averager(dev, count, int(interval*1000)) if (name is None) else Averager(name, dev, count, int(interval*1000))
    av.initialize()
    if (monitored):
       av.monitored = True
    return av


###################################################################################################
#Standard libraries management
###################################################################################################

if __name__ == "__main__":
    ca_channel_path=os.path.join(get_context().setup.getStandardLibraryPath(), "epics")
    sys.path.append(ca_channel_path)
    #This is to destroy previous context of _ca (it is not shared with PShell)
    if run_count > 0:
        if sys.modules.has_key("_ca"):
            import _ca
            _ca.initialize()


###################################################################################################
#Mathematical functions
###################################################################################################

def arrmul(a, b):
    """Multiply 2 series of the same size.

    Args:

        a(subscriptable)
        b(subscriptable)

    Returns:
        List

    """
    return map(mul, a, b)

def arrdiv(a, b):
    """Divide 2 series of the same size.

    Args:

        a(subscriptable)
        b(subscriptable)

    Returns:
        List

    """
    return map(truediv, a, b)

def arradd(a, b):
    """Add 2 series of the same size.

    Args:

        a(subscriptable)
        b(subscriptable)

    Returns:
        List

    """
    return map(add, a, b)

def arrsub(a, b):
    """Subtract 2 series of the same size.

    Args:

        a(subscriptable)
        b(subscriptable)

    Returns:
        List

    """
    return map(sub, a, b)

def arrabs(a):
    """Returns the absolute of all elements in series.

    Args:

        a(subscriptable)

    Returns:
        List

    """
    return map(abs, a)

def arroff(a, value = "mean"):
    """Subtract offset to all elemets in series.

    Args:

        a(subscriptable)
        type(int or str, optional): value to subtract from the array, or "mean" or "min".

    Returns:
        List

    """
    if value=="mean":
        value = mean(a)
    elif value=="min":
        value = min(a)
    return [x-value for x in a]

def mean(data):
    """Calculate the mean of a sequence.

    Args:
        data(subscriptable)

    Returns:
        Mean of the elements in the object.

    """
    return reduce(lambda x, y: x + y, data) / len(data)

def variance(data):
    """Calculate the variance of a sequence.

    Args:
        data(subscriptable)

    Returns:
        Variance of the elements in the object.

    """
    c = mean(data)
    ss = sum((x-c)**2 for x in data)
    return ss/len(data)

def stdev(data):
    """Calculate the standard deviation of a sequence.

    Args:
        data(subscriptable)

    Returns:
        Standard deviation of the elements in the object.

    """
    return variance(data)**0.5


def center_of_mass(data, x = None):
    """Calculate the center of mass of a series, and its rms.

    Args:

        data(subscriptable)
        x(list, tuple, array ..., optional): x coordinates

    Returns:
        Tuple (com, rms)

    """
    if x is None:
        x = Arr.indexesDouble(len(data))
    data_sum = sum(data)
    if (data_sum==0):
        return float('nan')
    xmd = arrmul( x, data)
    com = sum(xmd) / data_sum
    xmd2 = arrmul( x, xmd)
    com2 = sum(xmd2) / data_sum
    rms = math.sqrt(abs(com2 - com * com))
    return (com, rms)

def poly(val, coefs):
    """Evaluates a polinomial: (coefs[0] + coefs[1]*val + coefs[2]*val^2...

    Args:
        val(float): value
        coefs (list of loats): polinomial coefficients
    Returns:
        Evaluated function for val

    """
    r = 0
    p = 0
    for c in coefs:
        r = r + c * math.pow(val, p)
        p = p + 1
    return r

def histogram(data, range_min = None, range_max = None, bin = 1.0):
    """Creates histogram on data.

    Args:
        data (tuple, array, ArrayList or Array): input  data can be multi-dimensional or nested.
        range_min (int, optional): minimum histogram value. Default is floor(min(data))
        range_max (int, optional): maximul histogram value. Default is ceil(max(data))
        bin(int or float, optional): if int means number of bins. If float means bin size. Default = 1.0.
    Returns:
        tuple: (ydata, xdata)

    """
    if range_min is None: range_min = math.floor(min(flatten(data)))
    if range_max is None: range_max = math.ceil(max(flatten(data)))
    if type(bin) is float:
        bin_size = bin
        n_bin =  int(math.ceil(float(range_max - range_min)/bin_size))
    else:
        n_bin = bin
        bin_size = float(range_max - range_min)/bin

    result = [0] * n_bin
    for  d in flatten(data):
        b = int( float(d - range_min) / bin_size)
        if (b >=0) and (b < n_bin):
          result[b] = result[b] + 1
    return (result, frange(range_min, range_max, bin_size))

def _turn(p, q, r):
    return cmp((q[0] - p[0])*(r[1] - p[1]) - (r[0] - p[0])*(q[1] - p[1]), 0)

def _keep(hull, r):
    while len(hull) > 1 and _turn(hull[-2], hull[-1], r) != 1:
        hull.pop()
    return (not len(hull) or hull[-1] != r) and hull.append(r) or hull

def convex_hull(point_list=None, x=None, y=None):
    """Returns the convex hull from a list of points. Either point_list or x,y is provided.
       (Alhorithm taken from http://tomswitzer.net/2010/03/graham-scan/)
    Args:
        point_list (array of tuples, optional): arrays of the points
        x (array of float, optional): array with x coords of points
        y (array of float, optional): array with y coords of points
    Returns:
        Array of points or (x,y)

    """
    is_point_list = point_list is not None
    if not point_list:
        point_list=[]
        for i in range(len(x)):
            if((x[i] is not None) and (y[i] is not None)): point_list.append((x[i], y[i]))
    point_list.sort()
    lh,uh = reduce(_keep, point_list, []), reduce(_keep, reversed(point_list), [])
    ret = lh.extend(uh[i] for i in xrange(1, len(uh) - 1)) or lh
    if not is_point_list:
        x, y = [], []
        for i in range(len(ret)):
            x.append(ret[i][0])
            y.append(ret[i][1])
        return (x,y)
    return ret

###################################################################################################
#Utilities
###################################################################################################

def get_setting(name=None):
    """Get a persisted script setting value.

    Args:
        name (str): name of the setting.
    Returns:
        String with setting value or None if setting is undefined.
        If name is None then returns map with all settings.
    """
    return get_context().getSettings() if (name is None) else get_context().getSetting(name)

def set_setting(name, value):
    """Set a persisted script setting value.

    Args:
        name (str): name of the setting.
        value (obj): value for the setting, converted to string (if None then remove the setting).
    Returns:
        None.
    """
    get_context().setSetting(name, value)

def exec_cmd(cmd):
    """Executes a shell command. If errors happens raises an exception.

    Args:
        cmd (str): command process input.
    Returns:
        Output of command process.
    """
    import subprocess
    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
    (ret, err) = proc.communicate()
    if (err is not None) and err!="":
        raise Exception(err)
    return ret

def exec_cpython(script_name, args = [], method_name = None, python_name = "python"):
    """Executes an external cpython process.

    Args:
        script_name (str): name of the script (can be absolute or relative to script folder).
        args(list, optional): arguments to python process (or parameters to method, if not None)
        method_name (str, optional): if defined indicates a method to be called.
        python_name (str, optional): name of executable
    Returns:
        Return of python process.
    """
    if method_name is None:
        script = get_context().scriptManager.library.resolveFile(script_name)
        if script is None :
            script= os.path.abspath(script_name)
        c = python_name + " " + script + " "
        if args is not None and (len(args)>0):
            for arg in args:
                c = c + str(arg) + " "
        return exec_cmd(c)
    else:
        #Calling a method
        import json
        import tempfile
        script = os.path.abspath(get_context().scriptManager.library.resolveFile(script_name))
        with open(get_context().setup.getContextPath()+ "/Temp" + str(java.lang.Thread.currentThread().getId())+".py", "wb") as f:
            f.write(("script = '" +script +"'\n").replace('\\', '\\\\'))
            f.write("function = '" +method_name +"'\n")
            f.write("jsonargs = '" + json.dumps(args) +"'\n")
            f.write("""import sys
import json
import os
args =json.loads(jsonargs)
i = script.rfind(os.sep)
module = script[i+1:-3]
sys.path.insert(1,script[:i+1])
exec ('from ' + module + ' import ' + function + ' as function')
print (json.dumps(function(*args)))
""")
        f.close()
        ret = exec_cpython(os.path.abspath(f.name), python_name = python_name)
        os.remove(f.name)
        ret = '\n'+ret[0:-len(os.linesep)]
        jsonret = ret[ret.rfind('\n')+1:].strip()
        return json.loads(jsonret)

def bsget(channel, modulo=1, offset=0, timeout = 5.0):
    """Reads an values a bsread stream, using the default provider.

    Args:
        channel(str or  list of str): channel name(s)
        module(int, optional): stream modulo
        offset(int, optional): stream offset
        timeout(float, optional): stream timeout in secs
    Returns:
        BS value or list of  values

    """
    channels = to_list(channel)
    ret = Stream.readChannels(channels, modulo, offset, int(timeout * 1000))
    if is_string(channel):
        return ret[0]
    return ret

def flatten(data):
    """Flattens multi-dimentional or nested data.

    Args:
        data (tuple, array, ArrayList or Array): input data
    Returns:
        Iterator on the flattened data.

    """
    if isinstance(data,PyArray):
        if not data.typecode.startswith('['):
            return data

    import itertools
    return itertools.chain(*data)

def frange_gen(start, finish, step):
    while ((step >= 0.0) and (start <= finish)) or ((step < 0.0) and (start >= finish)):
      yield start
      start += step

def frange(start, finish, step, enforce_finish = False, inclusive_finish = False):
    """Create a list with a range of float values (a float equivalent to "range").

    Args:
        start(float): start of range.
        finish(float): end of range.
        step(float): step size.
        enforce_finish(boolean, optional): adds the final element even if range was not exact.
        inclusive_finish(boolean, optional): if false finish is exclusive (like in "range").

    Returns:
        list

    """
    step = float(step)
    ret = list(frange_gen(start, finish, step))
    if len(ret) > 0:
        if inclusive_finish == False:
            if ret[-1]==finish:
                del ret[-1]
        if enforce_finish and ret[-1]!=finish:
                ret.append(finish)
    return ret

def _get_caller():
    #Not doing inspect.currentframe().f_back because inspect is slow to load
    return sys._getframe(1).f_back if hasattr(sys, "_getframe") else None

def inject():
    """Restore initial globals: re-inject devices and startup variables to the interpreter.

    Args:
        None

    Returns:
        None

    """
    if __name__ == "__main__":
        get_context().injectVars()
    else:
        _get_caller().f_globals.update(get_context().scriptManager.injections)

def notify(subject, text, attachments = None, to=None):
    """Send email message.

    Args:
        subject(str): Message subject.
        text(str): Message body.
        attachments(list of str, optional): list of files to be attached (expansion tokens are allowed).
        to (list ofd str, optional): recipients. If None uses the recipients defined in mail.properties.
    Returns:
        None

    """
    get_context().notify(subject, text, to_list(attachments), to_list(to))

def string_to_obj(o):
    if is_string(o):
        if "://" in o:
            return InlineDevice(o)
        return eval(o)
    elif is_list(o):
        ret = []
        for i in o:
            ret.append(string_to_obj(i))
        return ret
    return o

def _getBuiltinFunctions(filter = None):
    ret = []
    for name in globals().keys():
        val = globals()[name]
        if type(val) is PyFunction:
            if filter is None or filter in name:
                #Only "public" documented functions
                if not name.startswith('_') and (val.__doc__ is not None):
                    ret.append(val)
    return to_array(ret)


def _getBuiltinFunctionNames(filter = None):
    ret = []
    for function in _getBuiltinFunctions(filter):
        ret.append(function.func_name)
    return to_array(ret)

def _getFunctionDoc(function):
    if is_string(function):
        if function not in globals():
            return
        function = globals()[function]
    if type(function) is PyFunction and '__doc__' in dir(function):
        ac = function.func_code.co_argcount
        var = function.func_code.co_varnames
        args = list(var)[:ac]
        defs = function.func_defaults
        if defs is not None:
            for i in range (len(defs)):
                index = len(args) - len(defs) + i
                args[index] = args[index] + " = " + str(defs[i])
        flags = function.func_code.co_flags
        if flags & 4 > 0:
            args.append('*' + var[ac])
            ac=ac+1
        if flags & 8 > 0:
            args.append('**' + var[ac])
        d = function.func_doc
        return function.func_name+ "(" + ", ".join(args) + ")" + "\n\n" + (d if (d is not None) else "")

def help(object = None):
    """
    Print help message for function or object (if available).

    Args:
        object (any, optional): function or object to get help.
                    If null prints a list of the builtin functions.

    Returns:
        None

    """
    if object is None:
        print "Built-in functions:"
        for f in _getBuiltinFunctionNames():
            print "\t" + f
    else:
        if type(object) is PyFunction:
            print _getFunctionDoc(object)
        elif '__doc__' in dir(object):
            #The default doc is now shown
            import org.python.core.BuiltinDocs.object_doc
            if object.__doc__ != org.python.core.BuiltinDocs.object_doc:
                print object.__doc__

###################################################################################################
#UI interaction
###################################################################################################

def set_status(status):
    """Set the application status.

    Args:
        status(str): new status.

    Returns:
        None
    """
    set_preference(Preference.STATUS, status)

def setup_plotting( enable_plots=None, enable_table=None,plot_list = None, line_plots = None, range = None, domain=None, defaults=None):
    if defaults == True: set_preference(Preference.DEFAULTS, True)
    if enable_plots is not None: set_preference(Preference.PLOT_DISABLED, not enable_plots)
    if enable_table is not None: set_preference(Preference.TABLE_DISABLED, not enable_table)
    if plot_list is not None: set_preference(Preference.ENABLED_PLOTS, None if plot_list == "all" else plot_list)
    if line_plots is not None:
        plots = None
        if line_plots != "none":
            plots = {}
            for plot in line_plots:
                plots[plot]=1
        set_preference(Preference.PLOT_TYPES, plots)
    if range is not None:
         if range == "none":
            set_preference(Preference.AUTO_RANGE, None)
         elif range == "auto":
            set_preference(Preference.AUTO_RANGE, True)
         else:
            set_preference(Preference.MANUAL_RANGE, range)
    if domain is not None:
        set_preference(Preference.DOMAIN_AXIS, domain)


def set_preference(preference, value):
    """Hints to graphical layer:

    Args:
        preference(Preference): Enum of preference types:
            PLOT_DISABLED: enable/disable scan plot (True/False)
            PLOT_LAYOUT: "Horizontal", "Vertical" or "Grid"
            TABLE_DISABLED: enable/disable scan table (True/False)
            ENABLED_PLOTS: select Readables to be plotted (list of Readable or String (names))
            PLOT_TYPES: Dictionary or (Readable or String):(String or int) pairs
                where the key is a plot name and the value is the desired plot type
            PRINT_SCAN: Print scan records to console
            AUTO_RANGE: Automatic range scan plots x-axis
            MANUAL_RANGE: Manually set scan plots x-axis
            MANUAL_RANGE_Y: Manually set scan plots y-axis
            DOMAIN_AXIS: Set the domain axis source: "Time", "Index", or a readable name.
                Default(None): first positioner
            STATUS: set application status
        value(object): preference value

    Returns:
        None
    """
    value = to_array(value, 'o') #If list then convert to Object array
    get_context().setPreference(preference, value)

def get_string(msg, default = None, alternatives = None, password = False):
    """
    Reads a string from UI
    Args:
        msg(str): display message.
        default(str, optional): value displayed when window is shown.
        alternatives(list of str, optional): if provided presents a combo box instead of an editing field.
        password(boolean, optional): if True hides entered characters.

    Returns:
        String entered of null if canceled
    """
    if password :
        return get_context().getPassword(msg, None)
    return get_context().getString(msg, str(default) if (default is not None) else None, alternatives)

def get_option(msg, type = "YesNoCancel"):
    """
    Gets an option from UI
    Args:
        msg(str): display message.
        type(str, optional): 'YesNo','YesNoCancel' or 'OkCancel'

    Returns:
        'Yes', 'No', 'Cancel'

    """
    return get_context().getOption(msg, type)

def show_message(msg, title=None, blocking = True):
    """
    Pops a blocking message to UI

    Args:
        msg(str): display message.
        title(str, optional): dialog title
    """
    get_context().showMessage(msg, title, blocking)

def show_panel(device, title=None):
    """
    Show, if exists, the panel relative to this device.

    Args:
        device(Device or str or BufferedImage): device
        title only apply to BufferedImage objects. For devices the title is the device name.
    """
    if type(device) is BufferedImage:
        device = DirectSource(title, device)
        device.initialize()
    if is_string(device):
        device = get_device(device)
    return get_context().showPanel(device)
