#!/usr/bin/env python3
# encoding: utf-8

#---------|---------|---------|---------|---------|---------|---------|---------|
# Copyright (C) 2018 Raul Roda <raulroda@yahoo.com>
#
# This program 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.
#
# This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
#---------|---------|---------|---------|---------|---------|---------|---------|


# TODO
# sobreescribir si bornero existe
# diferentes tipos de borneros a dibujar
# sobreescribir


## Imports
from collections import OrderedDict
from functools import cmp_to_key
import lxml.etree as etree  # python3-lxml
from operator import itemgetter as i
import re
import sys
import tkinter as tk
from tkinter import filedialog 
from tkinter import messagebox
from tkinter import ttk
import uuid as uuidly


## Globals (allows access from callbacks events)
frmFoot = None  # Has the auto-fill info
qet_project = None  # class to manage QET_Projects
data = None  # Has the terminals info of the project.


# CONSTANTS
VERSION = '1.0.4.alpha'
DEBUG = True
STRIP_LONG = 30
txt = {
    0: "- Right click to auto-fill.\n- Left click to edit.\n- <Return> after edit updates auto-fill"
}


class QETProject:
    """This class works with the XML source file of a QET Project.
    The list of terminals has dicts like:
        {uuid, block_name, segment, terminal_name, terminal_pos, 
        terminal_xref, terminal_type, conductor_name, cable, cable_cond} 
    where:
      - uuid: identificador of the terminal in the QET xml file.
      - block_name: terminal block that belong the terminal.
      - segment: to fit in a page, a terminal block is splitted.
      - terminal_name: comes from the diagram
      - terminal_pos: position. Specified in the plugin. For sorterin purposes.
      - terminal_xref: location calculated of the element.
      - terminal_type: STANDARD, GROUND, FUSE. For representing purposes.
      - conductor_name: Name of the electric hose.
      - cable_cond: name of the cable of the electric hose.
    """

    # class attributes
    QET_COL_ROW_SIZE = 25  # pixels offset for elements coord
    DEBUG = False


    def __init__(self, project_file, fromPage='', \
            toPage = '', searchImplicitsConnections = False):
        """class initializer. Parses the QET XML file.
        @param project_file: file of the QET project
        @param folio_reference_type: how to calc XRefs when recover project info:
           'A' auto. Same xref as configured in the QET diagram project.
           'D' default (%f-%l%c) i.e. 15-F4
        @param fromPage: first page in range to be processed
        @param toPage: last page in range to be processed
        @param searchImplicitsConnections: True for search implicit connections in TB creation"""

        self._qet_tree = etree.parse(project_file)

        self.qet_project_file = project_file
        self.qet_project = self._qet_tree.getroot()
        self.qet_project_file_to_save = project_file + '.qet'
        # determine xref format to use or default

        self.folio_reference_type = self.qet_project.find('.//newdiagrams'). \
                find('report').attrib['label']

        # XML version
        self.xml_version = self.qet_project.attrib['version']

        # pageOffset for folio numbers
        self.pageOffset = int (self.qet_project.attrib
                ['folioSheetQuantity'])  # offset table of contents

        # general project info
        self._totalPages = len (self.qet_project.findall('.//diagram')) + \
                self.pageOffset

        # elements type of terminal
        self._terminalElements = self._getListOfElementsByType( 'terminal' )


    def _getListOfElementsByType(self, element_type):
        """Return a list of component in library(collection) that
        have 'link_type' as element_type parameter.

        @return [] list with el names of elements that
                   are terminal as 'like_type'"""

        ret = []  # return list

        for element in self.qet_project.find('collection').iter('element'):
            definition = element[0]
            if 'link_type' in definition.attrib:
                if definition.attrib['link_type'] == element_type:
                    ret.append(element.attrib['name'])

        return list(set(ret))  # remove duplicates


    def _getElementName (self, element):
        """Returns the name of a terminal element.
        The name comes from 'dynamic_text' section.
        If not exists, the name is specified in elementInformation/label or 
        elementInformation/formula. 
        return: name of terminal"""

        for d in element.find('dynamic_texts').findall('dynamic_elmt_text'):
            if d.attrib['text_from'] == 'ElementInfo':
                return d.findtext('text')

        ## old version of QET XML diagram doesn't have dynamic text.
        label = formula = ''
        elinfos = element.find('elementInformations')
        if elinfos:
            #~ print ('---------------------------------------- 1')
            #~ print_debug_xml(elinfos)

            for t in elinfos.findall('elementInformation'):
                if t.attrib['name'] == 'label':
                    label = t.text
                if t.attrib['name'] == 'formula':
                    formula = t.text
        
        if label == None:  # attrib returns None if empty.
            label = ''
        if formula == None:
            formula = ''

        return [label, formula][label == '']


    def _getElementMetadata (self, element):
        """Returns the metadata of the terminal element.
        Al the info is Function field under 'elementInformation'
        return: {} keys: terminal_pos, terminal_type, cable, cable_conductor"""

        meta = ''
        ret = {'terminal_pos':'', 'terminal_type':'STANDARD', 'cable':'', 'cable_cond':''}
    
        ## Get meta string
        for t in element.find('elementInformations').findall('elementInformation'):
            if t.attrib['name'] == 'function':
                meta = t.text
        
        ## Getting data
        foo  = re.search('%p(\d+)(%|$)', meta)
        if foo:
            ret['terminal_pos'] = foo.group(1)

        foo  = re.search('%t([^%]*)(%|$)', meta)
        if foo:
            ret['terminal_type'] = foo.group(1)
    
        foo  = re.search('%c([^%]*)(%|$)', meta)
        if foo:
            ret['cable'] = foo.group(1)
        
        foo  = re.search('%n([^%]*)(%|$)', meta)
        if foo:
            ret['cable_cond'] = foo.group(1)
        
        return ret


    def _isValidTerminal (self, element):
        """ An element is valid if type is 'terminal' and label is like 'X1:1'
        @param element:  element  (XML etree object)
        @return: True / False"""
        
        regex_name = '\w+:\W?\w+$'
        
        if re.search(':', self._getElementName(element)):
            if 'type' in element.attrib:  # elements must have a 'type'
                for el in self._terminalElements:  # searching type
                    if re.search(el + '$', element.attrib['type']):
                        return True
        
        return False


    def _getCableNum(self, diagram, terminalId):
        """Return the cable number connected at 'terminalId' in the page 'diagram'
        @param diagram: diagram(page) XML etree object
        @param terminalId: text with the terminal Id
        @return: string whith cable  number"""

        ret = ''
        for cable in diagram.find('conductors').findall('conductor'):
            for cable_terminal in \
                    [x for x in cable.attrib if x[:8] == 'terminal' ]:
                if cable.attrib[cable_terminal] == terminalId:
                    ret = cable.attrib['num']
        return ret

    
    def _getXRef(self, diagram, element, offset_x = 0, offset_y = 0):
        """Return a string with the xreference.

        The element is specified by 'element' at page 'diagam'.
        The page number incremented in one if there are a "index" page

        @param diagram: diagram(page) XML etree object
        @param element: element XML etree object
        @param offset_x: correction of the coord x.
               Useful for Xref for the terminal of an element
        @param offset_y: correction of the coord y
        @return: string like "p-rc" (page - rowLetter colNumber)"""
        ret = self.folio_reference_type

        # get coord
        element_x = int(float(element.attrib['x'])) + int(float(offset_x))
        element_y = int(float(element.attrib['y'])) + int(float(offset_y))
        row, col = self._getXRefByCoord (diagram, element_x, element_y)
        diagram_page = str(int(diagram.attrib['order']) + self.pageOffset)

        # Change tags to real value
        if '%f' in ret:
            ret = ret.replace('%f', diagram_page)
        if '%F' in ret:
            # %F could include extra tags
            folio_label = diagram.attrib['folio']
            if '%id' in folio_label:
                folio_label = folio_label.replace('%id', diagram_page)
            if '%total' in folio_label:
                folio_label = folio_label.replace('%total', str(self._totalPages))
            if '%autonum' in folio_label:
                folio_label = folio_label.replace('%autonum', diagram_page)
            ret = ret.replace('%F', folio_label)
        if '%M' in ret:
            ret = ret.replace('%M', \
                self.qet_project.find('.//newdiagrams'). \
                find('inset').attrib['machine'])
        if '%LM' in ret:
            ret = ret.replace('%LM', \
                self.qet_project.find('.//newdiagrams'). \
                find('inset').attrib['locmach'])
        if '%l' in ret:
            ret = ret.replace('%l', row)
        if '%c' in ret:
            ret = ret.replace('%c', col)

        return ret


    def _getXRefByCoord(self, diagram, x, y):
        """Return a string with the xreference for the coordinates at page 'diagam'
        The page number incremented in one if there are a "index" page

        @param diagram: diagram(page) XML etree object
        @param x,y: coordinates
        @return: string like "p-rc" (page - rowLetter colNumber)"""

        # get requiered data
        cols = int(diagram.attrib['cols'])
        col_size = int(diagram.attrib['colsize'])
        rows = int(diagram.attrib['rows'])
        row_size = int(diagram.attrib['rowsize'])
        element_x = int(x)
        element_y = int(y)
        rows_letters = [chr(x + 65) for x in range(rows)]

        if self.DEBUG:
            print( '<getXRef>: Page order: {}\tCol size: {}\tRow size: {}\tX position: {}\tY Position: {}'. \
                format (page, col_size, row_size, element_x, element_y))

        row_letter = rows_letters[ int(
                (element_y - QETProject.QET_COL_ROW_SIZE) / row_size) - 1 + 1]
                # +1: cal calc. -1 index of lists start 0.
        column = str(int((element_x - QETProject.QET_COL_ROW_SIZE) / col_size) + 1)
        return (row_letter, column)


    def get_list_of_used_terminals(self):
        """Return a list of all terminal elements used in the qet project.
        @return list where every element is a dict. See class info.
            strip: a long terminal strip is splited into a smaller parts.
        """
        ret = []  # return dict

        # first search for elements of type 'terminal' and its conductors.
        for diagram in self.qet_project.findall('diagram'):  # all diagrams
            for element in diagram.findall('.//element'):  # all elements in diagram
                el = {}

                if self._isValidTerminal(element):

                    terminalName = self._getElementName (element)
                    meta_data = self._getElementMetadata (element)
                    
                    terminals = element.find('terminals').findall( 'terminal' )
                    terminalId = terminals[0].attrib['id']
                    cableNum = self._getCableNum(diagram, terminalId)
                    terminalId2 = terminals[1].attrib['id']
                    cableNum2 = self._getCableNum(diagram, terminalId2)
                    if cableNum == '': cableNum = cableNum2
                    
                    el['uuid'] = element.attrib['uuid']
                    el['block_name'] = terminalName.split(':')[0]
                    el['segment'] = ''
                    el['terminal_name'] = terminalName.split(':')[1]
                    el['terminal_pos'] = meta_data['terminal_pos']
                    el['terminal_xref'] = self._getXRef(diagram, element)
                    el['terminal_type'] = meta_data['terminal_type']
                    el['conductor_name'] = cableNum
                    el['cable'] = meta_data['cable']
                    el['cable_cond'] = meta_data['cable_cond']
                if el: ret.append(el)

        return ret


    def save_tb(self, data):
        """Save the array 'data' to the XML file"""
        for diagram in self.qet_project.findall('diagram'):  # all diagrams
            for element in diagram.iter('element'):  # all elements in diagram
                dt = [x for x in data if x['uuid'] == element.attrib['uuid']]
                if dt:
                    found = False
                    value = '%p{}%t{}%c{}%n{}'.format(
                            dt[0]['terminal_pos'], \
                            dt[0]['terminal_type'], \
                            dt[0]['cable'], \
                            dt[0]['cable_cond'])
                    for elinfo in element.iter('elementInformation'):
                        if elinfo.attrib['name'] == 'function':
                            elinfo.text = value
                            found = True
                    if not found:  # crete a new child
                        #~ print ('----------------------- {}'.format(element.attrib['uuid']))
                        father = element.find('elementInformations')
                        new = etree.SubElement(father, \
                                'elementInformation',
                                name="function", \
                                show="0")
                        new.text = value

        self._qet_tree.write(self.qet_project_file_to_save, pretty_print=True)


    def insert_tb(self, tb_node):
        """Inserts a xml node representing a terminal block"""
        father = self.qet_project.find('collection').find('category')
        father.insert(0,tb_node)

        self._qet_tree.write(self.qet_project_file_to_save)


class TerminalBlock:
    """This class represents a Terminal Block for a QET project.
    The list of terminals has dicts like:
        {uuid, block_name, segment, terminal_name, terminal_pos, 
        terminal_xref, terminal_type, conductor_name, cable, cable_cond}
    """

    DEBUG = False
    HEAD_WIDTH = 44
    HEAD_HEIGHT = 120
    UNION_WIDTH = 6
    UNION_HEIGHT = 70
    TERMINAL_WIDTH = 20
    TERMINAL_HEIGHT = HEAD_HEIGHT
    CONDUCTOR_LENGTH = 40
    CABLE_LENGTH = 40
    CONDUCTOR_END_LENGTH = 40
    


    def __init__(self, tb_segment, collec):
        """initializer.
        @param string tb_id: name for this terminal block (e.g. -X1)
        @param string tb_segment: longs teminal blocks are splitet to fit in a
            page. A segment has the same name that the terminal block thats
            belongs, but adding a suffix.
        @param collec: collection of terminals. Only the terminals of the
            segment 'tb_id' are accepted.
        """
        self.tb_segment = tb_segment
        self.terminals = self._addTerminals(collec)
        self.num_terminals = len(self.terminals)
        self.tb_id = self.terminals[0]['block_name']


    def _addTerminals (self, terminals):
        """Add terminals to the block, but only accept terminals that haves
        the same 'terminal block' as the defined on constructor. The order of
        the termnals is respected
        @param terminals: list of dics with terminal info. The format is
            explained in the class definition."""
        ret = []
        for ter in terminals:
            if ter['segment'] == self.tb_segment:
                ret.append(ter)
        return ret
        

    def _getNum(self, x):
        """ Returns the page part as integer of a XREF. Is there isn't digits,
        return 9999. Usefull for sort reasons.
        e.g. '12-B8' """

        foo = x.split('-')[0]
        if foo.isdigit():
            return int(foo)
        else:
            return 9999


    def _get_empty_terminal(self, terminal_name=''):
        """Returns a list corresponding a new empty terminalself.

        The new terminal haves the same teminal_block_name.

        @param terminal_name: name/number for the terminal block
        @return: valid list format for a terminal.
        """
        # [element_uuid, terminal_block_name, terminal_name/number, terminal_xref,
        # NORTH cable id side 1, N.cable id side 2, N.cable num, N. cable destination xref,
        # SOUTH cable id side 1, S.cable id side 2, S.cable num, S. cable destination xref]
        return ['', self.tb_id, str(terminal_name), '', \
                '', '', self.config['reservation_label'], '', \
                '', '', self.config['reservation_label'], '']


    def _generate_reservation_numbers(self):
        """Creates new terminals ID for gaps if exist.

        Only check gaps for numerical ID's (not for +1, -0,...).
        The list of terminal_numbers comes from a unique block terminal,
        i.e. X1, X12,...

        NOTE: Modify self.terminals
        @return list with gaps filled and sorted.
        """

        only_numbers = [int(x[self._IDX_TERM_NAME_])
            for x in self.terminals if x[self._IDX_TERM_NAME_].isdigit()]
        only_numbers.sort()
        if self.DEBUG:
            print ("<drawTerminalBlock> Reservation - {}". \
                format(only_numbers))

        print ('{}'.format(self.terminals[0]))

        if only_numbers:  # if the are digits in terminals numeration
            for i in range(1, int(only_numbers[-1])):
                if i not in only_numbers:
                    self.terminals.append( self._get_empty_terminal(i))


    def drawTerminalBlock(self):
        """
        Creates a XML node of the terminal block.
        coord (0,0) al corner upper-left

        @(param) self.terminals
        @return: none"""

        # calc some values    
        name = 'TB'+ self.tb_segment        
        total_width = TerminalBlock.HEAD_WIDTH + \
                TerminalBlock.UNION_WIDTH + \
                self.num_terminals * TerminalBlock.TERMINAL_WIDTH + \
                1  # +1 to force round the next tenth
        while (total_width % 10): total_width += 1
        total_height = TerminalBlock.TERMINAL_HEIGHT + \
                TerminalBlock.CONDUCTOR_LENGTH + \
                TerminalBlock.CABLE_LENGTH + \
                TerminalBlock.CONDUCTOR_END_LENGTH + 20 + \
                1  # +1 to force round the next tenth
        while (total_height % 10): total_height += 1
        sUUID = uuidly.uuid1().urn[9:]
        rect_style = 'line-style:normal;line-weight:normal;filling:white;color:black'
        line_style = 'line-style:normal;line-weight:normal;filling:none;color:black'

        # define the element
        """Save the array 'data' to the XML file"""
        root = etree.Element('element', name=name + '.elmt')
        
        definition = etree.SubElement(root, "definition", \
                height = str(total_height) , \
                width = str(total_width), \
                hotspot_x = '5', hotspot_y = '24', \
                link_type = 'simple', \
                orientation = 'dyyy' ,\
                version = '0.4', \
                type='element')
        uuid = etree.SubElement(definition, 'uuid', uuid=sUUID)
        
        names = etree.SubElement(definition, 'names')
        lang1 = etree.SubElement(names, 'name', lang='de')
        lang1.text = 'Terminalblock ' + name
        lang2 = etree.SubElement(names, 'name', lang='ru')
        lang2.text = '&#x422;&#x435;&#x440;&#x43C;&#x438;&#x43D;&#x430;&#x43B;&#x44C;&#x43D;&#x44B;&#x439; &#x431;&#x43B;&#x43E;&#x43A; ' + name
        lang3 = etree.SubElement(names, 'name', lang='pt')
        lang3.text = 'Bloco terminal ' + name
        lang4 = etree.SubElement(names, 'name', lang='en')
        lang4.text = 'Terminal block ' + name
        lang5 = etree.SubElement(names, 'name', lang='it')
        lang5.text = 'Terminal block ' + name
        lang6 = etree.SubElement(names, 'name', lang='fr')
        lang6.text = 'Bornier ' + name
        lang7 = etree.SubElement(names, 'name', lang='pl')
        lang7.text = 'Blok zacisk&#xF3;w ' + name
        lang8 = etree.SubElement(names, 'name', lang='es')
        lang8.text = 'Bornero ' + name
        lang9 = etree.SubElement(names, 'name', lang='nl')
        lang9.text = 'Eindblok ' + name
        lang10 = etree.SubElement(names, 'name', lang='cs')
        lang10.text = 'Termin&#xE1;lov&#xFD; blok ' + name

        informations = etree.SubElement(definition, 'informations')
        informations.text = 'Terminal block'
        
        description = etree.SubElement(definition, 'description')

        # element label
        label = etree.SubElement(description, 'dynamic_text', \
                x=str(TerminalBlock.HEAD_WIDTH + 5), \
                y=str(TerminalBlock.HEAD_HEIGHT + 5), \
                z='2', \
                text_from='ElementInfo', text_width='-1', \
                uuid = uuidly.uuid1().urn[9:], \
                font_size='10', frame='false')
        label_text = etree.SubElement(label, 'text')
        label_text.text = self.tb_id
        label_info = etree.SubElement(label, 'info_name')
        label_info.text = 'label'
                
        # draw TB header
        x = 0
        hd = etree.SubElement(description, 'rect', \
                x=str(x), y='0', \
                width=str(TerminalBlock.HEAD_WIDTH), \
                height=str(TerminalBlock.HEAD_HEIGHT), \
                antialias='false', style=rect_style)
        hd_label = etree.SubElement(description, 'text', \
                x = str( (TerminalBlock.HEAD_WIDTH / 2) + 5), \
                y = str(TerminalBlock.HEAD_HEIGHT / 2 + (len(self.tb_id) / 2) * 10), \
                rotation='270', \
                size='10', \
                text=self.tb_id)
        
        x += TerminalBlock.HEAD_WIDTH
        y = (TerminalBlock.HEAD_HEIGHT - TerminalBlock.UNION_HEIGHT) / 2
        un = etree.SubElement(description, 'rect', \
                x=str(x), \
                y=str(y), \
                width=str(TerminalBlock.UNION_WIDTH), \
                height=str(TerminalBlock.UNION_HEIGHT), \
                antialias='false', style=rect_style)   
                
        # process every Term- Block of max length per folio
        x += TerminalBlock.UNION_WIDTH
        last_cable_name = self.terminals[0]['cable']  # first cable name
        last_cable_coord_x = x + TerminalBlock.TERMINAL_WIDTH / 2
        for i in range(0, self.num_terminals):
            trmnl = self.terminals[i]
            halfx = x + TerminalBlock.TERMINAL_WIDTH / 2
                
            term = etree.SubElement(description, 'rect', \
                    x=str(x), \
                    y='0', \
                    width=str(TerminalBlock.TERMINAL_WIDTH), \
                    height=str(TerminalBlock.TERMINAL_HEIGHT), \
                    antialias='false', style=rect_style)   
            term_label = etree.SubElement(description, 'text', \
                    x=str(halfx + 4), \
                    y=str(TerminalBlock.TERMINAL_HEIGHT - 30), \
                    rotation='270', \
                    size='9', \
                    text=trmnl['terminal_name'])
            term_xref_label = etree.SubElement(description, 'text', \
                    x=str(halfx + 4), \
                    y=str(40), \
                    rotation='270', \
                    size='7', \
                    text=trmnl['terminal_xref'])
                    
            north_cable = etree.SubElement(description, 'line', \
                    x1 = str(halfx), x2 = str(halfx), \
                    y1 = '0', y2 = '-20', \
                    length1 = '1.5', length2 = '1.5', \
                    end1 = 'none', end2 = 'none', \
                    antialias = 'false', style = line_style)
            north_cable_label = etree.SubElement(description, 'text', \
                    x=str(halfx + 8), \
                    y=str(-2), \
                    rotation='270', \
                    size='6', \
                    text=trmnl['conductor_name'])
            north_terminal = etree.SubElement(description, 'terminal', \
                    x=str(halfx), \
                    y=str(-20), \
                    orientation='n')
      
            # draw south conductor
            south_cable = etree.SubElement(description, 'line', \
                    x1 = str(halfx), x2 = str(halfx), \
                    y1 = str(TerminalBlock.TERMINAL_HEIGHT), \
                    y2 = str(TerminalBlock.TERMINAL_HEIGHT + \
                            TerminalBlock.CONDUCTOR_LENGTH), \
                    length1 = '1.5', length2 = '1.5', \
                    end1 = 'none', end2 = 'none', \
                    antialias = 'false', style = line_style)
            south_cable_label = etree.SubElement(description, 'text', \
                    x=str(halfx + 8), \
                    y=str(TerminalBlock.TERMINAL_HEIGHT + \
                            TerminalBlock.CONDUCTOR_LENGTH / 2), \
                    rotation='270', \
                    size='6', \
                    text=trmnl['cable_cond'])
                                  
            y1 = TerminalBlock.TERMINAL_HEIGHT + \
                    TerminalBlock.CONDUCTOR_LENGTH + \
                    TerminalBlock.CABLE_LENGTH
            y2 = TerminalBlock.TERMINAL_HEIGHT + \
                    TerminalBlock.CONDUCTOR_LENGTH + \
                    TerminalBlock.CABLE_LENGTH + \
                    TerminalBlock.CONDUCTOR_END_LENGTH
            south_cable_end = etree.SubElement(description, 'line', \
                    x1 = str(halfx), x2 = str(halfx), \
                    y1 = str(y1), \
                    y2 = str(y2), \
                    length1 = '1.5', length2 = '1.5', \
                    end1 = 'none', end2 = 'none', \
                    antialias = 'false', style = line_style)
            south_cable_end_label = etree.SubElement(description, 'text', \
                    x=str(halfx + 8), \
                    y=str(y1 + (y2-y1)/2), \
                    rotation='270', \
                    size='6', \
                    text=trmnl['cable_cond'])
            south_terminal = etree.SubElement(description, 'terminal', \
                    x=str(halfx), \
                    y=str(y2), \
                    orientation='s')


            # draw representation of the cable of conductors
            if (trmnl['cable'] != last_cable_name or \
                    i == self.num_terminals - 1): # change cable or last term.
                x1 = last_cable_coord_x
                x2 = x - (TerminalBlock.TERMINAL_WIDTH / 2)
                y1 = TerminalBlock.TERMINAL_HEIGHT + TerminalBlock.CONDUCTOR_LENGTH
                y2 = TerminalBlock.TERMINAL_HEIGHT + \
                        TerminalBlock.CONDUCTOR_LENGTH + \
                        TerminalBlock.CABLE_LENGTH
                if i == self.num_terminals - 1: x2 = halfx
                        
                hor_line1 = etree.SubElement(description, 'line', \
                        x1 = str(x1), \
                        x2 = str(x2), \
                        y1 = str(y1), \
                        y2 = str(y1), \
                        length1 = '1.5', length2 = '1.5', \
                        end1 = 'none', end2 = 'none', \
                        antialias = 'false', style = line_style)
                hor_line2 = etree.SubElement(description, 'line', \
                        x1 = str(x1), \
                        x2 = str(x2), \
                        y1 = str(y2), \
                        y2 = str(y2), \
                        length1 = '1.5', length2 = '1.5', \
                        end1 = 'none', end2 = 'none', \
                        antialias = 'false', style = line_style)
                ver_line = etree.SubElement(description, 'line', \
                        x1 = str((x1+x2)/2), \
                        x2 = str((x1+x2)/2), \
                        y1 = str(y1), \
                        y2 = str(y2), \
                        length1 = '1.5', length2 = '1.5', \
                        end1 = 'none', end2 = 'none', \
                        antialias = 'false', style = line_style)
                ver_line_label = etree.SubElement(description, 'text', \
                        x = str((x1+x2)/2 + 8), \
                        y = str(y1 + ((y2-y1)/2) + len(last_cable_name)*3), \
                        rotation = '270', \
                        size = '6', \
                        text = last_cable_name)

                last_cable_coord_x = halfx
                
            # task at loop end
            x += TerminalBlock.TERMINAL_WIDTH
            last_cable_name = trmnl['cable']

        etree.ElementTree(root).write('tmp.xml', pretty_print=True)
        return root


def createGUI(root):
    """Create the main window
    @param root: main window"""
    # define main window

            
def right_on_field(wdg_clicked, wdg_autofill):
    """establecemos el texto del campo auto-fill"""
    val = wdg_autofill['text']
   
    wdg_clicked.delete(0, "end")
    wdg_clicked.insert(0, val)
    if val.isdigit():
        wdg_autofill['text'] = str( int(wdg_autofill['text']) + 1)


def return_on_field(wdg_clicked, wdg_autofill):
    """actualizamos auto-fill tras edicion"""
    val = wdg_clicked.get()
    if val.isdigit():
        wdg_autofill['text'] = str( int(val) + 1)
    else:
        wdg_autofill['text'] = str(val)


def right_on_pos(event):
    right_on_field ( event.widget, frmFoot.winfo_children()[0] )


def right_on_type(event):
    right_on_field ( event.widget, frmFoot.winfo_children()[5] )


def right_on_cable(event):
    right_on_field ( event.widget, frmFoot.winfo_children()[6] )


def right_on_cond(event):
    right_on_field ( event.widget, frmFoot.winfo_children()[7] )

            
def return_on_pos(event):
    return_on_field( event.widget, frmFoot.winfo_children()[0])


def return_on_type(event):
    return_on_field( event.widget, frmFoot.winfo_children()[5])


def return_on_cable(event):
    return_on_field( event.widget, frmFoot.winfo_children()[6])


def return_on_cond(event):
    return_on_field( event.widget, frmFoot.winfo_children()[7])


def click_on_save(event):
    """Event button.
    Inserts terminal block into XML structure.
    Saves project."""
    
    global wdg_data
    
    click_on_reorder(event)  # this updates wdg_data
    data_from_ui = convert_from_control_var()

    ## Loop segments
    segments = list(OrderedDict.fromkeys([x['segment'] for x in data_from_ui]))
    for seg in segments:
        a_block = TerminalBlock(seg, data_from_ui)
        qet_project.insert_tb (a_block.drawTerminalBlock())  # creates XML element file

    qet_project.save_tb(convert_from_control_var())
    messagebox.showinfo("QET", "Document saved as {}". \
            format(qet_project.qet_project_file_to_save))


def print_tb(data):
    if DEBUG:
        print ('{}\t{}\t{}\t{}\t{}\t{}'.format( \
            'Segm.', \
            'Blk', \
            'Ter.Pos', \
            'Cable', \
            'Conduct', \
            'Ter.Nam'))
        for it in data:
            print ('{}\t{}\t{}\t{}\t{}\t{}'.format( \
                    it['segment'], \
                    it['block_name'], \
                    it['terminal_pos'], \
                    it['cable'], \
                    it['cable_cond'], \
                    it['terminal_name']))
    
            
def click_on_reorder(event):
    """Re-sort data according the data edited by the user in the ui
    """    
    global wdg_data

    
    ## Converting to number, sorting  and converting to a widgets dicc
    data_from_ui = convert_from_control_var()
    data_sorted = sort_terminals(data_from_ui)

    # Put data again in entry widgets
    for row in range(len(data_sorted)):
        for key in data_sorted[row].keys():
            wdg_data[row][key].set(data_sorted[row][key])
        #~ ## wdg_data = convert_to_control_var(data_sorted)
    #~ ## print_tb(wdg_data)


    #~ messagebox.showinfo("QET", \
            #~ "This will redraw the table according the POS. column values.")
  

def sort_terminals(data):
    """Terminals are sorter by: block_name, terminal_pos, cable, 
    cable_cond, terminal_name.
    Later, if a terminal block is longer than STRIP_LONG, will be split
    using 'strip' field.
    @param data: lista de diccionarios con los datos de los terminales.
    """
    ret = []
    
    # convert values to int
    data = convert_numerical(data)
    
    # spliting if needed
    terms = list(OrderedDict.fromkeys([x['block_name'] for x in data])) # no dupl.
    for t in terms:
        tb = [x for x in data if x['block_name'] == t]
        tb_sorted = multikeysort(tb, \
                ['terminal_pos', 'cable', 'cable_cond', 'terminal_name'])
        segment_label = 0
        for i in range(0, len(tb), STRIP_LONG):
            segment_label += 1  # segment label
            segment = tb_sorted[i:i+STRIP_LONG]
            
            # Labeling strip
            for item in segment:
                item['segment'] = '{}({})'.format(t, segment_label)
            
            ret += segment
    return ret


def to_int(data):
    try: 
        int(data)
        return int(data)
    except ValueError:
        return data


def convert_numerical(data):
    """Convert string values to integers. This allows 'more intelligent'
    sorting"""
    for ter in data:
        ter['terminal_name'] = to_int( ter['terminal_name'] )
        ter['terminal_pos'] = to_int( ter['terminal_pos'] )
        ter['cable'] = to_int( ter['cable'] )
        ter['cable_cond'] = to_int( ter['cable_cond'] )
    return data


def cmp(a, b):
    """Returns 0, 1(a>b) or  -1(a<b)
    """
    if type(a) == type(b):
        return (a > b) - (a < b)
    elif (type(a) is int) and (type(b) is str):
        return -1
    elif (type(b) is int) and (type(a) is str):
        return 1


def multikeysort(items, columns):
    """ Sort list of dict using diferent keys. From
    https://stackoverflow.com/questions/1143671/
    python-sorting-list-of-dictionaries-by-multiple-keys
    @param items: dict to sort
    @param colums: list with keys names of dict
    """
    comparers = [
        ((i(col[1:].strip()), -1) if col.startswith('-') else (i(col.strip()), 1))
        for col in columns
    ]
    def comparer(left, right):
        comparer_iter = (
            cmp(fn(left), fn(right)) * mult
            for fn, mult in comparers
        )
        return next((result for result in comparer_iter if result), 0)
    return sorted(items, key=cmp_to_key(comparer))


def initUI(root, data):
    """Creates the UI
    param root: root frame
    param data: list of a dictionary for every terminal
      {uuid, block_name, terminal_name, terminal_pos, 
            terminal_xref, terminal_type, conductor_name, cable, cable_cond}
    """
    global frmFoot
    global wdg_data

    GRID_CONFIG = [
    {'TITLE':'POS.', 'WIDTH': 8, 'AUTOFILL': '1', 'dicc_index':'terminal_pos', \
        'right':right_on_pos,'return':return_on_pos}, \
    {'TITLE':'TERM.BLOCK', 'WIDTH': 13, 'AUTOFILL': '', 'dicc_index':'block_name', \
        'right':'','return':''}, \
    {'TITLE':'ID', 'WIDTH': 8, 'AUTOFILL': '', 'dicc_index':'terminal_name', \
        'right':'','return':''}, \
    {'TITLE':'XREF', 'WIDTH': 8, 'AUTOFILL': '', 'dicc_index':'terminal_xref', \
        'right':'','return':''}, \
    {'TITLE':'CONDUCTOR', 'WIDTH': 12, 'AUTOFILL': '', 'dicc_index':'conductor_name', \
        'right':'','return':''}, \
    {'TITLE':'TYPE', 'WIDTH': 10, 'AUTOFILL': 'GROUND', 'dicc_index':'terminal_type', \
        'right':right_on_type,'return':return_on_type}, \
    {'TITLE':'CABLE', 'WIDTH': 10, 'AUTOFILL': '-W1', 'dicc_index':'cable', \
        'right':right_on_cable,'return':return_on_cable}, \
    {'TITLE':'CABLE COND.', 'WIDTH': 13, 'AUTOFILL': '1', 'dicc_index':'cable_cond', \
        'right':right_on_cond,'return':return_on_cond}, \
    ]
    #~ {'TITLE':'UUID', 'WIDTH': 13, 'AUTOFILL': '', 'dicc_index':'uuid', \
        #~ 'right':'','return':''} \
 
    ## config root window
    root.title("QET - Terminal block generator - v{}".format(VERSION))
    root.resizable(True, True)  # width, height
    #~ img = tk.PhotoImage(file='qet.png')
    #~ root.tk.call('wm', 'iconphoto', root._w, img)
    #~ root.iconbitmap("qet.ico")  # only b/n ico for linux
    
    ## frame header
    frmHeader = tk.Frame(root, borderwidth=1, relief='solid')
    tk.Label(frmHeader, text=txt[0], justify='left').pack(side='left')
    
    b1 = tk.Button(frmHeader,text='CREATE TERMINAL BLOCKS')
    b1.pack(side='right')
    b1.bind("<Button-1>", click_on_save)
    
    b2 = tk.Button(frmHeader,text='REFRESH')
    b2.pack(side='right')
    b2.bind("<Button-1>", click_on_reorder)
    
    ## TABS
    tabs = ttk.Notebook(root)  # populates 'segment' field

    segments = list(OrderedDict.fromkeys([x['segment'] for x in data])) # no dupl.
    wdg_data_index = 0
    for seg in segments:
        ## frame data
        frmData = tk.Frame(tabs)
        
        ## caption
        for c in range(len(GRID_CONFIG)):
            tk.Label(frmData, text=GRID_CONFIG[c]['TITLE'], \
                     width = GRID_CONFIG[c]['WIDTH'], \
                     font=('Helvetica', 10, 'bold underline')).grid(row=0, column=c)
        offset = 1
        terminals = [x for x in data if x['segment'] == seg]  # choose a segment
        tab_row = 0
        for ter in terminals:
            for c in range(len(GRID_CONFIG)):
                lbl = tk.Entry(frmData, borderwidth=0, \
                        width = GRID_CONFIG[c]['WIDTH']-1, \
                        justify = 'center', fg='blue', bg='gray85', \
                        textvariable = wdg_data[wdg_data_index][GRID_CONFIG[c]['dicc_index']])
                lbl.grid(row=tab_row+offset,column=c)
                
                if GRID_CONFIG[c]['AUTOFILL'] == '':
                    lbl.config(state='disabled', disabledforeground = 'black', \
                            disabledbackground='gray85')
                    
                if GRID_CONFIG[c]['right'] != '':
                    lbl.bind("<Button-3>",GRID_CONFIG[c]['right'])
                    lbl.bind("<Return>",GRID_CONFIG[c]['return'])
            tab_row += 1
            wdg_data_index += 1
        tabs.add(frmData, text=seg)

    ## Config tab
    frmConfig = tk.Frame(tabs)
    tk.Label(frmConfig, text='This is the config tab').pack()
    tabs.add(frmConfig, text='Config')

    ## frame foot
    frmFoot = tk.Frame(root, borderwidth=1, relief='solid')
    for c in range(len(GRID_CONFIG)):
        t = GRID_CONFIG[c]['AUTOFILL']
        tk.Label(frmFoot, text=t, padx=2, \
                 width = GRID_CONFIG[c]['WIDTH'], \
                 font=('Helvetica', 10, 'bold'), fg='gray').grid(row=0, column=c)
    
    ## Packing
    frmHeader.pack(anchor='n', fill='x', expand='True', pady=6, padx=6)
    tabs.pack(fill='x', expand='True',pady=6, padx=6)
    frmFoot.pack(anchor='s', fill='x', expand='True', pady=6, padx=6)


def get_QET_path(wdg_root):
    """Returns the QET project file from command line or file dialog"""
    
    ## Get QET path from command line or show dialog to choose one.
    if len(sys.argv) == 1:  # first data is the prg name
        ftypes = [('QET', '*.qet'), ('All files', '*')]
        dlg = filedialog.Open(wdg_root, filetypes = ftypes)
        fl = dlg.show()

        if fl != '':
            return fl
    else:
        return sys.argv[1]


def convert_to_control_var(data_array):
    """Converts an array of lists to a control variables array to easy
    access to/from UI widgets"""
    ret = []

    for r in range(len(data_array)):
        a_row = {}
        for key in data_array[r].keys():
            a_row[key] = tk.StringVar(value=data_array[r][key])
        ret.append(a_row)

    return ret
    
    
def convert_from_control_var():
    """Converts an array of lists of control variables to its values.
    @ param global wdg_data: global variable that hosts widgets data
    """
    global wdg_data
    
    ret = []
    for r in range(len(wdg_data)):
        a_row = {}
        for key in wdg_data[r].keys():
            a_row[key] = wdg_data[r][key].get()
        ret.append(a_row)
    return ret
    
    
def print_debug_xml(element):
    if DEBUG:
        print (etree.tostring(element).decode())
    
     
def main():
    global qet_project
    global wdg_data  
    
    ## UI Root
    wdg_root = tk.Tk(  )
    
    ## Get QET path from command line or show dialog to choose one.
    qet_file = get_QET_path(wdg_root)
    
    ## QET Project
    qet_project = QETProject(qet_file)  # allow working with a QET XML file.
    qet_termials_used = qet_project.get_list_of_used_terminals()
    
    ## Converting to number, sorting splitting and converting to a widgets dicc
    qet_termials_used = sort_terminals(qet_termials_used)
    wdg_data = convert_to_control_var(qet_termials_used)
    
    ## UI
    initUI(wdg_root, qet_termials_used)
    
    ## main loop
    wdg_root.mainloop(  )


if __name__ == '__main__':
    main()
