class AirportDbClient:
    """
    Class to connect to the world readable Cloudant database. The class is a
    context manager, and must be used inside a with() statement.

    List of airports can be pulled from the database with a rectangle search query.

    Attributes:
        url (str): The url of the Cloudant account.
        db_name (str): Name of the Cloudant database.
        design_doc (str): Name of the design document.
        search_index (str): The search index used.
        client (Cloudant): Cloudant client instance.
        db (CloudantDatabase): Cloudant database instance.
    """
    def __init__(self, url, db_name, design_doc, search_index):
        self.url = url
        self.db_name = db_name
        self.design_doc = design_doc
        self.search_index = search_index

    def __enter__(self):
        """
        Runs when the control flow enters the with() statement.
        Creates the Cloudant client instant and the Cloudant database instant.

        To connect to the world readable database, I had to use the 'admin_party' flag.
        """
        self.client = Cloudant(" ",
                               " ",
                               admin_party=True,
                               url=self.url,
                               connect=True)

        self.db = CloudantDatabase(self.client, self.db_name)

        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """
        Runs when the control flow leaves the with() statement.
        """
        self.client.disconnect()

    @staticmethod
    def _format_query(rect):
        """
        Formats the query in the format of 'lat:[X TO Y] AND lon:[V TO Z]'.
        Creates one or two queries, depending on its input.

        Args:
            rect (list): List of 4 (lat1, lat2, lon1, lon2) floats or
                    list of 6 (lat1, lat2, lon1, lon2, lon3, lon4) floats.

        Returns:
            list: List of one query string, or two query strings.
        """

        if len(rect) == 4:
            query = f"lat:[{rect[0]} TO {rect[1]}] AND lon:[{rect[2]} TO {rect[3]}]"
            return [query]

        qr1 = f"lat:[{rect[0]} TO {rect[1]}] AND lon:[{rect[2]} TO {rect[3]}]"
        qr2 = f"lat:[{rect[0]} TO {rect[1]}] AND lon:[{rect[4]} TO {rect[5]}]"

        return [qr1, qr2]

    def get_search_results(self, rectangles):
        """
        Getting all of the search results from the database based on the queries submitted.

        Args:
            rectangles (list): Rectangle data for the query.
                Or data for two rectangles if 2 query is present.

        Returns:
            list: The list of airports from the database.

        Raises:
            AirportDbException: Every problem with the database and its connection (e.g.
                no internet, or wrong url and name etc.)
                No errors occur when the Cloudant client and database instance are created,
                therefore errors are only raised here, when the search method is called,
                because this is where the Cloudant database is actively used.
        """

        queries = self._format_query(rectangles)
        total_number = 0
        results = []

        try:
            for qr in queries:  # 1 or 2 queries

                # The max limit of results pulled at once for a request is 200.
                # Hence, when there are more than 200 results, multiple requests
                # must be sent.

                q_result = self.db.get_search_result(self.design_doc,
                                                     self.search_index,
                                                     query=qr,
                                                     limit=200)

                # NOTE: when 0 airports match the query, q_result will
                # look like {"total_rows":0,"bookmark":"g2o","rows":[]}

                # NOTE: the 'total_rows' field contains the total number of results,
                # not the number of results in the current query result.

                total_number = total_number + q_result['total_rows']
                results.extend([r['fields'] for r in q_result['rows']])

                while len(results
                          ) < total_number:  # Paging through all the results

                    # To get to the next page of results, the bookmark
                    # field of the previous request is used.
                    bookmark = q_result['bookmark']

                    q_result = self.db.get_search_result(self.design_doc,
                                                         self.search_index,
                                                         query=qr,
                                                         limit=200,
                                                         bookmark=bookmark)

                    results.extend([r['fields'] for r in q_result['rows']])

        except Exception as ex:  # Any problem when trying to connect to and use the database
            raise AirportDbException(ex)

        return results