"""
TITLE
MultiplePositions Hardware Object

DESCRIPTION
This object manages the movement of several motors to predefined positions.

<username> : name of the multiplepositions object
<mode>     : there is two ways of managing the change of predefined positions
              absolute: change the value of a predefined position by another
                        absolute position
              relative: do not change the absolute value of the predefined
                        position but the user value of the motors concerned

<motors>
    <object role="role1" ... :list of motors to be moved to reach a predefined
    <object role="role2" ...  position. the "role" will be used to referenced
        ...                   the motors in the definitions of the predefined
<motors>                      positions

<deltas>                    : for each motor you define the windows used to
    <role1>val1</role1>       determine that a motor as reach a position
    <role2>val2</role2>
    ...
</deltas>

<positions>
    <position>
        <name>      : name of a predefined position. Must be unique in the file
        <role1>val1 : position of the motor "role1" for the predefined position
                     "name"
        <role2>val2 : position of the motor "role2" for the predefined position
                     "name"
        <resoy>8.69565217391e-07</resoy> : for all the position, independent
        <beamx>100</beamx>                 value with keyword can be added,
                                           saved, read ...
    </position>
    ...
</position>


METHOD
    name:           get_state
    input par.:     None
    output par.:    state
    description:    return an and on the state of all the  motor used in the
                    object

    name:           moveToPosition
    input par.:     name
    output par.:    None
    description:    move all motors to the predefined position "position"

    name:           get_value
    input par.:     None
    output par.:    position
    description:    return the name of the current predefined position.
                    return None if all motors are not in their psotion

    name:           setNewPositions
    input par.:     name, newPositions
    output par.:    None
    description:    For the position "name", change the motors positions set
                    in "newPositions", a dictionary with motor role as keys
                    and new motor position as values.
                    Save the new values in the xml file

    name:           getPositionKeyValue
    input par.:     name, key
    output par.:    value
    description:    return the value of the independent "key" field of
                    the predefined position "name"

    name:           setPositionKeyValue
    input par.:     name, key, value
    output par.:    None
    description:    Change in the object and in the xml file the value of the
                    independent field "key" in the predefined position "name"

    name:           getRoles
    input par.:     None
    output par.:    roles[]
    description:    return the list of motor's role used in the objects


SIGNAL
    name:           stateChanged
    parameter:      state
    description:    send the new state of the object when it changes

    name:           noPosition
    parameter:      None
    description:    sent when after a position change of any of the motor
                    the object is not in any of the predefined positions

    name:           positionReached
    parameter:      positionName
    description:    sent when after a position change of any of the motor
                    the object has reach a predefined position.
                    The parameter is the name of this position.

TEMPLATE
<object class="MultiplePositions">
    <username>VLM Zoom</username>
    <mode>absolute</mode>
    <motors>
        <object role="zoom" hwrid="/berru/zoom"></object>
    </motors>

    <deltas>
        <zoom>0.1</zoom>
    </deltas>

    <positions>
        <position>
            <name>1X</name>
            <zoom>0</zoom>
            <resox>-4.16666666667e-07</resox>
            <resoy>7.35294117647e-07</resoy>
            <beamx>537</beamx>
            <beamy>313</beamy>
        </position>
        <position>
            <name>6X</name>
            <zoom>1</zoom>
            <resox>-5.52486187845e-07</resox>
            <resoy>8.69565217391e-07</resoy>
            <beamx>100</beamx>
            <beamy>100</beamy>
        </position>
        <position>
            <name>12X</name>
            <zoom>2</zoom>
            <resox>0.0000004</resox>
            <resoy>0.0000004</resoy>
            <beamx>200</beamx>
            <beamy>200</beamy>
        </position>
    </positions>
</object>"""

try:
    from xml.etree import cElementTree  # python2.5
except ImportError:
    import cElementTree

import logging

from mxcubecore.BaseHardwareObjects import HardwareObject


class MultiplePositions(HardwareObject):
    def init(self):
        try:
            self.mode
        except AttributeError:
            self.mode = "absolute"

        motors = self["motors"]
        self.roles = motors.get_roles()

        self.deltas = {}
        try:
            # WARNING self.deltas is a LINK to the INTERNAL properties dictionary
            # modifying it modifies the GLOBAL properties, not just the local copy
            # Maybe do self["deltas"].get_properties().copy()?
            self.deltas = self["deltas"].get_properties()
        except Exception:
            logging.getLogger().error("No deltas.")

        self.positions = {}
        self.positionsIndex = []
        try:
            positions = self["positions"]
        except Exception:
            logging.getLogger().error("No positions.")
        else:
            for position in positions:
                name = position.get_property("name")
                if name is not None:
                    self.positionsIndex.append(name)
                    self.positions[name] = {}

                    motpos = position.get_properties()
                    motroles = list(motpos.keys())

                    for role in self.roles:
                        self.positions[name][role] = motpos[role]
                else:
                    logging.getLogger().error("No name for position.")

        self.motors = {}
        for mot in self["motors"]:
            self.motors[mot.get_motor_mnemonic()] = mot
            self.connect(mot, "moveDone", self.checkPosition)
            self.connect(mot, "valueChanged", self.checkPosition)
            self.connect(mot, "stateChanged", self.stateChanged)

    def get_state(self):
        if not self.is_ready():
            return ""

        state = "READY"
        for mot in self.motors.values():
            if mot.get_state() == mot.MOVING:
                state = "MOVING"
            elif mot.get_state() == mot.UNUSABLE:
                return "UNUSABLE"

        return state

    def stateChanged(self, state):
        self.emit("stateChanged", (self.get_state(),))
        self.checkPosition()

    def moveToPosition(self, name, wait=False):
        move_list = []
        for role in self.roles:
            device = self.get_deviceby_role(role)
            pos = self.positions[name][role]
            move_list.append((device, pos))

        for mot, pos in move_list:
            if mot is not None:
                mot.set_value(pos)

        if wait:
            [mot.waitEndOfMove() for mot, pos in move_list if mot is not None]
        """
        for mne,pos in self.positions[name].items():
        self.motors[mne].set_value(pos)
        """

    def get_value(self):
        if not self.is_ready():
            return None

        for posName, position in self.positions.items():
            findPosition = 0

            for role in self.roles:
                pos = position[role]
                mot = self.get_deviceby_role(role)

                if mot is not None:
                    motpos = mot.get_value()
                    try:
                        if (
                            motpos < pos + self.deltas[role]
                            and motpos > pos - self.deltas[role]
                        ):
                            findPosition += 1
                    except Exception:
                        continue

            if findPosition == len(self.roles):
                return posName

        return None

    def checkPosition(self, *args):
        if not self.is_ready():
            return None

        posName = self.get_value()

        if posName is None:
            self.emit("noPosition", ())
            return None
        else:
            self.emit("positionReached", (posName,))
            return posName

    def setNewPositions(self, name, newPositions):
        position = self.__getPositionObject(name)

        if position is None:
            self.checkPosition()
            return

        for role, pos in list(newPositions.items()):
            self.positions[name][role] = pos
            position.setProperty(role, pos)

        self.checkPosition()
        self.commit_changes()

    def getPositionKeyValue(self, name, key):
        position = self.__getPositionObject(name)

        if position is None:
            return None

        return position.get_property(key)

    def setPositionKeyValue(self, name, key, value):
        xml_tree = cElementTree.fromstring(self.xml_source())
        positions = xml_tree.find("positions")

        pos_list = positions.findall("position")
        for pos in pos_list:
            if pos.find("name").text == name:
                if pos.find(key) is not None:
                    position = self.__getPositionObject(name)
                    position.setProperty(key, str(value))
                    self.commit_changes()
                    return True
                else:
                    key_el = cElementTree.SubElement(pos, key)
                    key_el.text = value
                    print((cElementTree.tostring(xml_tree)))
                    self.rewrite_xml(cElementTree.tostring(xml_tree))
                    return True

        return False

    def __getPositionObject(self, name):
        for position in self["positions"]:
            if position.get_property("name") == name:
                return position

        return None

    def getRoles(self):
        return self.roles

    def addPosition(self, el_dict):
        xml_tree = cElementTree.fromstring(self.xml_source())
        positions = xml_tree.find("positions")

        pos = cElementTree.SubElement(positions, "position")

        for key, val in el_dict.items():
            sel = cElementTree.SubElement(pos, key)
            sel.text = val

        self.rewrite_xml(cElementTree.tostring(xml_tree))

    def remPosition(self, name):
        xml_tree = cElementTree.fromstring(self.xml_source())
        positions = xml_tree.find("positions")

        pos_list = positions.findall("position")
        for pos in pos_list:
            if pos.find("name").text == name:
                positions.remove(pos)

        self.rewrite_xml(cElementTree.tostring(xml_tree))

    def addField(self, name, key, val):
        pass

    def remField(self, name, key):
        pass


"""
        xml_tree = cElementTree.fromstring(self.xml_source())
        for elt in xml_tree.findall(".//position"):
           if elt.find("name").text=="12X":
             new_elt = cElementTree.Element("bidule")
             new_elt.text = "HELLO"
             elt.append(new_elt)
        self.rewrite_xml(cElementTree.tostring(xml_tree))
"""
