# -*- coding: utf-8 -*-
#
#*******************************************************************************
#
#  Copyright 2022 RIEGL Laser Measurement Systems
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
#
#  SPDX-License-Identifier: Apache-2.0
#
#*******************************************************************************
#
"""
Point invert query
"""

from ctypes import byref, c_void_p, c_uint32

from . import library
from . import utilities


class QueryInvert:
    """
    Point invert query

    This class can be used to invert point attributes for all points or just
    those that meet some filter criteria. Calculating the inverted value (v')
    from the original value (v) and the attribute limits (min and max) is done
    as follows:

      v' = max - (v - min)

    For dynamic point attributes (bits/flags) this simply means to flip the
    bit (e.g. set "riegl.selected" to 1 if it was 0, otherwise set it to 0).

    This query is similar to using select() and update() and manually inverting
    the point attributes except that it might be faster (less processing time)
    and easier to use.

    Note You either must delete the query object or call close()
         __before__ the parent Pointcloud instance is closed/deleted!
    """

    def __init__(
        self,
        pointcloud,
        selection=None,
        nodes=None
    ):
        """
        Constructor

        Creates a query prepared for modifying points.
        """
        self.context = pointcloud.context
        self.pointcloud = pointcloud
        self.handle = c_void_p(None)
        self.buffers = list()  # used by value()

        try:
            if nodes is None:
                node = c_uint32(0)  # = all nodes
                self.context.check(
                    library.handle.rdb_pointcloud_query_invert_new(
                        self.context.handle,
                        self.pointcloud.handle,
                        node,
                        utilities.to_rdb_string(selection),
                        byref(self.handle)
                    )
                )
                return
            else:
                list_size = len(nodes)
                if list_size == 0:
                    return
                NodeArrayType = c_uint32 * list_size
                list_data = NodeArrayType(*nodes)
                list_size = c_uint32(list_size)
        except TypeError:  # 'nodes' doesn't seem to be iterable, assume scalar
            if nodes == 0:
                return
            list_data = c_uint32(nodes)
            list_size = c_uint32(1)

        self.context.check(
            library.handle.rdb_pointcloud_query_invert_nodes_new(
                self.context.handle,
                self.pointcloud.handle,
                byref(list_data), list_size,
                utilities.to_rdb_string(selection),
                byref(self.handle)
            )
        )

    def __del__(self):
        self.close()

    def __enter__(self):
        return self

    # noinspection PyUnusedLocal
    def __exit__(self, exc_type, exc_value, traceback):
        self.close()

    @property
    def valid(self):
        """"
        Check if query is not null

        A null query cannot be used to read points.

        Returns True if the query is not null
        """
        return self.handle != c_void_p(None)

    def close(self):
        """
        Finish query

        Call this function when done with modifying points.
        """
        if self.valid:
            library.handle.rdb_pointcloud_query_invert_delete(
                self.context.handle,
                byref(self.handle)
            )
            self.handle = c_void_p(None)

    def attribute(self, name):
        """
        Define attribute

        Use this function to define the point attribute to be inverted. To
        define multiple attributes, simply call this function once for each.

        Note: Point attributes having a length greater than 1 (vectors like
              the true color attribute "riegl.rgba") can only be inverted
              as a whole, i.e. you cannot invert particular vector elements
              (e.g. using "riegl.rgba[0]" will not work).
        """
        if self.valid:
            self.context.check(
                library.handle.rdb_pointcloud_query_invert_attribute(
                    self.context.handle,
                    self.handle,
                    utilities.to_rdb_string(name),
                )
            )

    def next(self, count):
        """
        Invert points

        Use this function to actually invert the point attribute(s).

        To process all points, you need to repeatedly call next() until it
        returns 0 (zero, see example 8). The number of points to process in
        one step is defined by 'count'. Please note that the actual number
        of processed points may be slightly different. To cancel processing
        just stop calling next() and cancel (rollback) the transaction.

        Returns the number of points processed
        """
        if not self.valid:
            return 0
        count = c_uint32(count)
        result = c_uint32(0)
        self.context.check(
            library.handle.rdb_pointcloud_query_invert_next(
                self.context.handle,
                self.handle,
                count,
                byref(result)
            )
        )
        return result.value

    def progress(self):
        """
        Progress

        This function returns a coarse progress information in percent (0-100%).
        Since the total number of returned points is not known in advance, this
        value just reflects the progress of the (internal) index traversal.
        """
        if not self.valid:
            return 0
        result = c_uint32(0)
        self.context.check(
            library.handle.rdb_pointcloud_query_invert_progress(
                self.context.handle,
                self.handle,
                byref(result)
            )
        )
        return result.value
