"""
Written by: Ian Doarn

Stock page

Interacts with the stock page and its sub pages
such as the product chooser
"""
from zbsmsa.utils.utilities import load_selectors, get_range_from_results, concat_selectors
from zbsmsa.utils.exceptions import ItemAddError, ItemFindError, SelectionError

class Stock:
    """
    Stock class
    
    requires the site class to be passed in as an object
    """
    def __init__(self, site):
        """
        :param site: Site class object
        """
        self.site = site

        # Load necessary selectors
        self.stock_page_selectors = load_selectors('stockTab.json')
        self.product_chooser_selectors = load_selectors('prodChooser.json')
        self.stock_page_table_selectors = load_selectors('stockTabTable.json')
        self.mutate_selectors = load_selectors('stockTabMutate.json')

        # store data from table
        self.current_table_data = []

    def stock_tab(self):
        """
        Stock tab button
        :return: 
        """
        self.site.wait.clickable(self.stock_page_selectors["stStockTabButton"])

    def stock_tab_search(self):
        """
        Stock tab search button
        :return: 
        """
        self.site.wait.clickable(self.stock_page_selectors["stStockTabSearchButton"])

    def stock_tab_mutate(self):
        """
        Stock tab mutate button
        :return: 
        """
        self.site.wait.clickable(self.mutate_selectors["stMutateButton"])

    def stock_tab_product_chooser(self):
        """
        Go to the stock tab product chooser
        but go to the stock tab first just in case we aren't there
        :return: 
        """
        self.stock_tab()
        self.site.wait.clickable(self.product_chooser_selectors["prodchooserSelect"])

    def mutate_stock(self, note, qty="1"):
        """
        Mutates stock that is selected from different
        inventory types
        
        :param note: Note to input 
        :param qty: quantity of stock to mutate, default @ "1"
        :return: 
        """

        # TODO: Figure out how to properly catch error messages!
        # TODO: Messages to catch: 'Some stock is already of the selected inventory type',
        # TODO:                    'All stock must be available'

        # Click mutate button
        self.stock_tab_mutate()

        # input quantity
        self.site.wait.send_keys_to(qty, self.mutate_selectors["stMutateQuantity"])

        # input note
        self.site.wait.send_keys_to(note, self.mutate_selectors["stMutateNote"])

        # Click save
        self.site.wait.clickable(self.mutate_selectors["stMutateSavebutton"])

        self.site.wait._for(1)

        # Go back a page to get ride of prompt
        self.site.driver.execute_script("window.history.go(-1)")

        # We will try to click cancel if we are given an error that does not
        # close the mutation window. If we are successful, hooray! if not then
        # there was no message and we go back to the stock page and
        # continue with our lives
        try:
            self.site.wait._for(1)
            self.site.wait.clickable(self.mutate_selectors["stMutateCancelButton"], time_out=2)
            return True
        except Exception as error:
            self.site.wait._for(1)
            self.stock_tab()
            return True
        finally:
            return False

    def select_row(self, r):
        """
        Select a specific row on the table
        
        :param r: Row number 
        :return: 
        """
        if r == 0:
            raise ValueError("row number cannot be 0")

        first_row = self.stock_page_table_selectors["stTableCheckBoxInit"]
        even_row = concat_selectors(self.stock_page_table_selectors["stTableCheckBoxRowEvenFront"],
                                    str(r),
                                    self.stock_page_table_selectors["stTableCheckBoxRowEvenBack"])
        odd_row = concat_selectors(self.stock_page_table_selectors["stTableCheckBoxRowOddFront"],
                                   str(r),
                                   self.stock_page_table_selectors["stTableCheckBoxRowOddBack"])
        # First row
        if r == 1:
            self.site.wait.clickable(first_row)
            return True
        elif r % 2 == 0:
            # is an even row
            self.site.wait.clickable(even_row)
            return True
        elif r % 2 != 0:
            # is an odd row
            self.site.wait.clickable(odd_row)
            return True

        # Was unable to select row
        raise SelectionError('stock table row [{}]'.format(str(r)))


    def iterate_search_table(self):
        """
        Iterates stock search table returning each entry
        
        !!!Browser window MUST be maximized for this to properly work!!!
        
        Process:
            1. Get results from bottom of page an format
            2. Get range with get_range_from_results
            3. iterate table
            4 iterate each rows tr html element and add it to the list trs keeping
             track of the row and the tr element
            5. for each tr in trs, we are going to get each element by the
             tag 'td'
            6. Then attempt to get the text from each element, if there is none,
             append 'None'
            7. Add that to the list of data, each entry in the list is a dictionary
             with the format {'row': row number, 'tr': table row, 'td': table cell, 'text': concat cell text}

        :return:
        """

        results = self.site.get_inner_html(self.stock_page_selectors["stStockTabResults"])

        if results.lower() == "no records":
            raise ValueError("No Records Found")

        # Get min and max range of table
        min_y, max_y = get_range_from_results(results)

        trs = []
        # iterate table, add 1 to max since range is 0 indexed
        for y in range(min_y, max_y + 1):
            front = self.stock_page_table_selectors["stTableRowFront"]
            back = self.stock_page_table_selectors["stTableRowBack"]

            try:
                # attempted to get the table row from the current row
                for tr in self.site.driver.find_elements_by_css_selector("{}{}{}".format(front, str(y), back)):
                    trs.append({'row': y, 'tr': tr})
            except Exception as error:
                print(error)

        data = []
        # iterate our list of table rows
        for tr in trs:
            # Get the row number
            # the table row object
            # and te table cells from the row
            row = tr['row']
            tr = tr['tr']
            td = tr.find_elements_by_tag_name('td')

            info = []
            for element in td:
                # for each cell in the row, attempt to get its text
                try:
                    if not info.__contains__(element.text):
                        info.append(element.text)
                # otherwise, set the text to none
                except Exception as error:
                    info.append('None')

            # Add the data to our list
            data.append({'row': row, 'tr': tr, 'td': td, 'text': '|'.join(info)})

        self.current_table_data = data


class ProductChooser():
    """
    Interacts with the Stock page product chooser
    """
    def __init__(self, stock_obj):
        """
        :param stock_obj: Stock class object
        """
        self.stock = stock_obj
        self.site = stock_obj.site
        self.driver = stock_obj.site.driver
        self.prod_selectors = self.stock.product_chooser_selectors

    def search(self, item):
        """
        Searches for item using product chooser
        :param item: item to search for
        :return: 
        """
        self.site.wait.send_keys_to(item, self.prod_selectors["prodchooserSearchBar"])
        self.site.wait.clickable(self.prod_selectors["prodchooserSearchButton"])

    def finish(self):
        """
        Click finish and return to stock page
        :return: None
        """
        self.site.wait.clickable(self.prod_selectors["prodchooserFinishButton"])

    def add(self, product_number):
        """
        Adds item to the product chooser
        
        First we iterate the table, creating our data.
        
        Then we check what row is came from, the row it cam from matters since
        the css selectors mutate depending on the row. We check to 
        see if it is the first row, an odd row, or an even row. We then try each selector
        combination.
        
        If we reach the end of if statement or the for loop
        then we raise an error since we were not able 
        to successfully add the item.
        
        :param product_number: Item to search for
        :return: Return only if item is successfully added
        """

        # collect our table data by iterating the table
        data = self.iterate_search_table()

        for row in data:
            r = row['row']
            text = row['text'].replace(' ','').split('|')

            try:
                results = [True for x in text if x == product_number]
                if len(results) != 0 and results[0] is True:
                    # Build our selectors
                    basic_selector = self.prod_selectors["prodchooserAdd"]
                    primary_selector = concat_selectors(self.prod_selectors["prodChooserAddLastFront"],
                                                        str(r),
                                                        self.prod_selectors["prodChooserAddLastBack"])
                    secondary_selector = concat_selectors(self.prod_selectors["prodChooserAddFront"],
                                                          str(r),
                                                          self.prod_selectors["prodChooserAddEvenBack"])

                    if r == 1:
                        # it is on the first row
                        self.site.wait.clickable(basic_selector)
                        return
                    elif r % 2 == 0:
                        # It is on an even row but the selectors
                        # for even rows mutate
                        # So we have to try these two combinations
                        try:
                            self.site.wait.clickable(primary_selector)
                            return
                        except Exception as error:
                            self.site.wait.clickable(secondary_selector)
                            return
                    elif r % 2 != 0:
                        # it is on an odd row
                        self.site.wait.clickable(primary_selector)
                        return

                    # should only get here if we cant add item
                    # or the css path on the site has mutated in some way
                    raise ItemAddError(product_number)
                else:
                    continue
            except Exception as error:
                raise ItemAddError(product_number, msg='Unable to find item in table. ERROR: {}'.format(error))

        # No match was found
        raise ItemAddError(product_number, msg='Unable to find item in table.')

    def iterate_search_table(self):
        """
        Iterates product chooser table returning each entry
        
        !!!Browser window MUST be maximized for this to properly work!!!
        
        Process:
            1. Get results from bottom of page an format
            2. Get range with get_range_from_results
            3. iterate table
            4 iterate each rows tr html element and add it to the list trs keeping
             track of the row and the tr element
            5. for each tr in trs, we are going to get each element by the
             tag 'td'
            6. Then attempt to get the text from each element, if there is none,
             append 'None'
            7. Add that to the list of data, each entry in the list is a dictionary
             with the format {'row': row number, 'tr': table row, 'td': table cell, 'text': concat cell text}
            
        :return: Data list
        """

        results = self.site.get_inner_html(self.prod_selectors["prodChooserResults"])

        # Get min and max range of table
        min_y, max_y = get_range_from_results(results)

        trs = []
        # iterate table, add 1 to max since range is 0 indexed
        for y in range(min_y, max_y + 1):
            front = self.prod_selectors["prodChooserTRpathFront"]
            back = self.prod_selectors["prodChooserTRpathBack"]

            try:
                # attempted to get the table row from the current row
                for tr in self.driver.find_elements_by_css_selector("{}{}{}".format(front, str(y), back)):
                    trs.append({'row': y, 'tr': tr})
            except Exception as error:
                print(error)


        data = []
        # iterate our list of table rows
        for tr in trs:
            # Get the row number
            # the table row object
            # and te table cells from the row
            row = tr['row']
            tr = tr['tr']
            td = tr.find_elements_by_tag_name('td')

            info = []
            for element in td:
                # for each cell in the row, attempt to get its text
                try: info.append(element.text)
                # otherwise, set the text to none
                except Exception as error: info.append('None')

            # Add the data to our list
            data.append({'row': row, 'tr': tr, 'td': td, 'text': '|'.join(info).replace(' ', '')})

        return data