from django.db import models
from apps.main.models import Configuration
from django.core.urlresolvers import reverse
# Create your models here.
from celery.execute import send_task
from datetime import datetime
import ast
import socket
import json
import requests
import struct
import os, sys, time

import multiprocessing


antenna_default = json.dumps({
                    "antenna_up": [[0.0,0.0,0.0,0.0,0.5,0.5,0.5,0.5],
                                    [0.0,0.0,0.0,0.0,0.5,0.5,0.5,0.5],
                                    [0.0,0.0,0.0,0.0,0.5,0.5,0.5,0.5],
                                    [0.0,0.0,0.0,0.0,0.5,0.5,0.5,0.5],
                                    [0.5,0.5,0.5,0.5,1.0,1.0,1.0,1.0],
                                    [0.5,0.5,0.5,0.5,1.0,1.0,1.0,1.0],
                                    [0.5,0.5,0.5,0.5,1.0,1.0,1.0,1.0],
                                    [0.5,0.5,0.5,0.5,1.0,1.0,1.0,1.0]
                                    ]
                                   ,
                    "antenna_down": [[0.0,0.0,0.0,0.0,0.5,0.5,0.5,0.5],
                                    [0.0,0.0,0.0,0.0,0.5,0.5,0.5,0.5],
                                    [0.0,0.0,0.0,0.0,0.5,0.5,0.5,0.5],
                                    [0.0,0.0,0.0,0.0,0.5,0.5,0.5,0.5],
                                    [0.5,0.5,0.5,0.5,3.0,3.0,3.0,3.0],
                                    [0.5,0.5,0.5,0.5,3.0,3.0,3.0,3.0],
                                    [0.5,0.5,0.5,0.5,3.0,3.0,3.0,3.0],
                                    [0.5,0.5,0.5,0.5,3.0,3.0,3.0,3.0]],
                })


tx_default = json.dumps({
                "up": [[1,1,1,1,0,0,0,0],
                       [1,1,1,1,0,0,0,0],
                       [1,1,1,1,0,0,0,0],
                       [1,1,1,1,0,0,0,0],
                       [0,0,0,0,1,1,1,1],
                       [0,0,0,0,1,1,1,1],
                       [0,0,0,0,1,1,1,1],
                       [0,0,0,0,1,1,1,1]],

              "down": [[1,1,1,1,0,0,0,0],
                       [1,1,1,1,0,0,0,0],
                       [1,1,1,1,0,0,0,0],
                       [1,1,1,1,0,0,0,0],
                       [0,0,0,0,1,1,1,1],
                       [0,0,0,0,1,1,1,1],
                       [0,0,0,0,1,1,1,1],
                       [0,0,0,0,1,1,1,1]],
             })

rx_default = json.dumps({
                "up": [[1,1,1,1,0,0,0,0],
                       [1,1,1,1,0,0,0,0],
                       [1,1,1,1,0,0,0,0],
                       [1,1,1,1,0,0,0,0],
                       [0,0,0,0,1,1,1,1],
                       [0,0,0,0,1,1,1,1],
                       [0,0,0,0,1,1,1,1],
                       [0,0,0,0,1,1,1,1]],

              "down": [[1,1,1,1,0,0,0,0],
                       [1,1,1,1,0,0,0,0],
                       [1,1,1,1,0,0,0,0],
                       [1,1,1,1,0,0,0,0],
                       [0,0,0,0,1,1,1,1],
                       [0,0,0,0,1,1,1,1],
                       [0,0,0,0,1,1,1,1],
                       [0,0,0,0,1,1,1,1]],
             })

status_default = '0000000000000000000000000000000000000000000000000000000000000000'
default_messages = {}

for i in range(1,65):
    default_messages[str(i)] = "Module "+str(i)


ues_default = json.dumps({
                "up": [0.533333,0.00000,1.06667,0.00000],
                "down": [0.533333,0.00000,1.06667,0.00000]
            })

onlyrx_default = json.dumps({
                    "up": False,
                    "down": False
                })

def up_convertion(cadena):
    valores = []
    for c in cadena:
        if c == 1.0: valores=valores+['000']
        if c == 2.0: valores=valores+['001']
        if c == 3.0: valores=valores+['010']
        if c == 0.0: valores=valores+['011']
        if c == 0.5: valores=valores+['100']
        if c == 1.5: valores=valores+['101']
        if c == 2.5: valores=valores+['110']
        if c == 3.5: valores=valores+['111']

    return valores

def up_conv_bits(value):

    if value == 1.0: bits="000"
    if value == 2.0: bits="001"
    if value == 3.0: bits="010"
    if value == 0.0: bits="011"
    if value == 0.5: bits="100"
    if value == 1.5: bits="101"
    if value == 2.5: bits="110"
    if value == 3.5: bits="111"

    return bits

def down_convertion(cadena):
    valores = []
    for c in cadena:
        if c == 1.0: valores=valores+['000']
        if c == 2.0: valores=valores+['001']
        if c == 3.0: valores=valores+['010']
        if c == 0.0: valores=valores+['011']
        if c == 0.5: valores=valores+['100']
        if c == 1.5: valores=valores+['101']
        if c == 2.5: valores=valores+['110']
        if c == 3.5: valores=valores+['111']

    return valores

def down_conv_bits(value):

    if value == 1.0: bits="000"
    if value == 2.0: bits="001"
    if value == 3.0: bits="010"
    if value == 0.0: bits="011"
    if value == 0.5: bits="100"
    if value == 1.5: bits="101"
    if value == 2.5: bits="110"
    if value == 3.5: bits="111"

    return bits

def up_conv_value(bits):

    if bits == "000": value=1.0
    if bits == "001": value=2.0
    if bits == "010": value=3.0
    if bits == "011": value=0.0
    if bits == "100": value=0.5
    if bits == "101": value=1.5
    if bits == "110": value=2.5
    if bits == "111": value=3.5

    return value

def down_conv_value(bits):

    if bits == "000": value=1.0
    if bits == "001": value=2.0
    if bits == "010": value=3.0
    if bits == "011": value=0.0
    if bits == "100": value=0.5
    if bits == "101": value=1.5
    if bits == "110": value=2.5
    if bits == "111": value=3.5

    return value

def ip2position(module_number):
    j=0
    i=0
    for x in range(0,module_number-1):
        j=j+1
        if j==8:
            i=i+1
            j=0

    pos = [i,j]
    return pos


def fromBinary2Char(binary_string):
    number = int(binary_string, 2)
    #Plus 33 to avoid more than 1 characters values such as: '\x01'-'\x1f'
    number = number + 33
    char = chr(number)
    return char

def fromChar2Binary(char):
    number = ord(char) - 33
    #Minus 33 to get the real value
    bits = bin(number)[2:]
    #To ensure we have a string with 6bits
    if len(bits) < 6:
        bits = bits.zfill(6)
    return bits

OPERATION_MODES = (
                 (0, 'Manual'),
                 (1, 'Automatic'),
             )


class ABSConfiguration(Configuration):
    active_beam     = models.PositiveSmallIntegerField(verbose_name='Active Beam', default=0)
    module_status   = models.CharField(verbose_name='Module Status', max_length=10000, default=json.dumps(status_default))
    operation_mode  = models.PositiveSmallIntegerField(verbose_name='Operation Mode', choices=OPERATION_MODES, default = 0)
    operation_value = models.FloatField(verbose_name='Periodic (seconds)', default="10", null=True, blank=True)
    module_messages = models.CharField(verbose_name='Modules Messages', max_length=10000, default=json.dumps(default_messages))

    class Meta:
        db_table = 'abs_configurations'

    def get_absolute_url_plot(self):
        return reverse('url_plot_abs_patterns', args=[str(self.id)])


    def parms_to_dict(self):

        parameters = {}

        parameters['device_id'] = self.device.id
        parameters['label']      = self.label
        parameters['device_type']      = self.device.device_type.name
        parameters['beams']     = {}

        beams = ABSBeam.objects.filter(abs_conf=self)
        b=1
        for beam in beams:
            #absbeam = ABSBeam.objects.get(pk=beams[beam])
            parameters['beams']['beam'+str(b)] = beam.parms_to_dict()#absbeam.parms_to_dict()
            b+=1

        return parameters


    def dict_to_parms(self, parameters):

        self.label = parameters['label']

        absbeams  = ABSBeam.objects.filter(abs_conf=self)
        beams     = parameters['beams']

        if absbeams:
            beams_number    = len(beams)
            absbeams_number = len(absbeams)
            if beams_number==absbeams_number:
                i = 1
                for absbeam in absbeams:
                    absbeam.dict_to_parms(beams['beam'+str(i)])
                    i = i+1
            elif beams_number > absbeams_number:
                i = 1
                for absbeam in absbeams:
                    absbeam.dict_to_parms(beams['beam'+str(i)])
                    i=i+1
                for x in range(i,beams_number+1):
                    new_beam = ABSBeam(
                               name     =beams['beam'+str(i)]['name'],
                               antenna  =json.dumps(beams['beam'+str(i)]['antenna']),
                               abs_conf = self,
                               tx       =json.dumps(beams['beam'+str(i)]['tx']),
                               rx       =json.dumps(beams['beam'+str(i)]['rx']),
                               ues      =json.dumps(beams['beam'+str(i)]['ues']),
                               only_rx  =json.dumps(beams['beam'+str(i)]['only_rx'])
                               )
                    new_beam.save()
                    i=i+1
            else: #beams_number < absbeams_number:
                i = 1
                for absbeam in absbeams:
                    if i <= beams_number:
                        absbeam.dict_to_parms(beams['beam'+str(i)])
                        i=i+1
                    else:
                        absbeam.delete()
        else:
            for beam in beams:
                new_beam = ABSBeam(
                           name     =beams[beam]['name'],
                           antenna  =json.dumps(beams[beam]['antenna']),
                           abs_conf = self,
                           tx       =json.dumps(beams[beam]['tx']),
                           rx       =json.dumps(beams[beam]['rx']),
                           ues      =json.dumps(beams[beam]['ues']),
                           only_rx  =json.dumps(beams[beam]['only_rx'])
                           )
                new_beam.save()



    def update_from_file(self, parameters):

        self.dict_to_parms(parameters)
        self.save()


    def get_beams(self, **kwargs):
        '''
        This function returns ABS Configuration beams
        '''
        return ABSBeam.objects.filter(abs_conf=self.pk, **kwargs)

    def clone(self, **kwargs):

        beams = self.get_beams()
        self.pk = None
        self.id = None
        for attr, value in kwargs.items():
            setattr(self, attr, value)
        self.save()

        for beam in beams:
            beam.clone(abs_conf=self)

        #-----For Active Beam-----
        new_beams = ABSBeam.objects.filter(abs_conf=self)
        self.active_beam = new_beams[0].id
        self.save()
        #-----For Active Beam-----
        #-----For Device Status---
        self.device.status = 3
        self.device.save()
        #-----For Device Status---

        return self


    def start_device(self):

        if self.device.status == 3:

            try:
                #self.write_device()
                send_task('task_change_beam', [self.id],)
                self.message = 'ABS running'

            except Exception as e:
                self.message = str(e)
                return False

            return True

        else:
            self.message = 'Please, select Write ABS Device first.'
            return False


    def stop_device(self):

        self.device.status = 2
        self.device.save()
        self.message = 'ABS has been stopped.'
        self.save()

        return True


    def write_device(self):

        """
        This function sends the beams list to every abs module.
        It needs 'module_conf' function
        """

        beams = ABSBeam.objects.filter(abs_conf=self)
        nbeams = len(beams)
        if self.connected_modules() == 0 :
            self.message = "No ABS Module detected."
            return False

        #-------------Write each abs module-----------

        if beams:
            block_id = 0
            message = 'SNDF{:03d}{:02d}{:02d}'.format(nbeams, nbeams, block_id)
            for i, status in enumerate(self.module_status):
                message += ''.join([fromBinary2Char(beam.module_6bits(i)) for beam in beams])
            status = ['0'] * 64
            n = 0

            sock = self.send_multicast(message)

            for i in range(32):
                try:
                    data, address = sock.recvfrom(1024)
                    print address, data
                    if data == '1':
                        status[int(address[0][10:])-1] = '3'
                    elif data == '0':
                        status[int(address[0][10:])-1] = '1'
                except Exception as e:
                    print 'Error {}'.format(e)
                    n += 1
            sock.close()
        else:
            self.message = "ABS Configuration does not have beams"
            return False

        if n == 64:
            self.message = "Could not write ABS Modules"
            self.device.status = 0
            self.module_status = ''.join(status)
            self.save()
            return False
        else:
            self.message = "ABS Beams List have been sent to ABS Modules"
            self.active_beam = beams[0].pk

        self.device.status = 3
        self.module_status = ''.join(status)
        self.save()
        return True


    def read_module(self, module):

        """
        Read out-bits (up-down) of 1 abs module NOT for Configuration
        """

        ip_address  = self.device.ip_address
        ip_address  = ip_address.split('.')
        module_seq  = (ip_address[0],ip_address[1],ip_address[2])
        dot         = '.'
        module_ip   = dot.join(module_seq)+'.'+str(module)
        module_port = self.device.port_address
        read_route = 'http://'+module_ip+':'+str(module_port)+'/read'

        module_status = json.loads(self.module_status)
        print(read_route)

        module_bits = ''

        try:
            r_read      = requests.get(read_route, timeout=0.5)
            answer      = r_read.json()
            module_bits = answer['allbits']
        except:
            return {}

        return module_bits

    def read_device(self):

        parms = {}
        # Reads active modules.
        module_status = json.loads(self.module_status)
        total = 0
        for status in module_status:
            if module_status[status] != 0:
                module_bits = self.read_module(int(status))
                bits={}
                if module_bits:
                    bits = (str(module_bits['um2']) + str(module_bits['um1']) + str(module_bits['um0']) +
                            str(module_bits['dm2']) + str(module_bits['dm1']) + str(module_bits['dm0']) )
                    parms[str(status)] = bits

                total +=1

        if total==0:
            self.message = "No ABS Module detected. Please select 'Status'."
            return False



        self.message = "ABS Modules have been read"
        #monitoreo_tx = JROABSClnt_01CeCnMod000000MNTR10
        return parms


    def connected_modules(self):
        """
        This function returns the number of connected abs-modules without updating.
        """
        num = 0
        print(self.module_status)
        for i, status in  enumerate(self.module_status):
            if status != '0':
                num += 1
                #print('status {}:{}'.format(i+1, status))
        return num

    def send_multicast(self, message):

        multicast_group = ('224.3.29.71', 10000)
        # Create the datagram socket
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.settimeout(1)
        # sock.bind((local_ip, 10000))
        local_ip = os.environ.get('LOCAL_IP', '127.0.0.1')
        local_ip = '192.168.1.128'
        sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(local_ip))
        sent = sock.sendto(message, multicast_group)
        print('Sending ' + message)
        return sock

    def status_device(self):
        """
        This function returns the status of all abs-modules as one.
        If at least one module is connected, its answer is "1"
        """
        
        sock = self.send_multicast('MNTR')
        
        n = 0
        status = ['0'] * 64
        for i in range(32):
            #if True:
            try:
                address = None
                data, address = sock.recvfrom(1024)
                print address, data
                if data == '1':
                    status[int(address[0][10:])-1] = '3'
                elif data == '0':
                    status[int(address[0][10:])-1] = '1'
                n += 1
                print('Module: {} connected'.format(address))
            except:
                print('Module: {} error'.format(address))
                pass
        sock.close()
        
        if n > 0:
            self.message = 'ABS modules Status have been updated.'
            self.device.status = 1
        else:
            self.device.status = 0
            self.message = 'No ABS module is connected.'
        self.module_status = ''.join(status)
        self.save()

        return self.device.status


    def send_beam(self, beam_pos):
        """
        This function connects to a multicast group and sends the beam number
        to all abs modules.
        """

        # Se manda a cero RC para poder realizar cambio de beam
        if self.experiment is None:
            confs = []
        else:
            confs = Configuration.objects.filter(experiment = self.experiment).filter(type=0)
        confdds  = ''
        confjars = ''
        confrc   = ''
        #TO STOP DEVICES: DDS-JARS-RC
        for i in range(0,len(confs)):
            if i==0:
                for conf in confs:
                    if conf.device.device_type.name == 'dds':
                        confdds = conf
                        confdds.stop_device()
                        break
            if i==1:
                for conf in confs:
                    if conf.device.device_type.name == 'jars':
                        confjars = conf
                        confjars.stop_device()
                        break
            if i==2:
                for conf in confs:
                    if conf.device.device_type.name == 'rc':
                        confrc = conf
                        confrc.stop_device()
                        break
        if beam_pos > 0:
            beam_pos = beam_pos - 1
        else:
            beam_pos = 0

        #El indice del apunte debe ser menor que el numero total de apuntes
        #El servidor tcp en el embebido comienza a contar desde 0
        status = ['0'] * 64
        message = 'CHGB{}'.format(beam_pos) 
        sock = self.send_multicast(message)
        for i in range(32):
            try:
                data, address = sock.recvfrom(1024)
                print address, data
                if data == '1':
                    status[int(address[0][10:])-1] = '3'
                elif data == '0':
                    status[int(address[0][10:])-1] = '1'
            except  Exception as e:
                print 'Error {}'.format(e)
                pass

        sock.close()

        #Start DDS-RC-JARS
        if confdds:
            confdds.start_device()
        if confrc:
            #print confrc
            confrc.start_device()
        if confjars:
            confjars.start_device()

        self.message = "ABS Beam has been changed"
        self.module_status = ''.join(status)
        self.save()
        return True


    def get_absolute_url_import(self):
        return reverse('url_import_abs_conf', args=[str(self.id)])


class ABSBeam(models.Model):

    name         = models.CharField(max_length=60, default='Beam')
    antenna      = models.CharField(verbose_name='Antenna', max_length=1000, default=antenna_default)
    abs_conf     = models.ForeignKey(ABSConfiguration, null=True, verbose_name='ABS Configuration')
    tx           = models.CharField(verbose_name='Tx', max_length=1000, default=tx_default)
    rx           = models.CharField(verbose_name='Rx', max_length=1000, default=rx_default)
    s_time       = models.TimeField(verbose_name='Star Time', default='00:00:00')
    e_time       = models.TimeField(verbose_name='End Time', default='23:59:59')
    ues          = models.CharField(verbose_name='Ues', max_length=100, default=ues_default)
    only_rx      = models.CharField(verbose_name='Only RX', max_length=40, default=onlyrx_default)

    class Meta:
        db_table = 'abs_beams'

    def __unicode__(self):
        return u'%s' % (self.name)

    def parms_to_dict(self):

        parameters = {}
        parameters['name']          = self.name
        parameters['antenna']       = ast.literal_eval(self.antenna)
        parameters['abs_conf']      = self.abs_conf.name
        parameters['tx']            = ast.literal_eval(self.tx)
        parameters['rx']            = ast.literal_eval(self.rx)
        parameters['s_time']        = self.s_time.strftime("%H:%M:%S")
        parameters['e_time']        = self.e_time.strftime("%H:%M:%S")
        parameters['ues']           = ast.literal_eval(self.ues)
        parameters['only_rx']       = json.loads(self.only_rx)

        return parameters

    def dict_to_parms(self, parameters):

        self.name     = parameters['name']
        self.antenna  = json.dumps(parameters['antenna'])
        #self.abs_conf = parameters['abs_conf']
        self.tx       = json.dumps(parameters['tx'])
        self.rx       = json.dumps(parameters['rx'])
        #self.s_time = parameters['s_time']
        #self.e_time = parameters['e_time']
        self.ues      = json.dumps(parameters['ues'])
        self.only_rx     = json.dumps(parameters['only_rx'])
        self.save()


    def clone(self, **kwargs):

        self.pk = None
        self.id = None
        for attr, value in kwargs.items():
            setattr(self, attr, value)

        self.save()

        return self


    def module_6bits(self, module):
        """
        This function reads antenna pattern and choose 6bits (upbits-downbits) for one abs module
        """
        module += 1
        if module > 64:
            beam_bits = ""
            return beam_bits

        data      = ast.literal_eval(self.antenna)
        up_data   = data['antenna_up']
        down_data = data['antenna_down']

        pos        = ip2position(module)
        up_value   = up_data[pos[0]][pos[1]]
        down_value = down_data[pos[0]][pos[1]]

        up_bits   = up_conv_bits(up_value)
        down_bits = down_conv_bits(down_value)
        beam_bits = up_bits+down_bits

        return beam_bits


    @property
    def get_upvalues(self):
        """
        This function reads antenna pattern and show the up-value of one abs module
        """

        data      = ast.literal_eval(self.antenna)
        up_data   = data['antenna_up']

        up_values = []
        for data in up_data:
            for i in range(0,8):
                up_values.append(data[i])

        return up_values

    @property
    def antenna_upvalues(self):
        """
        This function reads antenna pattern and show the up - values of one abs beam
        in a particular order
        """
        data      = ast.literal_eval(self.antenna)
        up_data   = data['antenna_up']

        return up_data

    @property
    def antenna_downvalues(self):
        """
        This function reads antenna pattern and show the down - values of one abs beam
        in a particular order
        """
        data      = ast.literal_eval(self.antenna)
        down_data   = data['antenna_down']

        return down_data

    @property
    def get_downvalues(self):
        """
        This function reads antenna pattern and show the down-value of one abs module
        """

        data      = ast.literal_eval(self.antenna)
        down_data = data['antenna_down']

        down_values = []
        for data in down_data:
            for i in range(0,8):
                down_values.append(data[i])

        return down_values

    @property
    def get_up_ues(self):
        """
        This function shows the up-ues-value of one beam
        """
        data      = ast.literal_eval(self.ues)
        up_ues = data['up']

        return up_ues

    @property
    def get_down_ues(self):
        """
        This function shows the down-ues-value of one beam
        """
        data      = ast.literal_eval(self.ues)
        down_ues = data['down']

        return down_ues

    @property
    def get_up_onlyrx(self):
        """
        This function shows the up-onlyrx-value of one beam
        """
        data      = json.loads(self.only_rx)
        up_onlyrx = data['up']

        return up_onlyrx

    @property
    def get_down_onlyrx(self):
        """
        This function shows the down-onlyrx-value of one beam
        """
        data      = json.loads(self.only_rx)
        down_onlyrx = data['down']

        return down_onlyrx

    @property
    def get_tx(self):
        """
        This function shows the tx-values of one beam
        """
        data = json.loads(self.tx)

        return data

    @property
    def get_uptx(self):
        """
        This function shows the up-tx-values of one beam
        """
        data = json.loads(self.tx)
        up_data = data['up']

        up_values = []
        for data in up_data:
            for i in range(0,8):
                up_values.append(data[i])

        return up_values

    @property
    def get_downtx(self):
        """
        This function shows the down-tx-values of one beam
        """
        data = json.loads(self.tx)
        down_data = data['down']

        down_values = []
        for data in down_data:
            for i in range(0,8):
                down_values.append(data[i])

        return down_values



    @property
    def get_rx(self):
        """
        This function shows the rx-values of one beam
        """
        data = json.loads(self.rx)

        return data

    @property
    def get_uprx(self):
        """
        This function shows the up-rx-values of one beam
        """
        data = json.loads(self.rx)
        up_data = data['up']

        up_values = []
        for data in up_data:
            for i in range(0,8):
                up_values.append(data[i])

        return up_values

    @property
    def get_downrx(self):
        """
        This function shows the down-rx-values of one beam
        """
        data = json.loads(self.rx)
        down_data = data['down']

        down_values = []
        for data in down_data:
            for i in range(0,8):
                down_values.append(data[i])

        return down_values
