import logging
import os
import re
import tarfile
from StringIO import StringIO

from lxml import etree


class CrashReport(object):
    """This class represents a crash report."""
    
    valid_name = re.compile('^crashreport\.[A-Za-z0-9]{6}\.tar\.gz$')
    
    def __init__(self, path):
        name = os.path.basename(path)
        
        self.name = name
        self.path = path
        self.confirmation_code = None
        self.data = CrashData()
    
    
    def has_valid_name(self):
        """Returns True is the report's name matches the name of a valid crash
        report. Otherwise it returns implicit False."""
        match = re.match(self.__class__.valid_name, self.name)
        
        if not match:
            logging.info('Invalid crash report name: %s' % self.name)
            return
        
        return True
    
    
    def has_valid_type(self):
        """Returns True if the report's file type matches the file type of a
        valid crash report. Otherwise it returns implicit False."""
        if not tarfile.is_tarfile(self.path):
            logging.info('The report %s cannot be read from the tarfile module'
                         % self.path)
            return
        
        try:
            tarfileobj = tarfile.open(self.path, 'r:gz')
        except tarfile.ReadError:
            logging.info('The provided mode is not suitable to open for reading'
                         ' the report %s' % self.path)
            return
        except tarfile.CompressionError:
            logging.info('The compression method for the report %s is not '
                         'supported' % self.path)
            return
        finally:
            tarfileobj.close()
        
        return True
    
    
    def has_valid_contents_number(self):
        """Returns True is the report contains the same number of files that a
        valid crash report has. Othewise it returns implicit False."""
        try:
            tarfileobj = tarfile.open(self.path, 'r:gz')
        except tarfile.ReadError:
            return
        except tarfile.CompressionError:
            return
        else:
            contents_list = tarfileobj.getnames()
            if not len(contents_list) == 1:
                logging.info('The report %s has invalid number of contents'
                             % self.path)
                return
            self.data.name = contents_list[0]
        finally:
            tarfileobj.close()
        
        return True



class CrashData(object):
    """This class represents the crash data that a crash report contains."""
    
    valid_name = re.compile('^crashreport\.[A-Za-z0-9]{6}\.xml$')
        
    def __init__(self):
        self.name = None
        self.path = None
        self.info = {}
        self.commands = {'crashtype': None,
                         'crashdate': None,
                         'hostname': None,
                         'ostype': None,
                         'osrelease': None,
                         'version': None,
                         'machine': None,
                         'panic': None,
                         'backtrace': None,
                         'ps_axl': None,
                         'vmstat_s': None,
                         'vmstat_m': None,
                         'vmstat_z': None,
                         'vmstat_i': None,
                         'pstat_T': None,
                         'pstat_s': None,
                         'iostat': None,
                         'ipcs_a': None,
                         'ipcs_T': None,
                         'nfsstat': None,
                         'netstat_s': None,
                         'netstat_m': None,
                         'netstat_id': None,
                         'netstat_anr': None,
                         'netstat_anA': None,
                         'netstat_aL': None,
                         'fstat': None,
                         'dmesg': None,
                         'kernelconfig': None,
                         'ddbcapturebuffer': None
                         }
    
    
    def has_valid_name(self):
        """Returns True if the report's crash data name matches the name of a
        valid crash data. Otherwise it returns implicit False."""
        match = re.match(self.__class__.valid_name, self.name)
        
        if not match:
            logging.info('Invalid crash data name: %s' % self.name)
            return
        
        return True
    
    
    def has_valid_crashdata(self):
        """Returns True if the crash data is a well formed and valid XML file.
        Otherwise implicit False."""
        dtdfile = StringIO("""<!ELEMENT crashreport (header, body)>
                           <!ELEMENT header (email)>
                           <!ELEMENT email (#PCDATA)>
                           <!ELEMENT body (command+)>
                           <!ELEMENT command (name, result)>
                           <!ELEMENT name (#PCDATA)>
                           <!ELEMENT result (#PCDATA)>""")
        
        try:
            elemtree = etree.parse(self.path)
        except:
            logging.info('%s is not a well formed crash report data.' %
                         (self.path))
            return
        else:
            dtd = etree.DTD(dtdfile)
            if not dtd.validate(elemtree):
                logging.info('%s is not a valid crash report data.' %
                             (self.path))
                return
        
        return True