import os
import socket
import subprocess
import pytest
import logging
import tempfile
from glob import glob

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]


def tlib_folder():
    """
    Returns tlib's absolute path
    """
    file_folder = os.path.dirname(__file__)
    return os.path.abspath(os.path.join(file_folder, os.pardir))


def tlib_asset_folder():
    """
    Returns absolute path of tlib's asset folder
    """
    return os.path.abspath(os.path.join(tlib_folder(), "asset"))


def tlib_modules_folder():
    """
    Returns absolute path of folder containing all modules
    """
    return os.path.abspath(os.path.join(tlib_folder(), "base"))


def tlib_config_folder():
    """
    Returns absolute path of tlib's config folder 
    """
    return os.path.abspath(os.path.join(tlib_folder(), "config"))


def tlib_template_folder():
    """
    Returns absolute path of tlib's template folder\n
    Template folder contains jinja templates used for generation of reports\n
    like screenshots
    """
    return os.path.abspath(os.path.join(tlib_folder(), "templates"))


def webdriver_chrome_executable(version="latest"):
    """
    Returns path to Chrome WebDriver executable
    """
    if version == "latest":
        #Find all cromedriver files and sort by filename
        path = os.path.join(tlib_asset_folder(), "selenium", "chromedriver_*.exe")
        r = sorted(glob(path))
        return os.path.join(tlib_asset_folder(), "selenium", r[-1])
    else:
        return os.path.join(tlib_asset_folder(), "selenium", "chromedriver_%s.exe" % version)


def webdriver_ie_executable():
    """
    Alias for webdriver_ie32_executable
    """
    return webdriver_ie32_executable();


def webdriver_ie32_executable():
    """
    Returns path to IE WebDriver executable
    """
    return os.path.join(tlib_asset_folder(), "selenium", "IE", "2.41.0", "Win32", "IEDriverServer.exe")

def webdriver_ie64_executable():
    """
    Returns path to IE WebDriver executable
    """
    return os.path.join(tlib_asset_folder(), "selenium", "IE", "2.41.0", "x64", "IEDriverServer.exe")

def android_server_apk():
    """
    Returns path to Webdriver for android
    """
    return os.path.join(tlib_asset_folder(), "selenium", "android-server-2.21.0.apk")


def selendroid_server_jar():
    """
    Returns path to selendroid jar for android native app
    """
    return os.path.join(tlib_asset_folder(), "selenium", "selendroid-standalone-0.9.0-with-dependencies.jar")


def is_valid_ip(ip):
    """
    Returns true if IP parameter is a valid IP address.\n
    Currently IPs in this format are valid:\n
    X.X.X.X\n
    X.X.X.X:PORT

    @type ip: str
    @return: bool
    """

    if ip is None:
        return False

    try:
        (ip, port) = ip.split(":", 1)
    except ValueError:
        #There is no ':' in the string
        port = None

    try:
        socket.inet_aton(ip)
    except socket.error:
        return False

    if port is not None:
        try:
            return (int(port) >= 1) and (int(port) <= 65535)
        except ValueError:
            #Not a valid integer
            return False

    return True

def run_command(logger, command, shell=False, fail_on_error=True):
    """
    Run a command and skip test if exit code is not 0

    Example:
    Run 'adb devices' and don't skip test if commands returns non-zero status
    run_command(logger, ["adb", "devices"], fail_on_error=False)

    @param logger: logger for debugging purposes
    @type logger: logging.logger
    @param command: Command to run
    @type command: list
    @param fail_on_error: When True, skip test if command returned a non-zero exit code
    @type fail_on_error: bool
    @rtype: list
    @return: Returns a list with stdout and stderr output
    """

    fd_out = tempfile.NamedTemporaryFile(delete=True)
    #With delete = True option, the files will be automatically removed after close
    fd_err = tempfile.NamedTemporaryFile(delete=True)
    try:
        process = subprocess.Popen(command, shell=shell, stdout=fd_out, stderr=fd_err)
    except WindowsError as e:
        logger.error(r"Problem running command, skipping test.\n%s" % e)
        # noinspection PyUnresolvedReferences
        if fail_on_error:
            pytest.fail(r"Problem running command, skipping test.\n%s" % e)

    # noinspection PyUnboundLocalVariable
    process.communicate()
    fd_out.seek(0)
    fd_err.seek(0)
    out = (fd_out.read(), fd_err.read())
    fd_out.close()
    fd_err.close()
    errcode = process.returncode

    if (errcode != 0) and fail_on_error:
        logger.error(r"Program  exited with code {errcode}, skipping test\n{out}".format(errcode=errcode, out=out))
        # noinspection PyUnresolvedReferences
        pytest.fail(r"Program exited with code {errcode}, skipping test\n{out}".format(errcode=errcode, out=out))

    return out


def start_process(logger, command, shell=False):
    """
    Run a command and skip test if exit code is not 0

    Example:
    Run 'adb devices' and don't skip test if commands returns non-zero status
    run_command(logger, ["adb", "devices"], fail_on_error=False)

    @param logger: logger for debugging purposes
    @type logger: logging.logger
    @param command: Command to run
    @type command: list
    @param fail_on_error: When True, skip test if command returned a non-zero exit code
    @type fail_on_error: bool
    @rtype: list
    @return: Returns a list with stdout and stderr output
    """

    try:
        process = subprocess.Popen(command, shell=shell)
    except WindowsError as e:
        logger.error(r"Problem running command, skipping test.\n%s" % e)
        # noinspection PyUnresolvedReferences
    return process

def sort_list(l):
    """
    Sorts a list, and if the list has other objects inside, it will iterate over them
    @param l: list to sort
    @type l: list
    @return: sorted list
    @rtype: list
    """
    if type(l) is not list:
        pytest.fail("Parameter l is not a list")

    for i, s in enumerate(l):
        if type(s) is list:
            l[i] = sort_list(s)
        if type(s) is dict:
            l[i] = sort_dict(s)

    l.sort()

    return l


def sort_dict(d):
    """
    Sorts a dictionary, and if the list has other objects inside, it will iterate over them
    @param d: dictionary to sort
    @type d: dict
    @return: sorted dictionary
    @rtype: dict
    """
    if type(d) is not dict:
        pytest.fail("Parameter d is not a dictionary")

    for key in d.keys():
        if type(d[key]) is list:
            #Sort list
            d[key] = sort_list(d[key])
        elif type(d[key]) is dict:
            d[key] = sort_dict(d[key])

    return d

