########################################################################
#
#  Copyright (C) 2009, CZ.NIC, z.s.p.o.
# 
#  This file is part of dnstester.
#
#  dnstester 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.
#
#  dnstester 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/>.
#
########################################################################

import gobject
import urllib2
from xml.dom import minidom
import socket
import sys
import time

import messages
import lang
import misc
import home
import config

devs = []
current_version = None

class DevicesParseError(Exception):

    def __init__(self, value):
        self.value = value

    def __str__(self):
        return self.value


def download_devices():

    # forced wait
    time.sleep(1)
    # optional longer wait
    if '--slowly' in sys.argv: time.sleep(5)

    old_timeout =  socket.getdefaulttimeout()
    try:
        socket.setdefaulttimeout(config.devdownload_timeout)
        d = urllib2.urlopen(misc.get_devices_url())
        r = d.read()
        d.close()
        socket.setdefaulttimeout(old_timeout)
        return r
    except:
        socket.setdefaulttimeout(old_timeout)
        return None

def parse_aliases(a):
    r = []

    for c in a.childNodes:
        if c.nodeType == c.TEXT_NODE: continue
        if c.nodeType == c.ELEMENT_NODE and c.localName == 'alias':
            name = c.getAttribute('name')
            r.append(name)
        else:
            raise DevicesParseError("<trusted> expected, but another element found")

    return r
    

def parse_test(t):
    name = t.getAttribute('name')
    result = t.getAttribute('result')
    return {name: result}


def parse_trusted_device(d):
    
    tests = {}
    for c in d.childNodes:
        if c.nodeType == c.TEXT_NODE: continue
        if c.nodeType == c.ELEMENT_NODE and c.localName == 'aliases':
            aliases = parse_aliases(c)
        elif c.nodeType == c.ELEMENT_NODE and c.localName == 'test':
            tests.update(parse_test(c))
        else:
            raise DevicesParseError("<trusted> expected, but another element found")
    return {'aliases': aliases, 'tests': tests}


def parse_trusted(t):
    
    devs = []

    for c in t.childNodes:
        if c.nodeType == c.TEXT_NODE: continue
        if c.nodeType == c.ELEMENT_NODE and c.localName == 'trusteddevice':
            devs.append(parse_trusted_device(c))
        else:
            raise DevicesParseError("<trusted> expected, but another element found")
    return devs


def parse_model(m):
    
    name = m.getAttribute('name')
    r = {'name':name}
    if m.hasAttribute('enable_entry') and m.getAttribute('enable_entry') == '1':
        r['enable_entry'] = True

    r['trusted'] = []

    for c in m.childNodes:
        if c.nodeType == c.TEXT_NODE: continue
        if c.nodeType == c.ELEMENT_NODE and c.localName == 'trusted':
            r['trusted'] = parse_trusted(c)
        else:
            raise DevicesParseError("<trusted> expected, but another element found")

    return r


def parse_maclist(xml):

    list = []
    for c in xml.childNodes:
        if c.nodeType == c.TEXT_NODE: continue
        if c.nodeType == c.ELEMENT_NODE and c.localName == 'prefix':
            if len(c.childNodes) != 1:
                raise DevicesParseError("invalid <prefix> content")
            content = c.firstChild
            if content.nodeType != c.TEXT_NODE:
                raise DevicesParseError("invalid <prefix> content")
            list.append(content.data)
        else:
            raise DevicesParseError("<prefix> expected, but another element found")
    return list


def parse_manufacturer(xml):

    name = xml.getAttribute('name')
    man = {'manufacturer' : name}
    if xml.hasAttribute('enable_entry') and xml.getAttribute('enable_entry') == '1':
        man['enable_entry'] = True
    maclist = []

    models = []
    for c in xml.childNodes:
        if c.nodeType == c.TEXT_NODE: continue
        if c.nodeType == c.ELEMENT_NODE and c.localName == 'model':
            models.append(parse_model(c))
        elif c.nodeType == c.ELEMENT_NODE and c.localName == 'maclist':
            if maclist: raise DevicesParseError("multiple <maclist> tags detected")
            maclist = parse_maclist(c)
        else:
            raise DevicesParseError("<model> or <maclist> expected, but another element found")
    man['models'] = models
    man['maclist'] = maclist
    return man


def parse_manufacturers(xml):
    mans = []
    
    for c in xml.childNodes:
        if c.nodeType == c.TEXT_NODE: continue
        if c.nodeType == c.ELEMENT_NODE and c.localName == 'manufacturer':
            m = parse_manufacturer(c)
            mans.append(m)
        else:
            raise DevicesParseError("<manufacturer> expected, but another element found")

    return mans


def parse_latestclient(xml):
    global current_version
    current_version = xml.getAttribute('version')


def process_xml(xml):

    global devs
    
    devices_processed = False
    latestclient_processed = False

    if len(xml.childNodes) != 1: raise DevicesParseError('multiple root elements found')
    config = xml.firstChild
    if config.localName != 'config': raise DevicesParseError('root element is not <config>')
    for c in config.childNodes: 
        if c.nodeType == c.TEXT_NODE: continue
        if c.nodeType != c.ELEMENT_NODE: continue

        if c.localName == 'devices': 
            my_devs = parse_manufacturers(c) 
            devices_processed = True
        elif c.localName == 'latestclient': 
            parse_latestclient(c) 
            latestclient_processed = True
        else:
            raise DevicesParseError('unknown element <%s>' % c.localName)

    if not devices_processed: raise DevicesParseError('<devices> element not found')
    if not latestclient_processed: raise DevicesParseError('<latestclient> element not found')

#    misc.dbg_print_struct(my_devs)
    devs = my_devs


def parse_xml(xml_text):
    try:
        xmldoc = minidom.parseString(xml_text)
        process_xml(xmldoc)
        return True
    except DevicesParseError, e:
        print "DevicesParseError: %s" % e.value
        return False
    except:
        return False
        
def load_devices():

    h = home.Home()
    xml_text = misc.load_from_file(h.devices_filename)
    if not xml_text:
        xml_text = misc.load_from_file(config.default_devices_file)
    return xml_text
    

def save_devices(xml_text):
    h = home.Home()
    if not h.devices_filename:
        return False
    try:
        f = open(h.devices_filename, 'w')
        f.write(xml_text)
        f.close()
        return True
    except:
        return False
    

def update_devices_async():

    xml_text = download_devices()
    if not xml_text:
        print "Warning, can't download actualized list of devices, using local list."
        xml_text = load_devices()
        if not xml_text:
            print "Can't load devices from %s file" % config.default_devices_file
            return False
        if not parse_xml(xml_text):
            print "Can't parse local device list, terminating."
            return False
    else:
        if not parse_xml(xml_text):
            print "Can't parse downloaded device list, terminating."
            return False
        if not save_devices(xml_text):
            print "Warning, can't save actualized list of devices."

    return True
