# -*- coding: utf-8 -*-
"""
Hardware file for the Superconducting Magnet (SCM)

QuDi is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

QuDi is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with QuDi. If not, see <http://www.gnu.org/licenses/>.

Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the
top-level directory of this distribution and at <https://github.com/Ulm-IQO/qudi/>
"""


import socket
from core.base import Base
import numpy as np
import time
from interface.magnet_interface import MagnetInterface
from collections import OrderedDict
import re


class Magnet(Base, MagnetInterface):
    """Magnet positioning software for superconducting magnet.
    Enables precise positioning of the magnetic field in spherical coordinates
    with the angle theta, phi and the radius rho.
    The superconducting magnet has three coils, one in x, y and z direction respectively.
    The current through these coils is used to compute theta, phi and rho.
    The alignment can be done manually as well as automatically via fluorescence alignment.
    """

    _modtype = 'Magnet'
    _modclass = 'hardware'

    def __init__(self, **kwargs):
        """Here the connections to the power supplies and to the counter are established"""
        super().__init__(**kwargs)
        socket.setdefaulttimeout(3)
        try:
            self.soc_x = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        except socket.timeout:
            self.log.error("socket timeout for coil in x-direction")
        try:
            self.soc_y = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        except socket.timeout:
            self.log.error("socket timeout for coil in y-direction")
        try:
            self.soc_z = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        except socket.timeout:
            self.log.error("socket timeout for coil in z-direction")

        # default waiting time of the pc after a message was sent to the magnet
        # should be set in the config file
        self.waitingtime = 0.01

        # This is saves in which interval the input theta was in the last movement
        self._inter = 1

        # this is a new thing. "normal_mode"
        # allows one to set magnetic fields up to 1 T along each axis
      
  # and the magnetic field vector can't be larger than 1.2 T
        # In z_mode you are allowed to move in a 5° solid angle along the z-axis
        # with a maximum field of 3 T.
        # For more documentation or how to change the mode look into
        # the function switch_mode
        self.mode = "normal_mode"

        # constraints of the superconducting magnet in T
        # should be
 set in the config file
        # Normally you should get and set constraints in the
        # function get_constraints(). The problem is here that
        # the constraint rho is no constant and is dependent on the
        # current theta and phi value.
        self.x_constr = 1.0
        self.y_constr = 1.0
        self.z_constr = 3.0
        self.rho_constr = 1.2

    def on_activate(self):
        """
        loads the config file and extracts the necessary configurations for the
        superconducting magnet

        @return int: (0: Ok, -1:error)
        """

# get necessary information from the config file
        config = self.getConfiguration()

        if 'magnet_port' in config.keys():
            port = config['magnet_port']
        else:
            self.log.error('No port hs been defined in the config file!')
            return -1

        if 'magnet_IP_address_x' in config.keys():
            self.soc_x.connect((config['magnet_IP_address_x'], port))
        else:
            self.log.error('No ip-address for connection to x-coil defined!')
            return -1

        if 'magnet_IP_address_y' in config.keys():
            self.soc_y.connect((config['magnet_IP_address_y'], port))
        else:
            self.log.error('No ip-address for connection to y-coil defined!')
            return -1

        if 'magnet_IP_address_z' in config.keys():
            self.soc_z.connect((config['magnet_IP_address_z'], port))
        else:
            self.log.error('No ip-address for connection to z-coil defined!')
            return -1

        if 'magnet_waitingtime' in config.keys():
            self.waitingtime = config['magnet_waitingtime']

        if 'magnet_x_constr' in config.keys():
            self.x_constr = config['magnet_x_constr']

        if 'magnet_y_constr' in config.keys():
            self.y_constr = config['magnet_y_constr']

        if 'magnet_z_constr' in config.keys():
            self.z_constr = config['magnet_z_constr']

        if 'magnet_rho_constr' in config.keys():
            self.rho_constr = config['magnet_rho_constr']

#       sending a signal to all coils to receive an answer to cut off the
#       useless welcome message.
        ask_dict = {'x': "STATE?\n", 'y': "STATE?\n", 'z': "STATE?\n"}
        answ_dict = self.ask(ask_dict)
        self.log.info("Magnet in state: {0}".format(answ_dict))
#       sending a command to the magnet to turn into SI units regarding
#       field units.
        self.heat_all_switches()
        tell_dict = {'x': 'CONF:FIELD:UNITS 1', 'y': 'CONF:FIELD:UNITS 1', 'z': 'CONF:FIELD:UNITS 1'}
        self.tell(tell_dict)

    def on_deactivate(self):
        self.soc_x.close()
        self.soc_y.close()
        self.soc_z.close()

    def utf8_to_byte(self, myutf8):
        """
        Convenience function for code refactoring
        @param string myutf8 the message to be encoded
        @return the encoded message in bytes
        """
        return myutf8.encode('utf-8')

    def byte_to_utf8(self, mybytes):
        """
        Convenience function for code refactoring
        @param bytes mybytes the byte message to be decoded
        @return the decoded string in uni code
        """
        return mybytes.decode()

# =========================== Magnet Functionality Core ====================================

    def get_constraints(self):
        """ Retrieve the hardware constraints from the magnet driving device.

        @return dict: dict with constraints for the magnet hardware. These
                      constraints will be passed via the logic to the GUI so
                      that proper display elements with boundary conditions
                      could be made.

        Provides all the constraints for each axis of a motorized stage
        (like total travel distance, velocity, ...)
        Each axis has its own dictionary, where the label is used as the
        identifier throughout the whole module. The dictionaries for each axis
        are again grouped together in a constraints dictionary in the form

            {'<label_axis0>': axis0 }

        where axis0 is again a dict with the possible values defined below. The
        possible keys in the constraint are defined in the interface file.
        If the hardware does not support the values for the constraints, then
        insert just None. If you are not sure about the meaning, look in other
        hardware files to get an impression.
        """
        constraints = OrderedDict()
        pos_dict = self.get_pos()
        coord_list = [pos_dict['rho'], pos_dict['theta'], pos_dict['phi']]
        pos_max_dict = self.rho_pos_max({'rad': coord_list})

        # get the constraints for the x axis:
        axis0 = {}
        axis0['label'] = 'rho'       # name is just as a sanity included
        axis0['unit'] = 'T'        # the SI units
        axis0['pos_min'] = 0
        axis0['pos_max'] = pos_max_dict['rho']
        axis0['pos_step'] = 300000
        axis0['vel_min'] = 0
        axis0['vel_max'] = 0.0404*0.01799  # unit is T/s
        axis0['vel_step']