#!/usr/bin/python
# ----------------------------------------------------------------------------------------------
# TODO
# Reverse Item from oldest to newest sorting on date
# Source selector from gpio button
# ----------------------------------------------------------------------------------------------
g_xmldebug= False
# ----------------------------------------------------------------------------------------------
g_dbg= False
g_tst= False
# ----------------------------------------------------------------------------------------------
h_ser=0
h_aud=0
# ----------------------------------------------------------------------------------------------
# nam   Name for that stream
# snd   Sound to be played
# url   URL of the stream
# div   xpath to get every division / record
# dat   where is the date  in this division
# tit   where is the title in this division
# inf   where is the info  in this division
# xml   True is XML
# ----------------------------------------------------------------------------------------------
nam_1 = "Reuters"
snd_1 = "ReutersFeed.wav"
url_1 = "http://www.reuters.com/news/archive/topNews"
div_1 = "//div[@class='story-content']"
dat_1 = ".//span[@class='timestamp']"
tit_1 = ".//h3[@class='story-title']"
inf_1 = "./p"
xml_1 = False
# ----------------------------------------------------------------------------------------------
nam_2 = "AFP"
snd_2 = "AFPFeed.wav"
url_2 = "https://www.afp.com/en/news-hub"
div_2 = "//div[starts-with(@id,'afp_new')]"
dat_2 = "./div[@class]/span[@class]"
tit_2 = "./div[@class]/h4[starts-with(@class,'htitle')]/a"
inf_2 = "./div[@class]/div[starts-with(@class,'showt')]/p"
xml_2 = False
# ----------------------------------------------------------------------------------------------
nam_3 = "CERT-EU"
snd_3 = "CERTFeed.wav"
url_3 = "https://cert.europa.eu/rss?type=category&id=CERT-LatestNews&language=all&duplicates=false"
div_3 = "//item"
dat_3 = "./pubdate" 
tit_3 = "./title" 
inf_3 = "./description"  
xml_3 = True
# ----------------------------------------------------------------------------------------------
nam_4 = "@realDonaldTrump"
snd_4 = "TwitterFeed.wav"
url_4 = "https://twitter.com/realDonaldTrump"
div_4 = "//div[@class='content']"
dat_4 = "./div/small[@class='time']/a[contains(@class,'tweet-timestamp')]/span"
tit_4 = "./div[@class='js-tweet-text-container']/p"
inf_4 = "./div//strong[contains(@class,'fullname')]"
xml_4 = False
# ==============================================================================================
# Stream design            
# Runs until end of stream or killed                        
# ==============================================================================================
class Stream:
    def __init__(self):
        self.running=False
        self.exists=True
        self.pid=0
        self.set_player("omxplayer")
    # ------------------------------------------------------------------------------------------
    def get_running(self):
        return self.running
    # ------------------------------------------------------------------------------------------
    def get_player(self):
        return self.player
    def set_player(self,x):
        try:
            res=os.system(x + " -v >/dev/null 2>&1")
            self.player=x
            if (res!=0):
                raise
        except:
            self.player=""
            self.exists=False
            print "ERROR: NO STREAM PLAYER"
            exit(2)
    # ------------------------------------------------------------------------------------------        
    def Play(self,name):        
        import subprocess
        if (self.exists):
            devnull=open('/dev/null', 'w')
        try:
                self.running=True
                self.p=subprocess.Popen([self.player,name],preexec_fn=os.setsid,stdout=devnull, shell=False)
                self.pid=self.p.pid
                print "INFO:  STREAM PROCESS STARTED"
                self.Wait()
                self.running=False
                print "INFO:  STREAM PROCESS ENDED"
        except:
                self.running=False
                print "ERROR: STREAM PROCESS NOT STARTED"
    # ------------------------------------------------------------------------------------------        
    def Kill(self):        
        import signal
        if (self.running):
            try:
                os.killpg(os.getpgid(self.pid), signal.SIGTERM)
                self.Wait()
                print "INFO:  STREAM PROCESS KILLED"
            except:
                print "ERROR: STREAM PROCESS NOT KILLED"
    # ------------------------------------------------------------------------------------------        
    def Wait(self):        
        self.p.wait()
# ==============================================================================================
# Sound design 
# ==============================================================================================
class Sound:
    import os
    def __init__(self):
        self.running=False
        self.exists=True
        self.set_player("aplay")
        self.set_path("./sound/")
    # ------------------------------------------------------------------------------------------
    def get_running(self):
        return self.running
    # ------------------------------------------------------------------------------------------
    def get_path(self):
        return self.path
    def set_path(self,x):
        if (os.path.exists(x)):
            self.path=x
        else:
            self.path=""
    # ------------------------------------------------------------------------------------------
    def get_player(self):
        return self.player
    def set_player(self,x):
        self.exists=True
        try:
            res=os.system(x + " -h >/dev/null 2>&1")
            self.player=x
            if (res!=0):
                raise
        except:
            self.player=""
            self.exists=False
            print "ERROR: NO AUDIO PLAYER"
            exit(2)
    # -------------------------------------------------------------------------------------------        
    def Play(self,name):        
        import subprocess
        if (self.exists):
            devnull=open('/dev/null', 'w')
        try:
                self.running=True
                self.p=subprocess.Popen([self.player,"-q",self.path + name],preexec_fn=os.setsid,stdout=devnull, shell=False)                
                self.pid=self.p.pid
#               print "INFO:  AUDIO  PROCESS STARTED"
                self.Wait()
                self.running=False
#               print "INFO:  AUDIO  PROCESS ENDED"
        except:
                self.running=False
                print "ERROR: AUDIO  PROCESS NOT STARTED"
    # ------------------------------------------------------------------------------------------        
    def Kill(self):        
        import signal
        if (self.running):
            try:
                os.killpg(os.getpgid(self.pid), signal.SIGTERM)
                self.Wait()
                print "INFO:  AUDIO  PROCESS KILLED"
            except:
                print "ERROR: AUDIO  PROCESS NOT KILLED"
    # ------------------------------------------------------------------------------------------        
    def Wait(self):        
        self.p.wait()
# ==============================================================================================
# Serial design
# ==============================================================================================
class Serial:
    global g_dbg
    import serial
    from   sys import platform
    # ------------------------------------------------------------------------------------------
    def __init__(self):
        self.ser      = 0
        self.running  = False
        self.exists   = False
        self.port     = "NONE"
        self.baud     = 50
        self.bytesize = self.serial.FIVEBITS
        self.parity   = self.serial.PARITY_NONE
        self.stopbits = self.serial.STOPBITS_ONE_POINT_FIVE
        # --------------------------------------------------------------------------------------
        # Get system information
        # --------------------------------------------------------------------------------------
        self.syst=platform.lower()
        if "win"     in self.syst:
            self.port = "COM1"
        if "linux" or "freebsd" in self.syst:
            self.port = "/dev/serial0"
        # --------------------------------------------------------------------------------------
        # Try to open and default to classic ASCII output if no serial is there
        # --------------------------------------------------------------------------------------
        try:
            self.ser = self.serial.Serial(self.port,self.baud)
            self.ser.reset_output_buffer()
            self.ser.close()
            self.exists=True
        except:
            self.ser = 0
            self.exists=False
            print "ERROR: NO SERIAL DEVICE"
            snd=Sound()
            snd.Play("NoSerial.wav")
            exit(2)
    # ------------------------------------------------------------------------------------------
    def get_running(self):
        return self.running
    # ------------------------------------------------------------------------------------------
    def get_exists(self):
        return self.exists
    # ------------------------------------------------------------------------------------------
    def get_port(self):
        return self.port
    def set_port(self,x):
        self.port=x
    # ------------------------------------------------------------------------------------------
    def get_baud(self):
        return self.baud
    def set_baud(self,x):
        self.baud=x
    # ------------------------------------------------------------------------------------------
    def get_bytesize(self):
        return self.bytesize
    def set_bytesize(self,x):
        self.bytesize=x
    # ------------------------------------------------------------------------------------------
    def get_parity(self):
        return self.parity
    def set_parity(self,x):
        self.parity=x
    # ------------------------------------------------------------------------------------------
    def get_stopbits(self):
        return self.stopbits
    def set_stopbits(self,x):
        self.stopbits=x
    # ------------------------------------------------------------------------------------------
    def Print(self,data):
        try:
            if (self.exists):
                self.running=True
                print "INFO:  SERIAL PROCESS START"
                self.ser = self.serial.Serial(self.port,self.baud)
                self.ser.bytesize = self.bytesize
                self.ser.parity   = self.parity
                self.ser.stopbits = self.stopbits
                self.ser.write(data)
                self.ser.close()
                self.running=False
                print "INFO:  SERIAL PROCESS END"
        except:
            self.running=False
            snd=Sound()
            snd.Play("NoSerial.wav")
            print "ERROR: SERIAL PROCESS NOT STARTED"
    # ------------------------------------------------------------------------------------------
    def Kill(self):
       if (self.running):
            self.ser.cancel_write()
            self.ser.reset_output_buffer()
            print "INFO:  SERIAL PROCESS KILLED"
# ==============================================================================================
# ITA2 converter design
# ==============================================================================================
class Ita2:
    # ------------------------------------------------------------------------------------------
    #           ' ' ,'!' ,'"' ,'#' ,'$' ,'%' ,'&' ,'\'' ,'(',')' ,'*' ,'+' ,',' ,'-' ,'.' ,'/' ,
    #           '0' ,'1' ,'2' ,'3' ,'4' ,'5' ,'6' ,'7' ,'8' ,'9' ,':' ,';' ,'<' ,'=' ,'>' ,'?' ,
    #           '@' ,'A' ,'B' ,'C' ,'D' ,'E' ,'F' ,'G' ,'H' ,'I' ,'J' ,'K' ,'L' ,'M' ,'N' ,'O' ,
    #           'P' ,'Q' ,'R' ,'S' ,'T' ,'U' ,'V' ,'W' ,'X' ,'Y' ,'Z' ,'[' ,'\\',']' ,'^' ,'_' ,
    #           '`' ,'a' ,'b' ,'c' ,'d' ,'e' ,'f' ,'g' ,'h' ,'i' ,'j' ,'k' ,'l' ,'m' ,'n' ,'o' ,
    #           'p' ,'q' ,'r ','s' ,'t' ,'u' ,'v' ,'w' ,'x' ,'y' ,'z' ,'{' ,'|' ,'}' ,'~' ,' '
    # ------------------------------------------------------------------------------------------
    TranspUS = [0x04,0x8D,0x91,0x00,0x89,0x00,0x9A,0x85,0x8F,0x92,0x00,0x91,0x8C,0x83,0x9C,0x9D,
                0x96,0x97,0x93,0x81,0x8A,0x90,0x95,0x87,0x86,0x98,0x8E,0x9E,0x8F,0x00,0x92,0x99,
                0x00,0x03,0x19,0x0E,0x09,0x01,0x0D,0x1A,0x14,0x06,0x0B,0x0F,0x12,0x1C,0x0C,0x18,
                0x16,0x17,0x0A,0x05,0x10,0x07,0x1E,0x13,0x1D,0x15,0x11,0x8F,0x9D,0x92,0x00,0x83,
                0x8B,0x03,0x19,0x0E,0x09,0x01,0x0D,0x1A,0x14,0x06,0x0B,0x0F,0x12,0x1C,0x0C,0x18,
                0x16,0x17,0x0A,0x05,0x10,0x07,0x1E,0x13,0x1D,0x15,0x11,0x8F,0x00,0x92,0x00,0x00]
    # ------------------------------------------------------------------------------------------
    FIGS = 0x1b
    LTRS = 0x1f
    # ------------------------------------------------------------------------------------------
    def __init__(self):
        self.padding=True               # Set NULL padding if true
        self.USTable=True               # Set US convert table if true
    # ------------------------------------------------------------------------------------------
    def get_padding(self):
        return self.padding
    def set_padding(self,x):
        self.padding=x
    # ------------------------------------------------------------------------------------------
    def get_USTable(self):
        return self.USTable
    def set_USTable(self,x):
        self.USTable=x
    # ------------------------------------------------------------------------------------------
    def crlf(self):
        return self.translate("\r\n")
    # ------------------------------------------------------------------------------------------
    def null(self):
        return chr(0x00)+chr(0x00)
    # ------------------------------------------------------------------------------------------
    # ------------------------------------------------------------------------------------------
    def translate(self, text):
        ita2 = chr(self.LTRS)
        letter = 1
        # --------------------------------------------------------------------------------------
        if (g_dbg):
             return text
        #---------------------------------------------------------------------------------------
        for char in text:
            # ----------------------------------------------------------------------------------
            # Old mechanical machines need time to complete CR / LF so adding a NULL
            # ----------------------------------------------------------------------------------
            if (char == '\n'):
                    ita2+=chr(0x02)
                    if self.padding:
                        ita2+=chr(0x00)+chr(0x00)
            if (char == '\r'):
                    ita2+=chr(0x08)
                    if self.padding:
                        ita2+=chr(0x00)
            # ----------------------------------------------------------------------------------
            # Taking care of the LETTER/FIGURE changes
            # ----------------------------------------------------------------------------------
            if (ord(char) >= 0x20) and (ord(char) <= 0x7F):
                    tmp = self.TranspUS[ord(char)-0x20]
                    if (tmp< 0x80) and (letter == 0):
                           ita2+=chr(self.LTRS)
                           letter=1
                    if (tmp>=0x80) and (letter == 1):
                            ita2+=chr(self.FIGS)
                            letter=0
                            tmp-=0x80;
                    ita2+=chr(tmp)
            # ----------------------------------------------------------------------------------
        return ita2
# ==============================================================================================
# Hash management
# ==============================================================================================
class Hash:
    # ------------------------------------------------------------------------------------------
    def __init__(self):
        self.dct={}
    # ------------------------------------------------------------------------------------------
    def cleanDict(self):
        self.dct.clear()
    ## -----------------------------------------------------------------------------------------
    def add2Dict(self,hsh):
        #---------------------------------------------------------------------------------------
        if hsh in self.dct:             # Already seen
            self.dct[hsh]=2             # Reset flag
            return False
        #---------------------------------------------------------------------------------------
        else:                           # First seen
            self.dct[hsh]=2             # Reset flag
            return True
    #-------------------------------------------------------------------------------------------
    def clearDict():
        try:
            old={}
            for key in self.dct:
                self.dct[key]-=1
                if (self.dct[key]==0):
                    old[key]="D"
                    print "WARN:  DELETE "+str(key)
            for key in old:
                self.dct.pop(key)
        except:
            print "ERROR: DICT"
        return
# ==============================================================================================
# Page converter design
# ==============================================================================================
class Page:
    nam = [nam_1,nam_2,nam_3,nam_4]
    snd = [snd_1,snd_2,snd_3,snd_4]
    url = [url_1,url_2,url_3,url_4]
    div = [div_1,div_2,div_3,div_4]
    dat = [dat_1,dat_2,dat_3,dat_4]
    tit = [tit_1,tit_2,tit_3,tit_4]
    inf = [inf_1,inf_2,inf_3,inf_4]
    xml = [xml_1,xml_2,xml_3,xml_4]
    # ------------------------------------------------------------------------------------------
    def __init__(self):
        self.linemax=str(64)
    # ------------------------------------------------------------------------------------------
    def get_linemax(self):
        return self.linemax
    def set_linemax(self,x):
        self.linemax=str(x)
    # ------------------------------------------------------------------------------------------
    def cleanline(self,line):
        if (line!=""):
            line=line.encode('ascii', 'ignore').decode()
            line=line.replace("\n", "")
            line=line.strip()
        return line
    # ------------------------------------------------------------------------------------------
    def Read(self,idx,dct):
        import re
        import urllib
        from lxml import html
        from lxml import etree
        from lxml.html.clean import clean_html
        # --------------------------------------------------------------------------------------
        first= True
        text = ""
        # --------------------------------------------------------------------------------------
        try:
            page = html.fromstring(urllib.urlopen(self.url[idx]).read())
        except:
            text="Error loading page"
            snd=Sound()
            snd.Play("NoConnection.wav")
            print "ERROR: PAGE LOAD"
            return text
        # --------------------------------------------------------------------------------------
#       if (True):
        try:
            if (not self.xml[idx]):
                page=clean_html(page)
            # ----------------------------------------------------------------------------------
            for element in page.xpath(self.div[idx]):   
                if (g_xmldebug):
                    print "DIV: " + element.text_content()
                    print etree.tostring(element, pretty_print=True)
                #-------------------------------------------------------------------------------
                # Title will be used as an already seen key
                #-------------------------------------------------------------------------------
                for title in element.xpath(self.tit[idx]):
                    if (g_xmldebug):
                        print "TIT"
                        print etree.tostring(title, pretty_print=True)
                    #---------------------------------------------------------------------------
                    # Here titles MUST differ between two items (not the case with twitter...)
                    #---------------------------------------------------------------------------
                    ctit=self.cleanline(title.text_content())
                   #hsh=hash(ctit)
                    #---------------------------------------------------------------------------
                    hsh=ctit
                    first=dct.add2Dict(hsh)
                    #---------------------------------------------------------------------------
                    # First seen :process
                    #---------------------------------------------------------------------------
                    if (first):
                        ctit = re.sub("(.{"+self.linemax+"})", "\\1\r\n", ctit, 0, re.DOTALL)
                #-------------------------------------------------------------------------------
                # First seen: process next items
                #-------------------------------------------------------------------------------
                if (first):
                    cdat="unknown date"
                    if (self.dat[idx]!=""):
                       for date  in element.xpath(self.dat[idx]):
                            cdat = self.cleanline(date.text)
                            if (g_xmldebug):
                                print "DAT: "
                                print etree.tostring(date, pretty_print=True)
                    cinf="unknown"
                    if (self.inf[idx]!=""):
                        for infos in element.xpath(self.inf[idx]):
                            if (g_xmldebug):
                                print "INF: "
                                print etree.tostring(infos, pretty_print=True)
                            cinf = re.sub("(.{"+self.linemax+"})", "\\1\r\n",self.cleanline(infos.text_content()), 0, re.DOTALL)
                #-------------------------------------------------------------------------------
                # First seen: assemble items
                #-------------------------------------------------------------------------------
                if (first):
                    text+='----\r\n'
                    text+=self.nam[idx]+" - "+cdat
                    text+='\r\n'
                    text+=ctit
                    text+='\r\n'
                    text+=cinf
                    text+='\r\n'
                # ------------------------------------------------------------------------------
                if (g_xmldebug):
                    exit()
        except:
            print "ERROR: PAGE PROCESSING"
        # --------------------------------------------------------------------------------------
        dct.cleanDict()
        # --------------------------------------------------------------------------------------
        return text
# ==============================================================================================
# IO Process
# ==============================================================================================
def IO_halt(pinnum):
    print "INFO:  SHUTDOWN NOW"
    if (g_dbg):
        return    
    import RPi.GPIO as GPIO 
    IO_unregister_events()
    snd=Sound()
    snd.Play("Shutdown.wav")
    time.sleep(1)
    os.system("sync;sync;halt")
    while 1:
        time.sleep(1)        
# ----------------------------------------------------------------------------------------------
def IO_mode(pinnum):
    print "INFO:  MODE CHANGE CALL"
    if (g_dbg):
        return    
    import RPi.GPIO as GPIO 
    GPIO.remove_event_detect(pinnum)
    if (h_ser):
        h_ser.Kill()
    if (h_aud):
        h_aud.Kill()
    IO_register_event(pinnum)
# ----------------------------------------------------------------------------------------------
def IO_run_audio():
    if (g_dbg):
        return False
    import RPi.GPIO as GPIO 
    return  GPIO.input(22)
# ==============================================================================================
# IO register
# ----------------------------------------------------------------------------------------------
EventsTable = {17:IO_halt,22:IO_mode}
# ----------------------------------------------------------------------------------------------
def IO_register_event(pinnum):
    if (g_dbg): 
        return
    import RPi.GPIO as GPIO 
    GPIO.setmode(GPIO.BCM)                  # Broadcom GPIO numbers
    GPIO.setup(pinnum, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.add_event_detect(pinnum, GPIO.BOTH, callback=EventsTable[pinnum],bouncetime=200)
# ----------------------------------------------------------------------------------------------
def IO_unregister_events():
    if (g_dbg): 
        return
    import RPi.GPIO as GPIO 
    for event in EventsTable:
        GPIO.remove_event_detect(event)
# ----------------------------------------------------------------------------------------------
def IO_register_events():
    if (g_dbg): 
        return
    for event in EventsTable:
        IO_register_event(event)
# ==============================================================================================
# Config
# ----------------------------------------------------------------------------------------------
def getConf():
    from ConfigParser import SafeConfigParser
    parser = SafeConfigParser()
    # -----------------------------------------------------------------------------------------
    try:
        parser.read('./config.ini')
    except:
        sit=0
        url="http://209.180.212.65:8000"
    # -----------------------------------------------------------------------------------------
    try:
        sit=int(parser.get('Site_Index', 'index'))
        if (sit<0) or (sit>3):
            sit=0
    except:
        sit=0
    # -----------------------------------------------------------------------------------------
    try:
        url=   (parser.get('Site_AFSK',  'url'))
    except:
        url="http://209.180.212.65:8000"
    # -----------------------------------------------------------------------------------------
    print "INFO:  Feed N." + str(sit)
    print "INFO:  AFSK " +       url
    return url,sit
# ----------------------------------------------------------------------------------------------
def mode_audio(url):
    global h_aud
    # ------------------------------------------------------------------------------------------
    print "INFO:  AUDIO MODE START"
    # ------------------------------------------------------------------------------------------
    # Get an output
    # ------------------------------------------------------------------------------------------
    h_aud=Stream()
    h_aud.Play(url)
    # ------------------------------------------------------------------------------------------
    print "INFO:  AUDIO MODE ENDS"
# ----------------------------------------------------------------------------------------------
def mode_data(idx,dct):
    global h_ser
    # ------------------------------------------------------------------------------------------
    print "INFO:  DATA  MODE START"
    # ------------------------------------------------------------------------------------------
    # Gets a converter
    # ------------------------------------------------------------------------------------------
    ita=Ita2()
    ita.padding=True
    ita.USTable=True
    # ------------------------------------------------------------------------------------------
    # Get a printer
    # ------------------------------------------------------------------------------------------
    h_ser=Serial()
    # ------------------------------------------------------------------------------------------
    # Send NULL to awake machine and start at a new line
    # ------------------------------------------------------------------------------------------
#   out.Print(ita.null())
#   out.Print(ita.crlf())
#   out.Print(ita.translate("RaspTTY (C) RxControl"))
#   out.Print(ita.crlf())
    # ------------------------------------------------------------------------------------------
#   snd.Play(g_snd[g_site])
    # ------------------------------------------------------------------------------------------
    # Read page and get new items
    # ------------------------------------------------------------------------------------------
    dat=Page()
    text=dat.Read(idx,dct)
    # ------------------------------------------------------------------------------------------
    # ITA2 Process
    # ------------------------------------------------------------------------------------------
    if (text!=""):
        h_ser.Print(ita.null())
        h_ser.Print(ita.translate(text))
    # ------------------------------------------------------------------------------------------
    print "INFO:  DATA MODE ENDS"
# ==============================================================================================
# Main
# ----------------------------------------------------------------------------------------------
import sys, getopt
import time
import os;
from   sys import platform
# ----------------------------------------------------------------------------------------------
def main(argv):
    global g_dbg
    global g_tst
    # ------------------------------------------------------------------------------------------
    # g_dbg mode
    # ------------------------------------------------------------------------------------------
    try:
        opts,args = getopt.getopt(argv[1:],"dt",["g_dbg","testing"])
    except:
        print 'service.py {-d|--g_dbg}{-t|--testing}'
        sys.exit(2)
    # ------------------------------------------------------------------------------------------
    g_dbg=False
    g_tst=False
    for opt,arg in opts:
       if opt in ("-d","--g_dbg"):
            g_dbg =True
       if opt in ("-t","--testing"):
            g_tst  =True
    # ------------------------------------------------------------------------------------------
    # Get a dictionnary
    # ------------------------------------------------------------------------------------------
    dct=Hash()
    # ------------------------------------------------------------------------------------------
    # Get conf
    # ------------------------------------------------------------------------------------------
    url,sit=getConf()
    # ------------------------------------------------------------------------------------------
    # Get an IO processor
    # ------------------------------------------------------------------------------------------
    IO_register_events()
    # ------------------------------------------------------------------------------------------
    # Will loop forever starting from there
    # ------------------------------------------------------------------------------------------
    snd=Sound()            
    snd.Play("RaspberryTTY.wav")
    while True:
        if (IO_run_audio()):    
            snd.Play("FSKMode.wav")
            mode_audio(url)                          # Will loop forever stating from there
        else:
            dct.cleanDict()
            snd.Play("DirectMode.wav")    
            mode_data(sit,dct)                       # Will loop forever stating from there
    # ------------------------------------------------------------------------------------------
# ==============================================================================================
#
# ----------------------------------------------------------------------------------------------
if __name__ == "__main__":
    main(sys.argv)
# ----------------------------------------------------------------------------------------------

