"""Module contains functionality for parsing HTML page of a particular vulnerability."""

import re

from cve_connector.vendor_cve.implementation.parsers.general_and_format_parsers\
    .html_parser import HtmlParser
from cve_connector.vendor_cve.implementation.vendors_storage_structures.android import Android

from cve_connector.vendor_cve.implementation.utilities.check_correctness \
    import is_correct_cve_id
from cve_connector.vendor_cve.implementation.utilities.utility_functions \
    import normalize_string, get_current_date, string_to_date, normalize_attributes


class AndroidVulnerabilityParser(HtmlParser):
    """
    Class responsible for parsing particular android vulnerability.
    """
    def __init__(self, url):
        super().__init__(url)
        self.date_format = '%B %d, %Y'  # September 5, 2017
        self.load_content()
        self.entity_to_add = None

    def parse(self):
        """
        Provides parsing functionality.

        :return: None
        """
        date_list = self.data.xpath('.//article[@class="devsite-article-inner"]'
                                    '//p/em[contains(text(), "Published")]/text()')
        date = get_current_date() if not date_list else self.get_published_date(date_list[0])
        tables = self.data.xpath(
            './/div[@class="devsite-table-wrapper"]/table[tbody/tr/th[contains(text(), "CVE")]]')
        h3_list = self.data.xpath('.//h3[@id]/text()')

        if len(tables) is not len(h3_list):
            h3_list = ['' for i in range(0, len(h3_list))]

        for table, h3_tag in zip(tables, h3_list):
            h3_tag.replace('\n', ' ')
            table_header, table_rows = self.parse_table(table)
            self.add_new_entities_from_rows(table_rows, table_header, h3_tag, date)

    def get_published_date(self, string):
        """
        Extract published date from input string.

        :param string: raw string containing published date
        :return: current date
        """
        date_string = re.match(r'Published (\w+ \d+, \d{4})', string)
        if date_string:
            date = string_to_date(date_string.group(1), self.date_format)
            return date
        return get_current_date()

    def add_new_entities_from_rows(self, rows, table_header, affected_comp, date):
        """
        Creates list of android vulnerabilities as property.

        :param rows: rows of table
        :param table_header: header of parsed table
        :param affected_comp: component affected by the vulnerabilities
        :param date: date of publication
        :return: None
        """
        for row in rows:
            if len(row) is not len(table_header):
                raise ValueError('Invalid row with data.')

            self.entity_to_add = Android()
            self.entity_to_add.affected_component = str(affected_comp)
            self.entity_to_add.published = date
            self.set_new_entity(normalize_attributes(row), table_header)
            if self.entity_to_add.is_valid_entity():
                self.entities.append(self.entity_to_add)
            self.entity_to_add = None

    def set_new_entity(self, attributes, table_header):
        """
        Sets attributes according to their names.

        :param attributes: values for attributes
        :param table_header: names of attributes
        :return: None
        """
        for attribute, header_item in zip(attributes, table_header):
            n_attr = normalize_string(attribute)
            n_header = normalize_string(header_item).lower()
            if 'references' not in table_header:
                self.set_attribute_according_header(n_attr, n_header)

    def set_attribute_according_header(self, attribute, header_item):
        """
        Sets attribute according to the name of attribute.

        :param attribute: value of attribute of class
        :param header_item: name of attribute
        :return: None
        """
        header_dictionary = {
            'cve': self.set_cve,
            'type': self.set_vuln_type,
            'severity': self.set_severity,
            'component': self.set_component,
            'aosp': self.set_updated_aosp_ver,
        }

        for header in header_dictionary:
            if header in header_item.lower():
                header_dictionary.get(header)(attribute)
                break

    def set_cve(self, attribute):
        """
        Sets CVE ID.

        :param attribute: raw string containing CVE ID
        :return: None
        """
        if is_correct_cve_id(attribute):
            self.entity_to_add.cve_id = attribute

    def set_vuln_type(self, attribute):
        """
        Sets vulnerability type.

        :param attribute: raw string containing vulnerability type
        :return: None
        """
        if attribute is not None:
            self.entity_to_add.vulnerability_type = attribute

    def set_severity(self, attribute):
        """
        Sets severity.

        :param attribute: raw string containing severity
        :return: None
        """
        if attribute is not None:
            self.entity_to_add.severity = attribute

    def set_component(self, attribute):
        """
        Sets affected component.

        :param attribute: raw string containing component
        :return: None
        """
        if attribute is not None:
            self.entity_to_add.component = attribute

    def set_updated_aosp_ver(self, attribute):
        """
        Sets version.

        :param attribute: string containing version
        :return: None
        """
        if attribute is not None:
            self.entity_to_add.updated_aosp_versions = attribute
