#!/usr/bin/python3
# -*- coding: utf-8 -*-

# A simple script which check if any newer versions exist
#
# (C) 2021 by Domenico Panella <pandom79@gmail.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 2
# of the License, or (at your option) any later version.
# See http://www.gnu.org/licenses/gpl-2.0.html for full license text.

import argparse
import logging
import requests
import re
import os
import os.path as path
from html.parser import HTMLParser

""" Global Variables """
urlsrc = None
regexurl = None
regexver = None
currentver = None
separator = None
outdir = None
all_versions = []
versions_found = []
versions_found_str = []
filename = "check_upgrade"

""" Classes and methods """
class MyHTMLParser(HTMLParser):

    def __init__(self, p_url, p_ver):
        super(MyHTMLParser, self).__init__()
        self.p_url = p_url
        self.p_ver = p_ver

    def handle_starttag(self, tag, attrs):
        for attr in attrs:
            logging.debug("tag = %s", attr[0])
            logging.debug("value = %s", attr[1])
            if attr[1] is not None:
                if self.p_url.search(attr[1]) is not None:
                    logging.debug("Match url! %s", self.p_url.search(attr[1]))
                    if self.p_ver.search(attr[1]) is not None:
                        version = self.p_ver.search(attr[1]).group()
                        logging.debug("Match ver! %s", version)
                        if version not in all_versions:
                            logging.debug("%s not exists : append!", version)
                            all_versions.append(version)
                            logging.debug("all_versions : %s", all_versions)
                    else:
                        print("Error in handle_starttag: Checks regexurl and regexver parameters values")
                        exit(1)

    def handle_data(self, data):
        if data is not None:
            logging.debug("data = %s", data)
            if len(self.p_url.findall(data)) > 0:
                logging.debug("Match url! %s", self.p_url.findall(data))
                urls = self.p_url.findall(data)
                for i in range(len(urls)):
                    url = self.p_ver.search(urls[i]).group()
                    if self.p_ver.search(url) is not None:
                        version = self.p_ver.search(url).group()
                        logging.debug("Match ver! %s", version)
                        if version not in all_versions:
                            logging.debug("%s not exists : append!", version)
                            all_versions.append(version)
                            logging.debug("all_versions : %s ", all_versions)
                    else:
                        print("Error in handle_data: Checks regexurl and regexver parameters values")
                        exit(1)

""" Functions """

""" Get numeric or alpha part of an alphanumeric string """
def getPart(type, value):
    result_str = ""
    if value:
        length = len(value)
        for i in range(length):
            value_str = str(value[i])
            if type == "numeric":
                if value_str.isdigit():
                    result_str += value_str
            elif type == "alpha":
                if value_str.isalpha():
                    result_str += value_str
    return result_str

""" Return True if 'currentValue' less than 'value', False otherwise """
def compareValue(currentValue, value):
    result = False
    logging.debug("Comparing the values ....")
    logging.debug("currentValue = %s", currentValue)
    logging.debug("value = %s", value)
    if not currentValue and value and str(value).isnumeric() and int(value) != 0:
        logging.debug("current value is none! Value is not None and != 0, exiting ....")
        result = True
    elif currentValue and not value:
        logging.debug("value is none! currentValue is not None, exiting ....")
        result = False
    else:
        if currentValue and value:
            numPartCurValue = getPart("numeric", currentValue)
            numPartValue = getPart("numeric", value)
            logging.debug("Comparing the numeric part ....")
            logging.debug("numPartCurValue of %s = %s", currentValue, numPartCurValue)
            logging.debug("numPartValue of %s = %s", value, numPartValue)
            if int(numPartCurValue) < int(numPartValue):
                logging.debug("%s less than %s, exiting ....", numPartCurValue, numPartValue)
                result = True
            elif int(numPartCurValue) > int(numPartValue):
                logging.debug("%s major than %s, exiting ....", numPartCurValue, numPartValue)
                result = False
            elif int(numPartCurValue) == int(numPartValue):
                logging.debug("%s is equal to %s", numPartCurValue, numPartValue)
                logging.debug("Comparing the alpha part ....")
                alphaPartCurValue = getPart("alpha", currentValue)
                alphaPartValue = getPart("alpha", value)
                length = len(alphaPartCurValue)
                if len(alphaPartCurValue) < len(alphaPartValue):
                    length = len(alphaPartValue)
                logging.debug("Length of alpha part = %s ", length)
                for i in range(length):
                    try:
                        alphaPartCurValueChr = alphaPartCurValue[i]
                    except:
                        alphaPartCurValueChr = None
                    try:
                        alphaPartValueChr = alphaPartValue[i]
                    except:
                        alphaPartValueChr = None
                    if not alphaPartCurValueChr and alphaPartValueChr:
                        logging.debug("alphaPartCurValueChr is None")
                        logging.debug("alphaPartValueChr = %s", alphaPartValueChr)
                        logging.debug("%s less than %s, exiting ....", alphaPartCurValueChr, alphaPartValueChr)
                        result = True
                        break
                    elif alphaPartCurValueChr and not alphaPartValueChr:
                        logging.debug("alphaPartCurValueChr = %s", alphaPartCurValueChr)
                        logging.debug("alphaPartValueChr is None")
                        logging.debug("%s major than %s, exiting ....", alphaPartCurValueChr, alphaPartValueChr)
                        result = False
                        break
                    elif alphaPartCurValueChr and alphaPartValueChr:
                        logging.debug("alphaPartCurValueChr = %s", alphaPartCurValueChr)
                        logging.debug("alphaPartValueChr = %s", alphaPartValueChr)
                        if alphaPartCurValueChr < alphaPartValueChr:
                            logging.debug("%s less than %s, exiting ....", alphaPartCurValueChr, alphaPartValueChr)
                            result = True
                            break;
                        elif alphaPartCurValueChr == alphaPartValueChr:
                            logging.debug("%s equal to %s, go to the next ....", alphaPartCurValueChr, alphaPartValueChr)
                            continue
                        elif alphaPartCurValueChr > alphaPartValueChr:
                            logging.debug("%s major than %s, exiting ....", alphaPartCurValueChr, alphaPartValueChr)
                            break;
    logging.debug("Return compareValue = %s !", str(result))
    return result

""" Return True if 'listCurrentVer' less than 'listVer', False otherwise """
def compareVersion(listCurrentVer, listVer):
    logging.debug("\n\nComparing the version ....")
    logging.debug("%s and %s", listCurrentVer, listVer)
    result = False
    lenght = len(listCurrentVer)
    if len(listCurrentVer) < len(listVer):
        lenght = len(listVer)
    logging.debug("Length = %s", lenght)
    for i in range(lenght):
        try:
            currentValue = listCurrentVer[i]
        except:
            currentValue = None
        try:
            value = listVer[i]
        except:
            value = None
        logging.debug("current value = %s", currentValue)
        logging.debug("value = %s", value)
        """ If both are number """
        if str(currentValue).isnumeric() and str(value).isnumeric():
            logging.debug("Both are numerics")
            if int(currentValue) < int(value):
                logging.debug("%s less than %s, exiting ....", currentValue, value)
                result = True
                break
            elif int(currentValue) == int(value):
                logging.debug("Both are equal, go to the next ...")
                continue
            else:
                logging.debug("%s major than %s, exiting ....", currentValue, value)
                break
        else:
            logging.debug("There are alphanumeric characters, let's go to compare the value ...")
            result = compareValue(currentValue, value)
            break

    logging.debug("Return compareVersion = %s !", str(result))
    return result

def findNextVersions():
    listCurrentVer = currentver.split(separator)
    for ver in all_versions:
        listVer = ver.split(separator)
        if compareVersion(listCurrentVer, listVer):
            logging.debug("Adding %s to the versions found ", listVer)
            versions_found.append(listVer)

def handle_file(operation):
    """ Go up one level to match the package path """
    one_up = path.abspath(path.join(outdir, "../"))
    outFileName = one_up + "//" + filename
    if operation == "create":
        outFile = open(outFileName, "w")
        """ Check if the file is created """
        if not path.exists(outFileName):
            print("Error: Unable to create ", filename, "file!")
            exit(1)
        else:
            for i in range(len(versions_found_str)):
                outFile.write(versions_found_str[i] + "\n")
            outFile.close()
    elif operation == "remove":
        if path.exists(outFileName):
            os.remove(outFileName)
            """ Check if the file is removed """
            if path.exists(outFileName):
                print("Error: Unable to remove ", filename, "file!")
                exit(1)

def create_new_list():
    version = ""
    for i in range(len(versions_found)):
        version = versions_found[i]
        value = ""
        for j in range(len(version)):
            value += version[j]
            if j < (len(version) - 1):
                value += separator
        versions_found_str.append(value)

def sort_versions():
    n = len(versions_found)
    for i in range(n - 1):
        for j in range(0, n - i - 1):
            if compareVersion(versions_found[j + 1], versions_found[j]):
                versions_found_swap = versions_found[j]
                versions_found[j] = versions_found[j + 1]
                versions_found[j + 1] = versions_found_swap

def check_input(p_ver):
    """ Outdir """
    if not outdir:
        print("Error: no outdir specified!")
        exit(1)
    """ Current version """
    if currentver:
        version_list = currentver.split(separator)
        logging.debug("current version list = %s", version_list)
        for i in range(len(version_list)):
            value = version_list[i]
            logging.debug("value = %s", value)
            logging.debug("value[0] = %s", value[0])
            """ Check first character """
            if not str(value[0]).isdigit():
                print(f"Error: '{currentver}' --> '{value}'")
                print("The values which compose the software version can't start with a non-numeric character!")
                exit(1)
            """ Check weird characters """
            for j in range(len(value)):
                if not str(value[j]).isdigit() and not str(value[j]).isalpha():
                    print(f"Error: '{currentver}' --> '{value}'")
                    print("The values which compose the software version can only contain numbers and letters!")
                    exit(1)
        """ Check last character """
        if not str(currentver[len(currentver) - 1]).isdigit() and not str(currentver[len(currentver) - 1]).isalpha():
            print(f"Error: '{currentver}'")
            print("The software version can't end with a non-alphanumeric character!")
            exit(1)
        """ Check regex """
        re_currentver = p_ver.match(currentver)
        if not re_currentver:
            print(f"Error : The current version '{currentver}' doesn't match the regex pattern '{regexver}'!")
            exit(1)

""" Main """
if __name__ == '__main__':

    parser = argparse.ArgumentParser(
        description='Open Build Service source service "check_upgrade".'
        'Used to retrieve the newer a package versions.')
    parser.add_argument('--outdir', required=True,
                        help='output directory of the package')
    parser.add_argument('--urlsrc', required=True,
                        help='The url which has to parsed.')
    parser.add_argument('--regexurl', required=True,
                        help='The regular expression which extracts the links to the versions')
    parser.add_argument('--regexver', required=True,
                        help='The regular expression which extracts the package version')
    parser.add_argument('--currentver', required=True,
                        help='the current version which pattern have to match <regexver> value.')
    parser.add_argument('--separator', default='.',
                        help='the character which separates the values (major version, minor version, etc ...) in '
                        'the string which makes up the package version.')
    args = vars(parser.parse_args())

    outdir = args['outdir']
    urlsrc = args['urlsrc']
    regexurl = args['regexurl']
    regexver = args['regexver']
    currentver = args['currentver']
    separator = args['separator']

    if os.environ.get('DEBUG_CHECK_UPGRADE') == "1":
        logging.getLogger().setLevel(logging.DEBUG)

    logging.debug("outdir = %s", outdir)
    logging.debug("urlsrc = %s", urlsrc)
    logging.debug("regexurl = %s", regexurl)
    logging.debug("regexver = %s", regexver)
    logging.debug("currentver = %s", currentver)
    logging.debug("separator = %s", separator)

    p_url = re.compile(regexurl)
    p_ver = re.compile(regexver)

    """ Check input values """
    logging.debug("\n\nChecking input values ....")
    check_input(p_ver)

    """ GET Http request """
    headers = {'Accept': 'text/html'}
    print("Executing the GET http request for \"" + urlsrc + "\" ....\n")
    response = requests.get(urlsrc, headers=headers)
    logging.debug("Http response = %s", response.text)
    logging.debug("Http response status code = %s", response.status_code)
    if (response.status_code == 404):
        print(f"Error: Unable contact the url '{urlsrc}'.\nCheck 'urlsrc' parameter value.")
        exit(1)

    """ Delete 'filename' file if exists """
    handle_file("remove")

    """ Parsing the response extracting the data according 'regexurl' pattern """
    parser = MyHTMLParser(p_url, p_ver)
    print("Parsing the http response ....")
    parser.feed(response.text);
    if len(all_versions) == 0:
        print("\nNothing found! Check the 'urlsrc' and 'regexurl' parameters values.")
    else:
        logging.debug("\n\nThe following versions have been found = \n%s", all_versions)
        """ Let's go to find newer versions """
        findNextVersions()
        """ Sorting versions """
        logging.debug("\n\nSorting the versions ...")
        sort_versions()
        """ Create a list of one string """
        create_new_list()
        """ Print all the newer versions than current version """
        print("\nCurrent version :", currentver)
        if len(versions_found_str) > 0:
            print("Available the following versions for upgrade :")
            for i in range(len(versions_found_str)):
                print(versions_found_str[i])
            """ Create the 'filename' file """
            handle_file("create")
        else:
            print("There are no newer versions!")