import os
import re
import shutil

from html import unescape
from robot.api import logger
from robot.libraries.BuiltIn import BuiltIn
from mykonos.core.core import Core
from mykonos.keywords.decorators import Decorators, Parallel
from mykonos.keywords.management_device import ManagementDevice


class GlobalElement(Core):
    def __init__(self):
        self.get = GetConditions()
        self.device_mobile = self.device()
        self.management_device = ManagementDevice()
        self.built_in = BuiltIn()
        self.index = 0

    @Parallel.device_check
    def open_notification(self, device=None, **settings):
        """Open notification a device.

        This keywords is used to open notification of device

        **Example:**
        || Open notification

        With Device/ Pararel :
        ||  @{emulator} =      |  192.168.1.1    | 192.168.1.2
        || Open notification   |device_parallel=@{emulator}

        """

        if device is not None:
            get_device = self.management_device.scan_current_device(device)
            return get_device().open.notification()
        else:
            return self.device_mobile.open.notification()

    @Parallel.device_check
    def open_quick_settings(self, device=None, **settings):
        """Open Quick Setting a device.

        This keywords is used to open setting of device

        **Example:**

        || Open Quick setting  |

        With Device/ Pararel :
        ||  @{emulator} =      |  192.168.1.1    | 192.168.1.2
        || Open Quick setting  |  device_parallel= @{emulator}

        """
        if device is not None:
            get_device = self.management_device.scan_current_device(device)
            return get_device().open.quick_settings()
        else:
            return self.device_mobile.open.quick_settings()

    @Parallel.device_check
    def clear_text(self, device=None, *argument, **settings):
        """Clear text on the text field base on locator.

        This keywords is used to clear text field.

        **Example:**

        ||Clear Text        |  className=sample class

        With Device/ Pararel :
        ||  @{emulator} =      |  192.168.1.1    | 192.168.1.2
        ||  Clear Text         | className=sample class     |device_parallel= @{emulator}
        """
        if 'locator' in settings:
            locator = settings['locator']
            return locator.clear_text()
        else:
            if 'device' is not None:
                get_device = self.management_device.scan_current_device(device)
                return get_device(*argument, **settings).clear_text()
            else:
                return self.device_mobile(*argument, **settings).clear_text()

    @Parallel.device_check
    def input_text(self, device=None, *argument, **settings):
        """Input text on the text field base on locator.

        This keywords is used to input text into text field.

        **Example:**

        || Input Text        |  className=sample class    input=text

        With Device/ Pararel :
        ||  @{emulator} =      |  192.168.1.1    | 192.168.1.2
        ||  Input Text         |  className=sample class | input=text  | device_parallel=@{emulator}

        """
        input = settings['input']
        del settings['input']

        if 'locator' in settings:
            locator = settings['locator']
            return locator.set_text(input)
        else:
            if device is not None:
                get_device = self.management_device.scan_current_device(device)
                return get_device(*argument, **settings).set_text(input)
            else:
                return self.device_mobile(*argument, **settings).set_text(input)

    @Parallel.device_check
    def count_elements(self, device=None, *argument, **settings):
        """Count total element from the page.

        This keywords is used to count total element on the device page.

        **Example:**

        || Count Elements          |  className=sample class

        With Device/ Pararel :
        ||  @{emulator} =   | 192.168.1.1    | 192.168.1.2
        ||  Count Element   | device_parallel=@{emulator}

        **Return:**

        Total of elements (int)

        """
        if 'locator' in settings:
            locator = settings['locator']
            return locator.count
        elif 'watcher' in settings:
            watcher = settings['watcher']
            del settings['watcher']

            return watcher.count
        else:
            if device is not None:
                get_device = self.management_device.scan_current_device(device)
                return get_device(*argument, **settings).count
            else:
                return self.device_mobile(*argument, **settings).count

    @Parallel.device_check
    def turn_on_screen(self, device=None, **settings):
        """Turn on Screen Device.

        **Example:**

        ||  Turn On Screen

        With Device/ Pararel :
        ||  @{emulator} =   | 192.168.1.1    | 192.168.1.2
        ||  Turn On Screen  | device_parallel=@{emulator}

        **Return:**

         True or False
        """
        if device is not None:
            get_device = self.management_device.scan_current_device(device)
            return get_device(**settings).screen.on()
        else:
            return self.device(**settings).screen.on()

    @Parallel.device_check
    def turn_off_screen(self, device=None, **settings):
        """Turn off Screen Device.

        **Example:**

        ||  Turn Off Screen

        With Device/ Pararel :
        ||  @{emulator} =   | 192.168.1.1    | 192.168.1.2
        ||  Turn Off Screen  | device_parallel=@{emulator}

        **Return:**

         True or False
        """
        if device is not None:
            get_device = self.management_device.scan_current_device(device)
            return get_device(**settings).screen.off()
        else:
            return self.device(**settings).screen.off()

    @Parallel.device_check
    def dump_xml(self, device=None, **settings):
        """Dump hierarchy of ui and will be saved as hierarchy.xml.

        **Example:**

        ||  Dump Xml        | file=sample.xml

        With Device /pararel :

        ||  @{emulator} =   |  192.168.1.1              | 192.168.1.2
        ||  Dump Xml        | file=sample.xml           | devices_pararel=@{emulator}

        **Return:**

        xml file of device
        """

        if 'file' in settings:
            file = settings['file']
            del settings['file']

        if device is not None:
            return self.management_device.scan_current_device(device).dump(file)
        else:
            return self.device().dump(file)

    @Parallel.device_check
    def capture_screen(self, file=None, device=None):
        """Capture screen of device testing.

        **Example:**

        ||  Capture Screen

        With file name:

        || Capture Screen        | file=sample

        With Device/ Pararel :
        ||  @{emulator} =   | 192.168.1.1    | 192.168.1.2
        || Capture Screen   | device_parallel=${emulator}
        || Capture Screen   | file=sample  | device_parallel=@{emulator}

        **Return:**

        screen capture of device(*.png)
        """
        file = self._get_file_capture_screen(file, device)
        get_curent_path = os.getcwd()
        xml_path = self._get_output_xml(get_curent_path)
        location_xml = os.path.dirname(xml_path)
        html_file = '</td></tr><tr><td colspan="3"><a href="%s">''<img src="%s" width="400px"></a>' % (file, file)
        html_convert_file = unescape(html_file)
        try:
            if location_xml != os.path.dirname(file):
                shutil.move(os.path.abspath(file), location_xml)
            else:
                logger.info('file is not need to move')

            logger.info(html_convert_file, True, False)

        except Exception as error:
            raise ValueError('file cannot be moved')

    def _get_output_xml(self, get_curent_path):
        for r, d, f in os.walk(get_curent_path):
            for files in f:
                if re.search("^.*xml", files):
                    xml_path = os.path.join(r, files)
                    return xml_path

    def _get_file_capture_screen(self, file=None, device=None):
        location = os.path.join(os.getcwd(), '')
        if file is not None:
            if device is not None:
                return self.device().screenshot(location+file+'.png')
            else:
                return self.management_device.scan_current_device(device).screenshot(location+file+'.png')
        else:
            self.index += 1
            filename = 'mykonos-screenshot-%d.png' % self.index
            if device is not None:
                return self.device().screenshot(location+filename)
            else:
                return self.management_device.scan_current_device(device).screenshot(location+filename)

class Click(Core):

    def __init__(self):
        self.device_mobile = self.device()
        self.management_device = ManagementDevice()

    @Parallel.device_check
    def click_element(self, device=None, *argument, **settings):
        """Click on UI base on locator.

        This keyword is used to click button or element of device.

        **Example:**

        ||  Click Element                    | className=sample class

        With Device/ Pararel :
        ||  @{emulator} =   | 192.168.1.1    | 192.168.1.2
        ||  Click Element                    | className=sample class  | device_parallel=@{emulator}
        """

        if 'locator' in settings:
            locator = settings['locator']
            return locator.click()

        else:
            if device is not None:
                get_devices = self.management_device.scan_current_device(device)
                return get_devices(*argument, **settings).click()

            elif 'watcher' in settings:
                watcher = settings['watcher']
                del settings['watcher']

                return watcher.click(*argument, **settings)
            else:
                return self.device_mobile(*argument, **settings).click()

    @Decorators.android_version
    def long_click_element(self, device=None, *argument, **settings):
        """Long click on UI base on locator.

        This keyword is used to long click button or element of device.


        **Example:**
        ||  Long Click Element                 | className=sample class

        With Device/ Pararel :
        ||  @{emulator} =   | 192.168.1.1    | 192.168.1.2
        ||  Long Click Element               | className=sample class  | device_parallel=@{emulator}

        """
        if 'locator' in settings:
            locator = settings['locator']
            return locator.long_click()
        else:
            if device is not None:
                get_devices = self.management_device.scan_current_device(device)
                return get_devices.long_click()
            else:
                return self.device_mobile(*argument, **settings).long_click()

    @Decorators.android_version
    def click_a_point(self, device=None, *argument, **settings):
        """Click into pointer target location.

         This keyword is used to click location  based on pointer X and Y.

         **Example:**

         ||  CLick A Point      |x=10   |y=20

         With Device/ Pararel :
         ||  @{emulator} =   | 192.168.1.1    | 192.168.1.2
         ||  CLick A Point   |x=10   |y=20    | device_parallel=@{emulator}

        """
        if 'x' in settings and 'y' in settings:
            x = settings['x']
            y = settings['y']
            del settings['x']
            del settings['y']
        else:
            raise ValueError('pointer x or y is refused')

        if device is not None:
            get_devices = self.management_device.scan_current_device(device)
            return get_devices.click(x, y)
        else:
            return self.device_mobile().click(x, y)


class GetConditions(Core):
    def __init__(self):
        self.device_mobile = self.device()
        self.management_device = ManagementDevice()

    @Parallel.device_check
    def get_info(self, device=None, value=None):
        """Get Info of Device.

        **Example:**

        ||  Get Info         |  value=displayRotation

         With Device/ Pararel :
         ||  @{emulator} =   | 192.168.1.1    | 192.168.1.2
         ||  Get Info        | value=displayRotation | device_parallel=@{emulator}

        **Return:**
        { u'displayRotation': 0,
          u'displaySizeDpY': 640,
          u'displaySizeDpX': 360,
          u'currentPackageName': u'com.android.launcher',
          u'productName': u'takju',
          u'displayWidth': 720,
          u'sdkInt': 18,
          u'displayHeight': 1184,
          u'naturalOrientation': True
        }
        """

        if value is None:
            result = self.device(device).info
        else:
            result = self.device(device).info[value]

        return result

    @Parallel.device_check
    def get_text(self, device=None, *argument, **settings):
        """Get text from element base on locator.

        **Example:**

        ||  Get Text         |  className=sample class

         With Device/ Pararel :
         ||  @{emulator} =   | 192.168.1.1    | 192.168.1.2
         ||  Get Text        | className=sample class | device_parallel=@{emulator}

        **Return:**
        String
        """
        if 'locator' in settings:
            locator = settings['locator']
            return locator.info['text']
        else:
            if device is not None:
                get_devices = self.management_device.scan_current_device(device)
                return get_devices(*argument, **settings).info['text']
            else:
                return self.device_mobile(*argument, **settings).info['text']

    @Parallel.device_check
    def get_element_attribute(self, device=None, *argument, **settings):
        """Get element attribute keyword of device.

        **List of Elements:**

         childCount, bounds, className, contentDescription,
         packageName, resourceName, text, visibleBounds,
         checkable, checked, clickable, enabled, focusable, disable,
         focused, longClickable, scrollable, selected

         **Example:**

         || Get Element Attribute    |  className=sample   | element=text

          With Device/ Pararel :
          ||  @{emulator} =           | 192.168.1.1    | 192.168.1.2
          || Get Element Attribute    | className=sample class | device_parallel=@{emulator}

         **Return:**

         Attribute from element device
        """
        element = settings['element']
        del settings['element']

        if 'locator' in settings:
            locator = settings['locator']
            return locator.info[element]
        else:
            if device is not None:
                get_devices = self.management_device.scan_current_device(device)
                return get_devices(*argument, **settings).info[element]
            else:
                return self.device_mobile(*argument, **settings).info[element]

    @Parallel.device_check
    def get_element(self, device=None, *argument, **settings):
        """Get element info of device .
        This keyword is used to get element info of device.

         **Example:**

        || Get Element    ||

      With Device/ Pararel :
      ||  @{emulator} =     || 192.168.1.1    | 192.168.1.2
      ||  Get Element       || device_parallel=@{emulator}

        **Return:**

        {'currentPackageName': 'com.google.android.apps.nexuslauncher',
         'displayHeight': 1794,
         'displayRotation': 0,
         'displaySizeDpX': 411,
         'displaySizeDpY': 731,
         'displayWidth': 1080,
         'productName': 'sdk_google_phone_x86',
         'screenOn': True,
         'sdkInt': 25,
         'naturalOrientation': True}
        """
        if 'locator' in settings:
            locator = settings['locator']
            return locator.info
        else:
            if device is not None:
                get_devices = self.management_device.scan_current_device(device)
                return get_devices(*argument, **settings).info
            else:
                return self.device_mobile(*argument, **settings).info

    @Parallel.device_check
    def get_element_by_coordinate_x(self, device=None, *argument, **settings):
        """Get element by coordinate X.
        This keyword is used to get coordinate X of device.

         **Example:**

         || Get Element By Coordinate X  |  className=sample class

      With Device/ Pararel :
          ||  @{emulator} =     || 192.168.1.1    | 192.168.1.2
          ||  Get Element By Coordinate X     || device_parallel=@{emulator} || className=sample class

        **Return:**

        Coordinate x(int)
        """
        if device is not None:
            bound = self.get_element_attribute(element='bounds', devices_parallel=device)
        else:
            bound = self.get_element_attribute(element='bounds', *argument, **settings)

        bottom = bound['bottom']
        top = bound['top']
        elm_x = (top+bottom)+top

        return elm_x

    @Parallel.device_check
    def get_element_by_coordinate_y(self, device=None, *argument, **settings):
        """Get element by coordinate Y.

        This keyword is used to get coordinate Y of device.

         **Example:**

        || Get Element By Coordinate Y  |  className=sample class

      With Device/ Pararel :
        ||  @{emulator} =     || 192.168.1.1    | 192.168.1.2
        ||  Get Element By Coordinate Y     || device_parallel=@{emulator} || className=sample class

        **Return:**

        Coordinate y(int)
        """

        if device is not None:
            bound = self.get_element_attribute(element='bounds', devices_parallel=device)
            display_height = self.get_height(devices_parallel=device)
        else:
            bound = self.get_element_attribute(element='bounds', *argument, **settings)
            display_height = self.get_height()

        left = bound['left']
        right = bound['right']
        elm_y = display_height-(right+left)
        return elm_y

    @Parallel.device_check
    def get_width(self, device=None):
        """Get width from display of device.

        This keyword is used to get widh of device,

        **Example:**

        || Get Width

      With Device/ Pararel :
        ||  @{emulator} =     || 192.168.1.1    | 192.168.1.2
        || Get Width          || device_parallel=@{emulator}

        **Return:**

        Width of device(int)
        """
        if device is None:
            get_device = self.device().info['displayWidth']
            return get_device
        else:
            get_devices = self.management_device.scan_current_device(device).info['displayWidth']
            return get_devices

    @Parallel.device_check
    def get_height(self, device=None):
        """Get height from display of device.

        This keyword is used to get widh of device.

        **Example:**

        || Get Height

      With Device/ Pararel :
        ||  @{emulator} =     || 192.168.1.1    | 192.168.1.2
        || Get Height          || device_parallel=@{emulator}

        **Return:**

        Height of device(int)
        """
        if device is None:
            get_device = self.device().info['displayHeight']
            return get_device
        else:
            get_devices = self.management_device.scan_current_device(device).info['displayHeight']
            return get_devices

    @Parallel.device_check
    def get_position(self, position=0, device=None, *argument, **settings):
        """Get Position of element.

        This keyword is used to get position of device element.

        **Example:**

        || Get Position      ||  className=sample   || position=1

      With Device/ Pararel :
        ||  @{emulator} =    || 192.168.1.1    || 192.168.1.2
        || Get Position      || device_parallel=@{emulator} || className=sample || position=1

        **Return:**

        Width of device(int)
        """
        if 'locator' in settings:
            locator = settings['locator']
            del settings['locator']
            return locator[position]
        else:
            if device is not None:
                devices = self.management_device.scan_current_device(device)
                return devices(*argument, **settings)[position]
            else:
                return self.device_mobile(*argument, **settings)[position]


class ExpectedConditions(Core):
    def __init__(self):
        self.device_mobile = self.device()
        self.get_conditions = GetConditions()
        self.management_device = ManagementDevice()

    @Parallel.device_check
    def page_should_contain_element(self, device=None, *argument, **settings):
        """Page should contain element.
        The keyword is used to verify the page is contains locator element.

        **Example:**

        || Page Should Contain Element | className=sample class

        With Device/ Pararel :
        || @{emulator} =      |  192.168.1.1    | 192.168.1.2
        || Page Should Contain Element   |device_parallel=@{emulator}

        **Return:**

        True or False
        """

        if 'locator' in settings:
            locator = settings['locator']
            del settings['locator']
            return locator.exists
        else:
            if device is not None:
                devices = self.management_device.scan_current_device(device)
                return devices(*argument, **settings).exists
            else:
                return self.device_mobile(*argument, **settings).exists

    @Parallel.device_check
    def page_should_contain_text(self, device=None, *argument, **settings):
        """Page should contain text.
        The keyword is used to verify the page is contains text.

        **Example:**

        || Page Should Contain Text | text=sample text

        With Device/ Pararel :
        || @{emulator} =      |  192.168.1.1    | 192.168.1.2
        || Page Should Contain Text   |device_parallel=@{emulator}

        **Return:**

        True or False
        """

        if 'locator' in settings:
            locator = settings['locator']
            del settings['locator']
            return locator.exists
        else:
            if device is not None:
                get_devices = self.management_device.scan_current_device(device)
                return get_devices(*argument, **settings).exists

            elif 'watcher' in settings:
                watcher = settings['watcher']
                del settings['watcher']

                return watcher.exists(*argument, **settings)
            else:
                return self.device_mobile(*argument, **settings).exists

    @Parallel.device_check
    def __get_device_global(self, device=None, *argument, **settings):
        if 'locator' in settings:
            device = settings['locator']
        else:
            if 'device' in settings:
                device = settings['device']
                del settings['device']

                device = device(*argument, **settings)
            else:
                device = self.device_mobile(*argument, **settings)

        return device

    @Parallel.device_check
    def page_should_not_contain_element(self, device=None, *argument, **settings):
        """Page should not contain element.

        The keyword is used to verify the page is not contains element.

        **Example:**

        || Page Should Not Contain Element | className=sample element

        With Device/ Pararel :
        || @{emulator} =      |  192.168.1.1    | 192.168.1.2
        || Page Should Not Contain Element   |device_parallel=@{emulator}

        **Return:**

        True or False
        """
        if 'locator' in settings:
            locator = settings['locator']
            del settings['locator']
            result = locator.exists
        else:
            if device is not None:
                devices = self.management_device.scan_current_device(device)
                result = devices(*argument, **settings).exists
            else:
                result = self.device_mobile(*argument, **settings).exists

        if result is True:
            return False
        else:
            return True

    @Parallel.device_check
    def page_should_not_contain_text(self, device=None, *argument, **settings):
        """Page should not contain text.

        The keyword is used to verify the page is not contains text.

        **Example:**

        || Page Should Contain Text | text=sample text

        With Device/ Pararel :
        || @{emulator} =      |  192.168.1.1    | 192.168.1.2
        || Page Should Contain Text   |device_parallel=@{emulator}


        **Return:**

        True or False

        """
        if 'locator' in settings:
            locator = settings['locator']
            del settings['locator']
            result = locator.exists
        else:
            if device is not None:
                devices = self.management_device.scan_current_device(device)
                result = devices(*argument, **settings).exists
            else:
                result = self.device_mobile(*argument, **settings).exists

        if result is True:
            return False
        else:
            return True

    @Parallel.device_check
    def text_should_be_enabled(self, device=None, *argument, **settings):
        """Text should be enabled.

        The keyword is used to identify text enable.

        **Example:**

        || Text Should Be Enabled | text=sample text

        With Device/ Pararel :
        || @{emulator} =      |  192.168.1.1    | 192.168.1.2
        || Text Should Be Enabled   |device_parallel=@{emulator}

        **Return:**

        True or False
        """
        if 'locator' in settings:
            locator = settings['locator']
            del settings['locator']
            result = locator.info['enabled']
        else:
            if device is not None:
                devices = self.management_device.scan_current_device(device)
                result = devices(*argument, **settings).info['enabled']
            else:
                result = self.device_mobile(*argument, **settings).info['enabled']

        if result is not True:
            return False
        else:
            return True

    @Parallel.device_check
    def text_should_be_disabled(self, device=None, *argument, **settings):
        """Text should be disabled.

        The keyword is used to identify text disabled.

        **Example:**

        || Element Should Be Disabled | text=sample text

        With Device/ Pararel :
        || @{emulator} =      |  192.168.1.1    | 192.168.1.2
        || Element Should Be Disabled   |device_parallel=@{emulator}

        **Return:**

        True or False
        """
        if 'locator' in settings:
            locator = settings['locator']
            result = locator.info['enabled']
        else:
            if device is not None:
                devices = self.management_device.scan_current_device(device)
                result = devices(*argument, **settings).info['enabled']
            else:
                result = self.device_mobile(*argument, **settings).info['enabled']

        if result is True:
            return False
        else:
            return True

    @Parallel.device_check
    def check_element_visible(self, device=None, *argument, **settings):
        """Check element visible.

        The keyword is used to check element visible.

        **Example:**

        || Check Element Visible | className=sampleclassName
        With Device/ Pararel :
        ||  @{emulator} =      |  192.168.1.1    | 192.168.1.2
        || Check Element Visible   |device_parallel=@{emulator}

        **Return:**

        True or False
        """
        if 'locator' in settings:
            result = settings['locator']
            del settings['locator']

        else:
            if device is not None:
                devices = self.management_device.scan_current_device(device)
                result = devices(*argument, **settings)
            else:
                result = self.device_mobile(*argument, **settings)

        try:
            check_element_visible = result.info['visibleBounds']
            if check_element_visible is not None:
                return True
        except Exception as error:
            return False

    @Parallel.device_check
    def check_element_non_visible(self, device=None, *argument, **settings):
        """Check element non visible.

        The keyword is used to check element non visible.

        **Example:**

        || Check Element Non Visible | className=sampleclassName

        With Device/ Pararel :
        ||  @{emulator} =      |  192.168.1.1    | 192.168.1.2
        || Check Element Non Visible   |device_parallel=@{emulator}

        **Return:**

        True or False
        """
        if 'locator' in settings:
            locator = settings['locator']
            del settings['locator']
        else:
            if device is not None:
                devices = self.management_device.scan_current_device(device)
                result = devices(*argument, **settings)
            else:
                result = self.device_mobile(*argument, **settings)
        try:
            check_element_visible = result.info['visibleBounds']
            if check_element_visible is not None:
                return False
        except Exception as error:
            return True
