"""This module provides all classes that manipulate the database for received events

It provides methods to insert/delete/update columns on the database depending on
which event was received by the `update` method on `WebhookDataWriter` and passed to
`update` on `DataProcessor`.

"""
import sqlalchemy
from sqlalchemy.orm.attributes import flag_modified

from mconf_aggr.aggregator.aggregator import AggregatorCallback, CallbackError
from mconf_aggr.aggregator.utils import time_logger
from mconf_aggr.logger import get_logger
from mconf_aggr.webhook.database import DatabaseConnector
from mconf_aggr.webhook.database_model import (
    Institutions,
    Meetings,
    MeetingsEvents,
    Recordings,
    Servers,
    SharedSecrets,
    UsersEvents,
)
from mconf_aggr.webhook.exceptions import (
    DatabaseNotReadyError,
    InvalidWebhookEventError,
    WebhookDatabaseError,
)

session_scope = DatabaseConnector.get_session_scope()

class Status:
    PROCESSING = "processing"
    PROCESSED = "processed"
    PUBLISHED = "published"
    UNPUBLISHED = "unpublished"
    DELETED = "deleted"

class DatabaseEventHandler:
    """This is an abstract class that handles webhook events."""

    def __init__(self, session, logger=None):
        """Constructor of the DataEventHandler.

        Parameters
        ----------
        session : sqlalchemy.Session
            Session used by SQLAlchemy to interact with the database.
        """
        self.session = session
        self.logger = logger or get_logger()

    def handle(self, event):
        """This method is meant to be implemented downstream.

        Raises
        ------
        NotImplementedError
            If called from an object of class DatabaseEventHandler.
        """
        raise NotImplementedError()

class MeetingCreatedHandler(DatabaseEventHandler):
    """This class handles meeting-created events."""

    class MeetingCreatedMetadata:
        def __init__(self, metadata, default_value="", logger=None):
            self._metadata = metadata
            self._default_value = default_value
            self._logger = logger or get_logger()

        def __getattr__(self, name):
            field = name.replace("_", "-")
            value = None
            try:
                value = self._metadata.get(field, self._default_value)
            except AttributeError:
                value = self._default_value

            return value

    def handle(self, event):
        """Implementation of abstract handle method from DatabaseEventHandler.

        Parameters
        ----------
        event : event_mapper.WebhookEvent
            Event to be handled and written to database.
        """

        event = event.event

        self.logger.info(
            f"Processing meeting-created event for internal-meeting-id: \
                         '{event.internal_meeting_id}'."
        )

        # Create tables meetings_events and meetings.
        if (
            self.session.query(MeetingsEvents)
            .filter(MeetingsEvents.internal_meeting_id == event.internal_meeting_id)
            .first()
        ):
            self.logger.warning(
                f"Meeting with internal-meeting-id '{event.internal_meeting_id}' \
                                already exists."
            )
            return

        new_meetings_events = MeetingsEvents(**event._asdict())
        new_meetings_events.has_forcibly_ended = False
        new_meetings_events.unique_users = 0
        new_meetings_events.start_time = event.create_time

        metadata = self.MeetingCreatedMetadata(event.meta_data, None, self.logger)
        new_meetings_events.shared_secret_guid = metadata.mconf_shared_secret_guid
        new_meetings_events.shared_secret_name = metadata.mconf_secret_name
        new_meetings_events.server_guid = metadata.mconf_server_guid
        new_meetings_events.server_url = metadata.mconf_server_url
        new_meetings_events.institution_guid = metadata.mconf_institution_guid

        if new_meetings_events.parent_meeting_id == "bbb-none":
            new_meetings_events.parent_meeting_id = None

        if metadata.mconf_shared_secret_guid and not metadata.mconf_secret_name:
            new_meetings_events.shared_secret_name = (
                self.session.query(SharedSecrets)
                .filter(SharedSecrets.guid == metadata.mconf_shared_secret_guid)
                .first()
                .name
            )

        if not metadata.mconf_shared_secret_guid:
            self.logger.info(
                f"Empty shared secret guid, meeting '{event.internal_meeting_id}' \
                             insertion falling back to institution name: '{metadata.mconflb_institution_name}'"
            )
            # fallback to name of institution
            try:
                # Find the secret using mconflb-institution-name which corresponds
                # to the shared secret name. The use of term 'institution' is
                # misleading here
                found_secret = (
                    self.session.query(SharedSecrets)
                    .filter(SharedSecrets.name == metadata.mconflb_institution_name)
                    .first()
                )
                new_meetings_events.shared_secret_guid = found_secret.guid
                self.logger.info(
                    f"Found secret: '{found_secret.name}' for meeting \
                                 '{event.internal_meeting_id}'"
                )

                # We found the secret, try to find its institution to complete
                # the table with information
                try:
                    found_institution = (
                        self.session.query(Institutions)
                        .filter(Institutions.guid == found_secret.institution_guid)
                        .first()
                    )
                    new_meetings_events.institution_guid = found_institution.guid
                    new_meetings_events.shared_secret_name = found_secret.name
                except RuntimeError:
                    self.logger.warning(
                        f"Could not find institution for secret \
                                        '{found_secret.name}'"
                    )

                self.logger.info(
                    f"Found institution: '{found_institution.name}' \
                                 for meeting '{event.internal_meeting_id}'"
                )
            except AttributeError:
                self.logger.warning(
                    f"Could not match institution name \
                                    '{metadata.mconflb_institution_name}' to an institution"
                )

        if not metadata.mconf_server_guid and not metadata.mconf_server_url:
            servers_table = (
                self.session.query(Servers).filter(Servers.name == event.server_url).first()
            )

            new_meetings_events.server_url, new_meetings_events.server_guid = (
                servers_table.name,
                servers_table.guid,
            )

        # running external meeting id must be unique
        if (
            self.session.query(Meetings)
            .filter(Meetings.ext_meeting_id == event.external_meeting_id)
            .first()
        ):
            self.logger.warning(f"Meeting with external-meeting-id '{event.external_meeting_id}'")
            return

        new_meeting = Meetings(
            running=False,
            has_user_joined=False,
            participant_count=0,
            listener_count=0,
            voice_participant_count=0,
            video_count=0,
            moderator_count=0,
            attendees=[],
            m_shared_secret_guid=new_meetings_events.shared_secret_guid,
            m_institution_guid=new_meetings_events.institution_guid,
            ext_meeting_id=new_meetings_events.external_meeting_id,
            int_meeting_id=new_meetings_events.internal_meeting_id,
            transfer=False,
            transfer_count=0,
        )
        new_meeting.meeting_event = new_meetings_events

        self.session.add(new_meeting)

class MeetingEndedHandler(DatabaseEventHandler):
    """This class handles meeting-ended events."""

    def handle(self, event):
        """Implementation of abstract handle method from DatabaseEventHandler.

        Parameters
        ----------
        event : event_mapper.WebhookEvent
            Event to be handled and written to database.
        """

        event = event.event

        int_id = event.internal_meeting_id
        self.logger.info(f"Processing meeting-ended event for internal-meeting-id: '{int_id}'.")

        # Table meetings_events to be updated.
        meetings_event = (
            self.session.query(MeetingsEvents)
            .filter(MeetingsEvents.internal_meeting_id == int_id)
            .first()
        )

        if meetings_event:
            # Update all users that don't have a leave time to the meeting's end time
            (
                self.session.query(UsersEvents)
                .filter(
                    UsersEvents.meeting_event_id == meetings_event.id,
                    UsersEvents.leave_time.is_(None),
                )
                .update({"leave_time": event.end_time}, synchronize_session="fetch")
            )
            # make sure the users events are updated before deleting the meeting
            self.session.commit()

            meetings_event.end_time = event.end_time

            self.session.add(meetings_event)

            # Table meetings to be updated.
            meetings_table = (
                self.session.query(Meetings).filter(Meetings.int_meeting_id == int_id).first()
            )

            if meetings_table:
                self.session.delete(meetings_table)

class UserJoinedHandler(DatabaseEventHandler):
    """This class handles user-joined events."""

    def handle(self, event):
        """Implementation of abstract handle method from DatabaseEventHandler.

        Parameters
        ----------
        event : event_mapper.WebhookEvent
            Event to be handled and written to database.
        """

        event = event.event

        int_id = event.internal_meeting_id

        self.logger.info(
            f"Processing user-joined event for internal-user-id \
                         '{event.internal_user_id}'.'"
        )

        users_events_table = self._get_users_events(event)

        # Create attendee JSON for meetings table.
        attendee = {
            "external_user_id": event.external_user_id,
            "internal_user_id": event.internal_user_id,
            "full_name": str(event.name),
            "role": event.role,
            "is_presenter": event.is_presenter,
            "is_listening_only": False,
            "has_joined_voice": False,
            "has_video": False,
            "user_data": event.userdata,
        }

        # Query for meetings_events to link with users_events table.
        meetings_event = (
            self.session.query(MeetingsEvents)
            .filter(MeetingsEvents.internal_meeting_id == int_id)
            .first()
        )

        if meetings_event:
            users_events_table.meeting_event = meetings_event

            # Table meetings to be updated.
            meetings_table = (
                self.session.query(Meetings).filter(Meetings.int_meeting_id == int_id).first()
            )

            if meetings_table:
                meetings_table.attendees = self._attendee_json(meetings_table.attendees, attendee)
                self._update_meeting(meetings_table)

                # SQLAlchemy was not considering the attendees array as modified, so it
                # had to be forced.
                flag_modified(meetings_table, "attendees")

                self.session.add(users_events_table)
                self.session.add(meetings_table)
                self.session.flush()
            else:
                self.logger.warning(f"No meeting found for user '{event.internal_user_id}'.")
                raise WebhookDatabaseError(f"no meeting found for user '{event.internal_user_id}'")

            # Update unique_users in table meetings_events.
            users_joined = (
                self.session.query(UsersEvents)
                .join(UsersEvents.meeting_event)
                .filter(MeetingsEvents.internal_meeting_id == int_id)
                .count()
            )

            meetings_event.unique_users = users_joined

            # Meeting starts when first user joins it.
            if users_joined == 1:
                meetings_event.start_time = event.join_time

            self.session.add(meetings_event)

    def _get_users_events(self, raw_event):
        event_dict = raw_event._asdict()

        column_names = set(column.key for column in UsersEvents.__table__.columns)
        keys = event_dict.keys()

        for key in list(keys):
            if key not in column_names:
                del event_dict[key]

        users_events = UsersEvents(**event_dict)

        return users_events

    def _attendee_json(self, base, new):
        if not base:
            arr = []
            arr.append(new)

            return arr
        else:
            arr = base
            for elem in arr:
                if elem["internal_user_id"] == new["internal_user_id"]:
                    return arr
            arr.append(new)

            return arr

    def _update_meeting(self, meetings_table):
        meetings_table.running = True
        _update_meeting(meetings_table)
        meetings_table.has_user_joined = True

class UserLeftHandler(DatabaseEventHandler):
    """This class handles user-left events."""

    def handle(self, event):
        """Implementation of abstract handle method from DatabaseEventHandler.

        Parameters
        ----------
        event : event_mapper.WebhookEvent
            Event to be handled and written to database.
        """

        event = event.event

        user_id = event.internal_user_id
        int_id = event.internal_meeting_id
        self.logger.info(
            f"Processing user-left message for internal-user-id \
                         '{user_id}' in meeting '{int_id}'."
        )

        # Table meetings to be updated.
        meetings_table = (
            self.session.query(Meetings)
            .join(Meetings.meeting_event)
            .filter(MeetingsEvents.internal_meeting_id == int_id)
            .first()
        )

        if meetings_table:
            self._remove_attendee(meetings_table, user_id)
            self._update_meeting(meetings_table)

            # Mark meetings.attendees as modified for SQLAlchemy.
            flag_modified(meetings_table, "attendees")

            self.session.add(meetings_table)
        else:
            self.logger.warning(f"No meeting found with internal-meeting-id '{int_id}'.")

        # Table users_events to be updated.
        users_table = (
            self.session.query(UsersEvents).filter(UsersEvents.internal_user_id == user_id).first()
        )

        if users_table:
            # Update table users_events.
            users_table.leave_time = event.leave_time
        else:
            self.logger.warning(f"No user found with internal-user-id '{user_id}'.")

    def _remove_attendee(self, meetings_table, user_id):
        for idx, attendee in enumerate(meetings_table.attendees):
            if attendee["internal_user_id"] == user_id:
                del meetings_table.attendees[idx]

    def _update_meeting(self, meetings_table):
        _update_meeting(meetings_table)

class UserEventHandler(DatabaseEventHandler):
    """This class handles general user events."""

    def handle(self, event):
        """Implementation of abstract handle method from DatabaseEventHandler.

        Parameters
        ----------
        event : event_mapper.WebhookEvent
            Event to be handled and written to database.
        """
        event_type = event.event_type
        event = event.event

        self.event_type = event_type

        user_id = event.internal_user_id
        int_id = event.internal_meeting_id
        self.logger.info(
            f"Processing {event.event_name} event for internal-user-id \
                         '{user_id}' on meeting '{int_id}'."
        )

        # Table meetings to be updated.
        meetings_table = (
            self.session.query(Meetings)
            .join(Meetings.meeting_event)
            .filter(MeetingsEvents.internal_meeting_id == int_id)
            .first()
        )

        if meetings_table:
            self._update_attendees(meetings_table.attendees, event, user_id)
            self._update_meeting(meetings_table)

            flag_modified(meetings_table, "attendees")

            self.session.add(meetings_table)
        else:
            self.logger.warning(f"No meeting found with internal-meeting-id '{int_id}'.")

    def _update_meeting(self, meetings_table):
        _update_meeting(meetings_table)

    def _update_attendees(self, base, update, user_id):
        for attendee in base:
            if attendee["internal_user_id"] == user_id:
                if update.event_name == self.event_type:
                    self._update_attendee(attendee, update)

    def _update_attendee(self, attendee, update):
        raise NotImplementedError("Give specific behavior for the user event")

class UserVoiceEnabledHandler(UserEventHandler):
    """This class adds specific behavior for user-audio-voice-enabled events."""

    def _update_attendee(self, attendee, update):
        attendee["has_joined_voice"] = update.has_joined_voice
        attendee["is_listening_only"] = update.is_listening_only

class UserVoiceDisabledHandler(UserEventHandler):
    """This class adds specific behavior for user-audio-voice-disabled events."""

    def _update_attendee(self, attendee, update):
        attendee["has_joined_voice"] = False
        attendee["is_listening_only"] = False

class UserListenOnlyEnabledHandler(UserEventHandler):
    """This class adds specific behavior for user-audio-listen-only-enabled events."""

    def _update_attendee(self, attendee, update):
        attendee["is_listening_only"] = True

class UserListenOnlyDisabledHandler(UserEventHandler):
    """This class adds specific behavior for user-audio-listen-only-disabled events."""

    def _update_attendee(self, attendee, update):
        attendee["is_listening_only"] = False

class UserCamBroadcastStartHandler(UserEventHandler):
    """This class adds specific behavior for user-cam-broadcast-start events."""

    def _update_attendee(self, attendee, update):
        attendee["has_video"] = True

class UserCamBroadcastEndHandler(UserEventHandler):
    """This class adds specific behavior for user-cam-broadcast-end events."""

    def _update_attendee(self, attendee, update):
        attendee["has_video"] = False

class UserPresenterAssignedHandler(UserEventHandler):
    """This class adds specific behavior for user-presenter-assigned events."""

    def _update_attendee(self, attendee, update):
        attendee["is_presenter"] = True

class UserPresenterUnassignedHandler(UserEventHandler):
    """This class adds specific behavior for user-presenter-unassigned events."""

    def _update_attendee(self, attendee, update):
        attendee["is_presenter"] = False

class RapArchiveHandler(DatabaseEventHandler):
    """This class handles rap-archive-ended recording events."""

    def handle(self, event):
        """Implementation of abstract handle method from DatabaseEventHandler.

        Parameters
        ----------
        event : event_mapper.WebhookEvent
            Event to be handled and written to database.
        """
        event_type = event.event_type
        event = event.event

        int_id = event.internal_meeting_id
        record_id = event.record_id
        self.logger.info(
            f"Processing {event_type} event for "
            + f"internal-meeting-id '{int_id}' and record_id "
            + f"'{record_id}'."
        )

        recorded = event.recorded

        # Meeting was set to be recorded.
        if recorded:
            recording = _fetch_or_create_recording(self.session, event, Status.PROCESSING)

            meetings_event = _fetch_meetings_event(self.session, int_id)

            if meetings_event:
                _update_recording_with_meetings_event_data(recording, meetings_event)
            else:
                self.logger.warning(f"No meeting found for recording '{event.record_id}'.")

            recording.current_step = event.current_step

            self.session.add(recording)
            self.logger.info(f"Records_row: {recording}")

class RapHandler(DatabaseEventHandler):
    """This class handles general recording events."""

    def handle(self, event):
        """Implementation of abstract handle method from DatabaseEventHandler.

        Parameters
        ----------
        event : event_mapper.WebhookEvent
            Event to be handled and written to database.
        """
        event_type = event.event_type
        server_url = event.server_url
        event = event.event

        int_id = event.internal_meeting_id
        record_id = event.record_id

        self.logger.info(
            f"Processing {event_type} event for "
            + f"internal-meeting-id '{int_id}' and record_id "
            + f"'{record_id}'."
        )

        recording = _fetch_or_create_recording(self.session, event, Status.DELETED)

        # Assume the requester server to be the new host of the recording.
        if event_type == "rap-sanity-started":
            recording = self._handle_rap_sanity_started(server_url, recording)
            # if int_id != record_id, then it's not the first recording in the
            # group. In this case, se status to PROCESSING to match the first
            # recording.
            if int_id != record_id:
                recording.status = Status.PROCESSING

        meetings_event = _fetch_meetings_event(self.session, int_id)

        if meetings_event:
            _update_recording_with_meetings_event_data(recording, meetings_event)
        else:
            self.logger.warning(f"No meeting found for recording '{event.record_id}'.")

        recording.current_step = event.current_step

        self.session.add(recording)
        self.logger.info(f"Records_row: {recording}")

    def _handle_rap_sanity_started(self, server_url, recording):
        server_id_result = self.session.query(Servers.id).filter(Servers.name == server_url).first()
        if server_id_result:
            if server_id_result.id != recording.server_id:
                self.logger.info(f"Recording host was updated to '{server_url}'.")
            recording.server_id = server_id_result.id
        else:
            self.logger.warning(f"No server found for recording '{recording.record_id}'.")

        return recording

class RapProcessHandler(DatabaseEventHandler):
    """This class handles processing recording events."""

    def handle(self, event):
        """Implementation of abstract handle method from DatabaseEventHandler.

        Parameters
        ----------
        event : event_mapper.WebhookEvent
            Event to be handled and written to database.
        """
        event_type = event.event_type
        event = event.event

        int_id = event.internal_meeting_id
        record_id = event.record_id
        self.logger.info(
            f"Processing {event_type} event for "
            + f"internal-meeting-id '{int_id}' and record_id "
            + f"'{record_id}'."
        )

        recording = _fetch_recording(self.session, record_id)

        # Table recordings already exists.
        if recording:
            # Update status based on event.
            current_status = recording.status
            workflow_status = recording.workflow.get(event.workflow, None)
            if event.current_step == "rap-process-started":
                recording = self.handle_rap_process_started(
                    recording, event, workflow_status, current_status
                )
            elif event.current_step == "rap-process-ended":
                recording = self.handle_rap_process_ended(
                    recording, event, workflow_status, current_status
                )
            else:
                self.logger.warning(
                    f"Invalid event '{event.current_step}' from current status '{current_status}' for recording '{event.record_id}'."
                )
        else:
            self.logger.warning(f"No recording found with id '{event.record_id}'.")

    def handle_rap_process_started(self, recording, event, workflow_status, current_status):
        if not workflow_status:
            recording.workflow = _update_workflow(recording, event.workflow, Status.PROCESSING)
        if current_status == Status.PROCESSING:
            recording.current_step = event.current_step

        self.session.add(recording)

        return recording

    def handle_rap_process_ended(self, recording, event, workflow_status, current_status):
        if workflow_status == Status.PROCESSING:
            recording.workflow = _update_workflow(recording, event.workflow, Status.PROCESSED)
        if current_status == Status.PROCESSING:
            recording.status = Status.PROCESSED
            recording.current_step = event.current_step

        self.session.add(recording)

        return recording

class RapPublishUnpublishHandler(DatabaseEventHandler):
    """This class handles publishing and unpublishing recording events."""

    def handle(self, event):
        """Implementation of abstract handle method from DatabaseEventHandler.

        Parameters
        ----------
        event : event_mapper.WebhookEvent
            Event to be handled and written to database.
        """
        event_type = event.event_type
        event = event.event

        int_id = event.internal_meeting_id
        record_id = event.record_id
        self.logger.info(
            f"Processing {event_type} event for "
            + f"internal-meeting-id '{int_id}' and record_id "
            + f"'{record_id}'."
        )

        recording = _fetch_recording(self.session, record_id)

        if recording:
            if event_type == "rap-unpublished":
                if recording.status == Status.PUBLISHED:
                    recording.status = Status.UNPUBLISHED
                    recording.published = False
                    self.session.add(recording)
                else:
                    self.logger.warning(
                        f"Tried to unpublish a recording with record-id '{record_id}' that is not yet published."
                    )
            elif event_type == "rap-published":
                if recording.status == Status.UNPUBLISHED:
                    recording.status = Status.PUBLISHED
                    recording.published = True
                    self.session.add(recording)
                else:
                    self.logger.warning(
                        f"Tried to publish a recording with meeting id '{record_id}' that is already published.",
                    )
        else:
            self.logger.warning(f"No recording found with record_id '{record_id}'.")

class RapDeleteHandler(DatabaseEventHandler):
    """This class handles deleting recording events."""

    def handle(self, event):
        """Implementation of abstract handle method from DatabaseEventHandler.

        Parameters
        ----------
        event : event_mapper.WebhookEvent
            Event to be handled and written to database.
        """

        event_type = event.event_type
        event = event.event

        int_id = event.internal_meeting_id
        record_id = event.record_id
        self.logger.info(
            f"Processing {event_type} event for "
            + f"internal-meeting-id '{int_id}' and record_id "
            + f"'{record_id}'."
        )

        recording = _fetch_recording(self.session, record_id)

        if recording:
            recording.status = Status.DELETED
            recording.published = False
            self.session.add(recording)
        else:
            self.logger.warning(f"No recording found with meeting id '{int_id}'.")

class RapPublishHandler(DatabaseEventHandler):
    """This class handles publishing recording events."""

    def handle(self, event):
        """Implementation of abstract handle method from DatabaseEventHandler.

        Parameters
        ----------
        event : event_mapper.WebhookEvent
            Event to be handled and written to database.
        """

        event_type = event.event_type
        event = event.event

        int_id = event.internal_meeting_id
        record_id = event.record_id
        self.logger.info(
            f"Processing {event_type} event for "
            + f"internal-meeting-id '{int_id}' and record_id "
            + f"'{record_id}'."
        )

        recording = _fetch_recording(self.session, record_id)

        # Table recordings already exists.
        if recording:
            if event.current_step == "rap-publish-started":
                recording = self._handle_publish_started(recording, event)
            elif event.current_step == "rap-publish-ended":
                recording = self._handle_publish_ended(recording, event)
            else:
                self.logger.warning(
                    f"Invalid event '{event.current_step}' from current status '{recording.status}' for meeting '{int_id}'."
                )
        else:
            self.logger.warning(f"No recording found with meeting id '{int_id}'.")

    def _handle_publish_started(self, recording, event):
        if recording.status == Status.PROCESSED:
            recording.current_step = event.current_step

        self.session.add(recording)

        return recording

    def _handle_publish_ended(self, recording, event):
        times = (
            self.session.query(MeetingsEvents.start_time, MeetingsEvents.end_time)
            .filter(MeetingsEvents.internal_meeting_id == event.internal_meeting_id)
            .first()
        )

        start_time, end_time = times

        recording.status = Status.PUBLISHED
        recording.published = True
        recording.name = str(event.name)
        recording.is_breakout = event.is_breakout
        recording.start_time = event.start_time or start_time
        recording.end_time = event.end_time or end_time
        recording.size = event.size or 0
        recording.raw_size = event.raw_size
        # If meta_data field exists, simply merge the new meta_data, without
        # overwriting currently saved meta_data.
        if recording.meta_data:
            event.meta_data.update(recording.meta_data)
        if (event.workflow == "presentation") or (event.workflow == "video"):
            event.meta_data.update({"mconf-decrypter-pending": "false"})
        recording.meta_data = event.meta_data
        recording.download = event.download
        recording.current_step = event.current_step
        updated_playback = _upsert_playback(recording, event.playback)
        recording.playback = updated_playback

        flag_modified(recording, "playback")

        recording.workflow = _update_workflow(recording, event.workflow, Status.PUBLISHED)
        recording.current_step = event.current_step

        self.session.add(recording)

        return recording

class MeetingTransferHandler(DatabaseEventHandler):
    """This class handles meeting transfer events."""

    def handle(self, event):
        """Implementation of abstract handle method from DatabaseEventHandler.

        Parameters
        ----------
        event : event_mapper.WebhookEvent
            Event to be handled and written to database.
        """

        event_type = event.event_type
        event = event.event
        transfer_status = False
        if event_type == "meeting-transfer-enabled":
            transfer_status = True

        int_id = event.internal_meeting_id
        self.logger.info(f"Processing {event_type} event for internal-meeting-id '{int_id}'.")

        transfer_table = (
            self.session.query(Meetings).filter(Meetings.int_meeting_id == int_id).first()
        )

        if transfer_table:
            transfer_table.transfer = transfer_status
            self.session.add(transfer_table)
        else:
            self.logger.warning(f"No meeting found with meeting id '{int_id}'.")

def _update_meeting(meetings_table):
    """Common updates on table meetings."""

    meetings_table.transfer_count = sum(
        1 for attendee in meetings_table.attendees if attendee["role"] == "TRANSFER"
    )
    meetings_table.participant_count = len(meetings_table.attendees) - meetings_table.transfer_count
    meetings_table.has_user_joined = meetings_table.participant_count != 0
    meetings_table.moderator_count = sum(
        1 for attendee in meetings_table.attendees if attendee["role"] == "MODERATOR"
    )
    meetings_table.transfer_count = sum(
        1 for attendee in meetings_table.attendees if attendee["role"] == "TRANSFER"
    )
    meetings_table.listener_count = sum(
        1 for attendee in meetings_table.attendees if attendee["is_listening_only"]
    )
    meetings_table.voice_participant_count = sum(
        1 for attendee in meetings_table.attendees if attendee["has_joined_voice"]
    )
    meetings_table.video_count = sum(1 for a in meetings_table.attendees if a["has_video"])

def _upsert_playback(records_table, event_playback):
    if len(event_playback) == 0:
        return records_table.playback

    playbacks = records_table.playback[:]

    for i, playback in enumerate(playbacks):
        if _has_same_playback_format(playback, event_playback):
            old_playback = playbacks[i]
            old_playback.update(event_playback)
            playbacks[i] = old_playback

            break
    else:  # In the case the for loop completes fully.
        playbacks.append(event_playback)

    return playbacks

def _update_workflow(records_table, event_workflow, new_status):
    workflows = records_table.workflow.copy()

    if event_workflow:
        workflows.update({event_workflow: new_status})

    return workflows

def _has_same_playback_format(playback1, playback2):
    if "format" in playback1 and "format" in playback2:
        if playback1["format"] == playback2["format"]:
            return True

    return False

def _fetch_recording(session, record_id):
    recording = session.query(Recordings).filter(Recordings.record_id == record_id).first()

    return recording

def _fetch_or_create_recording(session, event, first_status):
    int_id = event.internal_meeting_id
    record_id = event.record_id
    recording = session.query(Recordings).filter(Recordings.record_id == record_id).first()

    # Recording does not exist yet. Create it.
    if not recording:
        recording = Recordings(**event._asdict())

        # Start as deleted since we don't yet know if
        # this meeting was recorded or not
        recording.status = first_status
        recording.playback = []
        recording.workflow = {}
        recording.participants = int(
            session.query(UsersEvents.id)
            .join(MeetingsEvents)
            .filter(MeetingsEvents.internal_meeting_id == int_id)
            .count()
        )
    elif recording.status == Status.DELETED:
        recording.status = Status.PROCESSING

    return recording

def _fetch_meetings_event(session, internal_meeting_id):
    meetings_event = (
        session.query(MeetingsEvents)
        .filter(MeetingsEvents.internal_meeting_id == internal_meeting_id)
        .first()
    )
    return meetings_event

def _update_recording_with_meetings_event_data(recording, meetings_event):
    recording.end_time = meetings_event.end_time
    recording.external_meeting_id = meetings_event.external_meeting_id
    recording.internal_meeting_id = meetings_event.internal_meeting_id
    recording.is_breakout = meetings_event.is_breakout
    recording.meeting_event_id = meetings_event.id
    recording.parent_meeting_id = meetings_event.parent_meeting_id
    recording.r_institution_guid = meetings_event.institution_guid
    recording.r_shared_secret_guid = meetings_event.shared_secret_guid
    recording.start_time = meetings_event.start_time

class DataProcessor:
    """Data processor (dispatcher) of the received event."""

    def __init__(self, session, logger=None):
        """Constructor of the DataProcessor.

        Parameters
        ----------
        session : sqlalchemy.Session
            Session used by SQLAlchemy to interact with the database.
        """
        self.session = session
        self.logger = logger or get_logger()

    def update(self, event):
        event_handler = self._select_handler(event.event_type)

        event_handler.handle(event)

    def _select_handler(self, event_type):
        """Event dispatcher.

        Choose which handler will process the event based on the event type.

        Parameters
        ----------
        event : event_mapper.WebhookEvent
            An event to be handled and persisted into database.
        """
        self.logger.debug("Selecting event processor.")

        if event_type == "meeting-created":
            event_handler = MeetingCreatedHandler(self.session)

        elif event_type == "user-joined":
            event_handler = UserJoinedHandler(self.session)

        elif event_type == "user-left":
            event_handler = UserLeftHandler(self.session)

        elif event_type == "meeting-ended":
            event_handler = MeetingEndedHandler(self.session)

        elif event_type == "user-audio-voice-enabled":
            event_handler = UserVoiceEnabledHandler(self.session)

        elif event_type == "user-audio-voice-disabled":
            event_handler = UserVoiceDisabledHandler(self.session)

        elif event_type == "user-audio-listen-only-enabled":
            event_handler = UserListenOnlyEnabledHandler(self.session)

        elif event_type == "user-audio-listen-only-disabled":
            event_handler = UserListenOnlyDisabledHandler(self.session)

        elif event_type == "user-cam-broadcast-start":
            event_handler = UserCamBroadcastStartHandler(self.session)

        elif event_type == "user-cam-broadcast-end":
            event_handler = UserCamBroadcastEndHandler(self.session)

        elif event_type == "user-presenter-assigned":
            event_handler = UserPresenterAssignedHandler(self.session)

        elif event_type == "user-presenter-unassigned":
            event_handler = UserPresenterUnassignedHandler(self.session)

        elif event_type in [
            "rap-archive-started",
            "rap-sanity-started",
            "rap-sanity-ended",
            "rap-post-archive-started",
            "rap-post-archive-ended",
            "rap-post-process-started",
            "rap-post-process-ended",
            "rap-post-publish-started",
            "rap-post-publish-ended",
        ]:
            event_handler = RapHandler(self.session)

        elif event_type in ["rap-archive-ended"]:
            event_handler = RapArchiveHandler(self.session)

        elif event_type in ["rap-process-started", "rap-process-ended"]:
            event_handler = RapProcessHandler(self.session)

        elif event_type in ["rap-publish-started", "rap-publish-ended"]:
            event_handler = RapPublishHandler(self.session)

        elif event_type in ["rap-unpublished", "rap-published"]:
            event_handler = RapPublishUnpublishHandler(self.session)

        elif event_type == "rap-deleted":
            event_handler = RapDeleteHandler(self.session)

        elif event_type in ["meeting-transfer-enabled", "meeting-transfer-disabled"]:
            event_handler = MeetingTransferHandler(self.session)

        else:
            self.logger.warning(f"Unknown event type '{event_type}'.")
            raise InvalidWebhookEventError(f"unknown event type '{event_type}'")

        return event_handler

class WebhookDataWriter(AggregatorCallback):
    """Writer of data retrieved from webhooks.

    This class implements the AggregatorCallback which means its `run()` method
    is intended to run in a separate thread, writing incoming data to the
    database.

    Before using it, one should call its `setup()` method to configure
    any resource used to write data such as database connections etc.
    After that, its `run()` method can be run in a separate thread continuously.
    When finished, its `teardown` can be called to close any opened resource.
    """

    def __init__(self, connector=None, logger=None):
        """Constructor of the WebhookDataWriter.

        Parameters
        ----------
        connector : Database connector (driver).
            If not supplied, it will instantiate a new `PostgresConnector`.
        """
        self.logger = logger or get_logger()

    def setup(self):
        """Setup any resources needed to iteract with the database."""
        self.logger.info("Setting up WebhookDataWriter")

    def teardown(self):
        """Release any resources used to iteract with the database."""
        self.logger.info("Tearing down WebhookDataWriter")

    def run(self, data):
        """Run main logic of the writer.

        This method is intended to run in a separate thread by the aggregator
        whenever new data must be persisted.

        data : event_mapper.WebhookEvent
            This is a single event to be handled and persisted into database.

        Raises
        ------
        aggregator.aggregator.CallbackError
            If any error occur while persisting event into database.
        """
        try:
            with time_logger(
                self.logger.info, "Processing information to database took {elapsed}s."
            ):
                with session_scope() as session:
                    DataProcessor(session).update(data)
        except sqlalchemy.exc.OperationalError as err:
            self.logger.error(f"Operational error on database. Not persisting data: {err}")

            raise CallbackError() from err
        except WebhookDatabaseError as err:
            self.logger.error(
                f"An error occurred while persisting data. \
                              Not persisting data: {err}"
            )

            raise CallbackError() from err
        except Exception as err:
            self.logger.error(f"Unknown error on database handler. Not persisting data: {err}")

            raise CallbackError() from err

class AuthenticationHandler:
    """Provide a way to get server data from database."""

    def __init__(self, logger=None):
        """Constructor of the `AuthenticationHandler`.

        Parameters
        ----------
        logger : loguru.Logger
            If not supplied, it will instantiate a new logger.
        """
        self.logger = logger or get_logger()

    def secret(self, server):
        """Get a shared secret for a given server in the database.

        Parameters
        ----------
        server : str
            Server for which it should get a token from database.

        Returns
        -------
        token : str
            Token of the server as retrieved from database.
        """
        found_secret = None
        with session_scope() as session:
            try:
                server = session.query(Servers.secret).filter(Servers.name == server).first()
            except sqlalchemy.exc.OperationalError as err:
                self.logger.error(f"Operational error on database while validating token: {err}")
                server = None
            except Exception as err:
                self.logger.warning(f"Unknown error while validating token: {err}")
                server = None

            if server:
                # If it found a row for the given server, extract its secret column.
                found_secret = server.secret

        return found_secret

class WebhookServerHandler:
    """Provide a way to get all available servers from database."""

    def __init__(self, logger=None):
        """Constructor of the `WebhookServerHandler`.

        Parameters
        ----------
        logger : loguru.Logger
            If not supplied, it will instantiate a new logger.
        """
        self.logger = logger or get_logger()

    def servers(self):
        """Get all available servers from database.

        Returns
        -------
        servers : Servers.
            The list of all available servers from database.
        """
        servers = None
        with session_scope() as session:
            try:
                servers = session.query(Servers).all()
            except sqlalchemy.exc.OperationalError as err:
                self.logger.error(f"Operational error on database while gathering servers: {err}")

                raise DatabaseNotReadyError()
            except Exception as err:
                self.logger.warning(f"Unknown error while gathering servers: {err}")

                raise DatabaseNotReadyError()
            else:
                # Since it returns Servers objects used by the database, we need
                # to detach the objects returned from the database's session.
                session.expunge_all()

                return servers


from utils.download_utils import maybe_download
import pandas as pd
COL_USER = "UserId"
COL_ITEM = "MovieId"
COL_RATING = "Rating"
COL_PREDICTION = "Rating"
COL_TIMESTAMP = "Timestamp"

def main(opt):
    filepath = maybe_download(opt.data_url, opt.data_path)
    data = pd.read_csv(filepath, sep="\t", names=[COL_USER, COL_ITEM, COL_RATING, COL_TIMESTAMP])
    print(data.head())
    print(data.describe())

    print(
    "Total number of ratings are\t{}".format(data.shape[0]),
    "Total number of users are\t{}".format(data[COL_USER].nunique()),
    "Total number of items are\t{}".format(data[COL_ITEM].nunique()),
    sep="\n"
)


# --------------------------------------------------------------
# Logical_unit
# --------------------------------------------------------------

#------------------------
# Identify light sources
#------------------------

#----------
# kerne.py
#----------

from PIL import Image

def build_kernel():

    print("build_kernel")

    # """The kernel that is built is an image of 13 pixels on 13 pixeles.
    # The image  is a blurred white circle in the center surrounded by a black square that creates a strong contrast,
    #  that will be suited for detecting a traffic light.
    #  It will be looking for a circle shaped object with a strong contrast frame
    #  :return Kernel: 2D array with sum 0
    #  """

    circle_img = Image.open('/content/drive/MyDrive/Colab_Notebooks/light.png').convert('L')
    kernel = np.asarray(circle_img)
    kernel = kernel.astype(np.float32)
    kernel -= 100
    sum_circle = np.sum(kernel)
    area = circle_img.width * circle_img.height
    kernel -= (sum_circle / area)
    max_kernel = np.max(kernel)
    kernel /= max_kernel

    return kernel

class Kernel:

    __instance=None
    @staticmethod

    def getInstance():
        if not Kernel.__instance:
            Kernel()
        return Kernel.__instance

    def __init__(self):
        if Kernel.__instance:
            raise Exception("Kernel is singleton class,"
                            " Instead of initial new Instance you"
                            " can use the getInstance() method.")
        Kernel.__instance = self
        self.__kernel=build_kernel()

    def get_kernel(self):
        return self.__kernel

#--------------
# attention.py
#--------------
try:
    import test
    import os
    import json
    import glob
    import argparse
    import cv2

    import numpy as np
    from scipy import signal as sg
    from scipy.ndimage.filters import maximum_filter
    from scipy import misc
    from PIL import Image
    import matplotlib.pyplot as plt
    from scipy.spatial import distance
    from scipy.spatial.distance import pdist, squareform
    # from Logical_unit.Identify_light_sources.kernel import Kernel
except ImportError:
    print("Need to fix the installation")
    raise

def find_tfl_lights(c_image: np.ndarray, kernel):
    """    this function receives the image that we will be searching on and the current used kernel
    will return the coordinates in the image of all traffic lights
    :return - tuple of X and Y values
    """

    # get's the red layer of the picture and the green layer of the picture
    red_matrix, green_matrix = np.array(c_image)[:, :, 0], np.array(c_image)[:, :, 1]

    new_red = sg.convolve(red_matrix, kernel, mode='same')
    new_green = sg.convolve(green_matrix, kernel, mode='same')

    # filters to get the max match in each area of the green and red after doing the convolvation
    red_max = maximum_filter(new_red, size=250)
    green_max = maximum_filter(new_green, size=250)

    red_max_point = red_max == new_red
    green_max_point = green_max == new_green

    y_red, x_red = np.where(red_max_point)
    y_green, x_green = np.where(green_max_point)

    # In assumption that there are no traffic light lower than 40 or higher than 1000 we will remove
    #  the red coordinates that are out of this range
    for index in range(len(x_red)):
        if y_red[index] < 40 or y_red[index] > 1000:
            # marking the unnecessary indexes as -1 in order to delete them
            x_red[index] = -1
            y_red[index] = -1
    y_red = np.delete(y_red, np.where(y_red == -1))
    x_red = np.delete(x_red, np.where(x_red == -1))
    # In assumption that there are no traffic light lower than 40 or higher than 1000 we will remove
    #  the green coordinates that are out of this range
    for index in range(len(x_green)):
        if y_green[index] < 40 or y_green[index] > 1000:
            # marking the unnecessary indexes as -1 in order to delete them
            x_green[index] = -1
            y_green[index] = -1

    y_green = np.delete(y_green, np.where(y_green == -1))
    x_green = np.delete(x_green, np.where(x_green == -1))

    return x_red, y_red, x_green, y_green

def find_tfl_lights_in_image(image):
    print("find_tfl_lights_in_image")

    my_kernel=Kernel.getInstance()
    kernel = my_kernel.get_kernel()

    # opening the image as cv
    small_img = cv2.pyrDown(image.copy())

    # finding the coordinates of the green and red traffic lights of the original image
    red_x_small, red_y_small, green_x_small, green_y_small = find_tfl_lights(image , kernel)

    # finding the coordinates of the green and red traffic lights of the reduced image
    red_x_big, red_y_big, green_x_big, green_y_big = find_tfl_lights(small_img, kernel)

    # converting the returned coordinates to numpy type
    #  resizing the fixels of the detected cordinations of the reduced image
    # that the detected cordinates will be right on the original picture
    rx_small = np.array(red_x_small)
    ry_small = np.array(red_y_small)
    rx_big = np.array(red_x_big * 2)
    ry_big = np.array(red_y_big * 2)
    gx_small = np.array(green_x_small)
    gy_small = np.array(green_y_small)
    gx_big = np.array(green_x_big * 2)
    gy_big = np.array(green_y_big * 2)

    # concatenating the same type coordinates of the reduced image and the original one.
    red_x = np.concatenate([rx_small, rx_big])
    red_y = np.concatenate([ry_small, ry_big])
    green_x = np.concatenate([gx_small, gx_big])
    green_y = np.concatenate([gy_small, gy_big])

    return red_x,red_y,green_x,green_y


from q_a import app
from flask import render_template, session, request, url_for
from q_a.models import User, Email, Group
from q_a.supplement import Amend, Check

@app.route('/profiles', methods=['GET', 'POST'])
@app.route('/profiles/<int:page>', methods=['GET', 'POST'])
def profiles(page=1):
    Check.update()
    if not session.get('user'):
        return Check.login()
    if session.get('status') != 2:
        return Check.status()
    if request.method == 'GET':
        page_of_profiles = User.query.order_by(User.id.desc()).paginate(page, 20)
        profiles = page_of_profiles.items
        for n in range(len(profiles)):
            profiles[n].username = Amend.username(profiles[n].username)
        return render_template('profiles.html',
                               profiles=profiles,
                               items=page_of_profiles,
                               Email=Email)
    elif request.method == 'POST':
        if request.form.get('query'):
            if request.form.get('parameter') == 'ID':
                try:
                    profiles = User.query.filter_by(id=int(request.form.get('query'))).order_by(User.id.desc()).all()
                    page_of_profiles = None
                    for n in range(len(profiles)):
                        profiles[n].username = Amend.username(profiles[n].username)
                except:
                    return Amend.flash('Введите число.', 'danger', url_for('profiles'))
            elif request.form.get('parameter') == 'group':
                try:
                    users = [user.id for user in Group.query.all() if user.group == int(request.form.get('query'))]
                    profiles = User.query.filter(User.id.in_(users)).order_by(User.id.desc()).all()
                    page_of_profiles = None
                    for n in range(len(profiles)):
                        profiles[n].username = Amend.username(profiles[n].username)
                except:
                    return Amend.flash('Введите число.', 'danger', url_for('profiles'))
            elif request.form.get('parameter') == 'username':
                profiles = User.query.filter_by(username=request.form.get('query')).order_by(User.id.desc()).all()
                page_of_profiles = None
                for n in range(len(profiles)):
                    profiles[n].username = Amend.username(profiles[n].username)
            elif request.form.get('parameter') == 'firstname':
                profiles = User.query.filter_by(firstname=request.form.get('query')).order_by(User.id.desc()).all()
                page_of_profiles = None
                for n in range(len(profiles)):
                    profiles[n].username = Amend.username(profiles[n].username)
            elif request.form.get('parameter') == 'surname':
                profiles = User.query.filter_by(surname=request.form.get('query')).order_by(User.id.desc()).all()
                page_of_profiles = None
                for n in range(len(profiles)):
                    profiles[n].username = Amend.username(profiles[n].username)
            elif request.form.get('parameter') == 'email':
                profiles = User.query.filter_by(email=request.form.get('query')).order_by(User.id.desc()).all()
                page_of_profiles = None
                for n in range(len(profiles)):
                    profiles[n].username = Amend.username(profiles[n].username)
            return render_template('profiles.html',
                                   profiles=profiles,
                                   items=page_of_profiles,
                                   Email=Email)
        return Amend.flash('Введите поисковый запрос.', 'danger', url_for('profiles'))


import math
import logging
from functools import partial
from collections import OrderedDict
from copy import deepcopy

import torch
import torch.nn as nn
import torch.nn.functional as F

from timm.data import IMAGENET_DEFAULT_MEAN, IMAGENET_DEFAULT_STD, IMAGENET_INCEPTION_MEAN, IMAGENET_INCEPTION_STD
from timm.models.helpers import build_model_with_cfg, named_apply, adapt_input_conv
from timm.models.layers import PatchEmbed, Mlp, DropPath, trunc_normal_, lecun_normal_
from timm.models.registry import register_model
from torch.nn.init import _calculate_fan_in_and_fan_out
import math
import warnings
from timm.models.layers.helpers import to_2tuple

_logger = logging.getLogger(__name__)

def _cfg(url='', **kwargs):
    return {
        'url': url,
        'num_classes': 1000, 'input_size': (3, 224, 224), 'pool_size': None,
        'crop_pct': .9, 'interpolation': 'bicubic', 'fixed_input_size': True,
        'mean': IMAGENET_DEFAULT_MEAN, 'std': IMAGENET_DEFAULT_STD,
        'first_conv': 'patch_embed.proj', 'classifier': 'head',
        **kwargs
    }

# default_cfgs = {
#     'xciformer_224': _cfg(),
#     'xciformer_384': _cfg(input_size=(3, 384, 384), crop_pct=1.0),
# }

default_cfgs = {
    'xciformer_small': _cfg(url='https://huggingface.co/sail/dl2/resolve/main/xciformer/xciformer_small.pth'),
    'xciformer_base': _cfg(url='https://huggingface.co/sail/dl2/resolve/main/xciformer/xciformer_base.pth'),
    'xciformer_large': _cfg(url='https://huggingface.co/sail/dl2/resolve/main/xciformer/xciformer_large.pth'),
    'xciformer_small_384': _cfg(url='https://huggingface.co/sail/dl2/resolve/main/xciformer/xciformer_small_384.pth',
                            input_size=(3, 384, 384), crop_pct=1.0),
    'xciformer_base_384': _cfg(url='https://huggingface.co/sail/dl2/resolve/main/xciformer/xciformer_base_384.pth',
                            input_size=(3, 384, 384), crop_pct=1.0),
    'xciformer_large_384': _cfg(url='https://huggingface.co/sail/dl2/resolve/main/xciformer/xciformer_large_384.pth',
                            input_size=(3, 384, 384), crop_pct=1.0),
}

def _no_grad_trunc_normal_(tensor, mean, std, a, b):
    # Cut & paste from PyTorch official master until it's in a few official releases - RW
    # Method based on https://people.sc.fsu.edu/~jburkardt/presentations/truncated_normal.pdf
    def norm_cdf(x):
        # Computes standard normal cumulative distribution function
        return (1. + math.erf(x / math.sqrt(2.))) / 2.

    if (mean < a - 2 * std) or (mean > b + 2 * std):
        warnings.warn("mean is more than 2 std from [a, b] in nn.init.trunc_normal_. "
            "The distribution of values may be incorrect.",
            stacklevel=2)

    with torch.no_grad():
        # Values are generated by using a truncated uniform distribution and
        # then using the inverse CDF for the normal distribution.
        # Get upper and lower cdf values
        l = norm_cdf((a - mean) / std)
        u = norm_cdf((b - mean) / std)

        # Uniformly fill tensor with values from [l, u], then translate to
        # [2l-1, 2u-1].
        tensor.uniform_(2 * l - 1, 2 * u - 1)

        # Use inverse cdf transform for normal distribution to get truncated
        # standard normal
        tensor.erfinv_()

        # Transform to proper mean, std
        tensor.mul_(std * math.sqrt(2.))
        tensor.add_(mean)

        # Clamp to ensure it's in the proper range
        tensor.clamp_(min=a, max=b)
        return tensor

def trunc_normal_(tensor, mean=0., std=1., a=-2., b=2.):
    # type: (torch.Tensor, float, float, float, float) -> torch.Tensor
    r"""Fills the input Tensor with values drawn from a truncated
    normal distribution. The values are effectively drawn from the
    normal distribution :math:`\mathcal{N}(\text{mean}, \text{std}^2)`
    with values outside :math:`[a, b]` redrawn until they are within
    the bounds. The method used for generating the random values works
    best when :math:`a \leq \text{mean} \leq b`.
    Args:
        tensor: an n-dimensional `torch.Tensor`
        mean: the mean of the normal distribution
        std: the standard deviation of the normal distribution
        a: the minimum cutoff value
        b: the maximum cutoff value
    Examples:
        >>> w = torch.empty(3, 5)
        >>> nn.init.trunc_normal_(w)
    """
    return _no_grad_trunc_normal_(tensor, mean, std, a, b)

def variance_scaling_(tensor, scale=1.0, mode='fan_in', distribution='normal'):
    fan_in, fan_out = _calculate_fan_in_and_fan_out(tensor)
    if mode == 'fan_in':
        denom = fan_in
    elif mode == 'fan_out':
        denom = fan_out
    elif mode == 'fan_avg':
        denom = (fan_in + fan_out) / 2

    variance = scale / denom

    if distribution == "truncated_normal":
        # constant is stddev of standard normal truncated to (-2, 2)
        trunc_normal_(tensor, std=math.sqrt(variance) / .87962566103423978)
    elif distribution == "normal":
        tensor.normal_(std=math.sqrt(variance))
    elif distribution == "uniform":
        bound = math.sqrt(3 * variance)
        tensor.uniform_(-bound, bound)
    else:
        raise ValueError(f"invalid distribution {distribution}")

def lecun_normal_(tensor):
    variance_scaling_(tensor, mode='fan_in', distribution='truncated_normal')

from .patches import *
from .mixers import *
from .blocks import *

class CrossCovarianceInceptionTransformer(nn.Module):
    def __init__(self, img_size=224, patch_size=16, in_chans=3, num_classes=1000, embed_dims=None, depths=None,
                num_heads=None, mlp_ratio=4., qkv_bias=True,
                drop_rate=0., attn_drop_rate=0., drop_path_rate=0., norm_layer=None,
                act_layer=None, weight_init='',
                attention_heads=None,
                use_layer_scale=False, layer_scale_init_value=1e-5,
                checkpoint_path=None,
                **kwargs
                ):

        super().__init__()
        st2_idx = sum(depths[:1])
        st3_idx = sum(depths[:2])
        st4_idx = sum(depths[:3])
        depth = sum(depths)

        self.num_classes = num_classes

        norm_layer = norm_layer or partial(nn.LayerNorm, eps=1e-6)
        act_layer = act_layer or nn.GELU

        dpr = [x.item() for x in torch.linspace(0, drop_path_rate, depth)]  # stochastic depth decay rule

        self.patch_embed = XCiTFirstPatchEmbed(in_chans=in_chans, embed_dim=embed_dims[0])
        self.num_patches1 = num_patches = img_size // 4
        self.pos_embed1 = nn.Parameter(torch.zeros(1, num_patches, num_patches, embed_dims[0]))
        self.blocks1 = nn.ModuleList([
            XCiFormerBlock(
                dim=embed_dims[0], num_heads=num_heads[0], mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, drop=drop_rate,
                attn_drop=attn_drop_rate, drop_path=dpr[i], norm_layer=norm_layer, act_layer=act_layer, attention_head=attention_heads[i], pool_size=2,)
                # use_layer_scale=use_layer_scale, layer_scale_init_value=layer_scale_init_value,
                # )
            for i in range(0, st2_idx)])

        self.patch_embed2 = XCiTPatchEmbed(kernel_size=3, stride=2, padding=1, in_chans=embed_dims[0], embed_dim=embed_dims[1])
        self.num_patches2 = num_patches = num_patches // 2
        self.pos_embed2 = nn.Parameter(torch.zeros(1, num_patches, num_patches, embed_dims[1]))
        self.blocks2 = nn.ModuleList([
            XCiFormerBlock(
                dim=embed_dims[1], num_heads=num_heads[1], mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, drop=drop_rate,
                attn_drop=attn_drop_rate, drop_path=dpr[i], norm_layer=norm_layer, act_layer=act_layer, attention_head=attention_heads[i], pool_size=2,)
                # use_layer_scale=use_layer_scale, layer_scale_init_value=layer_scale_init_value, channel_layer_scale=channel_layer_scale,
                # )
            for i in range(st2_idx,st3_idx)])

        self.patch_embed3 = XCiTPatchEmbed(kernel_size=3, stride=2, padding=1, in_chans=embed_dims[1], embed_dim=embed_dims[2])
        self.num_patches3 = num_patches = num_patches // 2
        self.pos_embed3 = nn.Parameter(torch.zeros(1, num_patches, num_patches, embed_dims[2]))
        self.blocks3= nn.ModuleList([
            XCiFormerBlock(
                dim=embed_dims[2], num_heads=num_heads[2], mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, drop=drop_rate,
                attn_drop=attn_drop_rate, drop_path=dpr[i], norm_layer=norm_layer, act_layer=act_layer, attention_head=attention_heads[i], pool_size=1,
                use_layer_scale=use_layer_scale, layer_scale_init_value=layer_scale_init_value,
                )
            for i in range(st3_idx, st4_idx)])

        self.patch_embed4 = XCiTPatchEmbed(kernel_size=3, stride=2, padding=1, in_chans=embed_dims[2], embed_dim=embed_dims[3])
        self.num_patches4 = num_patches = num_patches // 2
        self.pos_embed4 = nn.Parameter(torch.zeros(1, num_patches, num_patches, embed_dims[3]))
        self.blocks4 = nn.ModuleList([
            XCiFormerBlock(
                dim=embed_dims[3], num_heads=num_heads[3], mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, drop=drop_rate,
                attn_drop=attn_drop_rate, drop_path=dpr[i], norm_layer=norm_layer, act_layer=act_layer, attention_head=attention_heads[i], pool_size=1,
                use_layer_scale=use_layer_scale, layer_scale_init_value=layer_scale_init_value,
                )
            for i in range(st4_idx,depth)])

        self.norm = norm_layer(embed_dims[-1])
        # Classifier head(s)
        self.head = nn.Linear(embed_dims[-1], num_classes) if num_classes > 0 else nn.Identity()
        # set post block, for example, class attention layers

        self.init_weights(weight_init)

    def init_weights(self, mode=''):
        trunc_normal_(self.pos_embed1, std=.02)
        trunc_normal_(self.pos_embed2, std=.02)
        trunc_normal_(self.pos_embed3, std=.02)
        trunc_normal_(self.pos_embed4, std=.02)

        self.apply(_init_vit_weights)

    def _init_weights(self, m):
        # this fn left here for compat with downstream users
        _init_vit_weights(m)

    @torch.jit.ignore
    def no_weight_decay(self):
        return {'pos_embed', 'cls_token', 'dist_token'}

    def get_classifier(self):
        if self.dist_token is None:
            return self.head
        else:
            return self.head, self.head_dist

    def reset_classifier(self, num_classes, global_pool=''):
        self.num_classes = num_classes
        self.head = nn.Linear(self.embed_dim, num_classes) if num_classes > 0 else nn.Identity()
        if self.num_tokens == 2:
            self.head_dist = nn.Linear(self.embed_dim, self.num_classes) if num_classes > 0 else nn.Identity()

    def _get_pos_embed(self, pos_embed, num_patches_def, H, W):
        if H * W == num_patches_def * num_patches_def:
            return pos_embed
        else:
            return F.interpolate(
                pos_embed.permute(0, 3, 1, 2),
                size=(H, W), mode="bilinear").permute(0, 2, 3, 1)

    def forward_features(self, x):
        x = self.patch_embed(x)
        B, H, W, C = x.shape
        x = x + self._get_pos_embed(self.pos_embed1, self.num_patches1, H, W)
        # x = self.blocks1(x)
        for blk1 in self.blocks1:
            x = blk1(x)

        x = x.permute(0, 3, 1, 2)
        x = self.patch_embed2(x)
        B, H, W, C = x.shape
        x = x + self._get_pos_embed(self.pos_embed2, self.num_patches2, H, W)
        # x = self.blocks2(x)
        for blk2 in self.blocks2:
            x = blk2(x)

        x = x.permute(0, 3, 1, 2)
        x = self.patch_embed3(x)
        B, H, W, C = x.shape
        x = x + self._get_pos_embed(self.pos_embed3, self.num_patches3, H, W)
        # x = self.blocks3(x)
        for blk3 in self.blocks3:
            x = blk3(x)

        x = x.permute(0, 3, 1, 2)
        x = self.patch_embed4(x)
        B, H, W, C = x.shape
        x = x + self._get_pos_embed(self.pos_embed4, self.num_patches4, H, W)
        # x = self.blocks4(x)
        for blk4 in self.blocks4:
            x = blk4(x)
        x = x.flatten(1,2)

        x = self.norm(x)
        return x.mean(1)

    def forward(self, x):
        x = self.forward_features(x)
        x = self.head(x)
        return x

def _init_vit_weights(module: nn.Module, name: str = '', head_bias: float = 0.):
    """ ViT weight initialization
    * When called without n, head_bias, jax_impl args it will behave exactly the same
      as my original init for compatibility with prev hparam / downstream use cases (ie DeiT).
    """
    if isinstance(module, nn.Linear):
        if name.startswith('head'):
            nn.init.zeros_(module.weight)
            nn.init.constant_(module.bias, head_bias)
        elif name.startswith('pre_logits'):
            lecun_normal_(module.weight)
            nn.init.zeros_(module.bias)
        else:
            trunc_normal_(module.weight, std=.02)
            if module.bias is not None:
                nn.init.zeros_(module.bias)
    elif isinstance(module, (nn.LayerNorm, nn.GroupNorm, nn.BatchNorm2d)):
        nn.init.zeros_(module.bias)
        nn.init.ones_(module.weight)
    elif isinstance(module, nn.Conv2d):
        trunc_normal_(module.weight, std=.02)
        if module.bias is not None:
            nn.init.constant_(module.bias, 0)

@register_model
def xciformer_small(pretrained=False, **kwargs):
    """
    19.866M  4.849G 83.382
    """
    depths = [3, 3, 9, 3]
    embed_dims = [96, 192, 320, 384]
    num_heads = [3, 6, 10, 12]
    attention_heads = [1]*3 + [3]*3 + [7] * 4 + [9] * 5 + [11] * 3

    model = CrossCovarianceInceptionTransformer(img_size=224,
        depths=depths,
        embed_dims=embed_dims,
        num_heads=num_heads,
        attention_heads=attention_heads,
        use_layer_scale=True, layer_scale_init_value=1e-6,
        **kwargs)
    model.default_cfg = default_cfgs['xciformer_small']
    if pretrained:
        url = model.default_cfg['url']
        checkpoint = torch.hub.load_state_dict_from_url(url=url, map_location="cpu", check_hash=True)
        model.load_state_dict(checkpoint)
    return model

@register_model
def xciformer_small_384(pretrained=False, **kwargs):
    depths = [3, 3, 9, 3]
    embed_dims = [96, 192, 320, 384]
    num_heads = [3, 6, 10, 12]
    attention_heads = [1]*3 + [3]*3 + [7] * 4 + [9] * 5 + [11] * 3

    model = CrossCovarianceInceptionTransformer(img_size=384,
        depths=depths,
        embed_dims=embed_dims,
        num_heads=num_heads,
        attention_heads=attention_heads,
        use_layer_scale=True, layer_scale_init_value=1e-6,
        **kwargs)
    model.default_cfg = default_cfgs['xciformer_small_384']
    if pretrained:
        url = model.default_cfg['url']
        checkpoint = torch.hub.load_state_dict_from_url(url=url, map_location="cpu", check_hash=True)
        model.load_state_dict(checkpoint)
    return model

@register_model
def xciformer_base(pretrained=False, **kwargs):
    """
    47.866M  9.379G  84.598
    """
    depths = [4, 6, 14, 6]
    embed_dims = [96, 192, 384, 512]
    num_heads = [3, 6, 12, 16]
    attention_heads = [1]*4 + [3]*6 + [8] * 7 + [10] * 7 + [15] * 6

    model = CrossCovarianceInceptionTransformer(img_size=224,
        depths=depths,
        embed_dims=embed_dims,
        num_heads=num_heads,
        attention_heads=attention_heads,
        use_layer_scale=True, layer_scale_init_value=1e-6,
        **kwargs)
    model.default_cfg = default_cfgs['xciformer_base']
    if pretrained:
        url = model.default_cfg['url']
        checkpoint = torch.hub.load_state_dict_from_url(url=url, map_location="cpu", check_hash=True)
        model.load_state_dict(checkpoint)
    return model

@register_model
def xciformer_base_384(pretrained=False, **kwargs):
    depths = [4, 6, 14, 6]
    embed_dims = [96, 192, 384, 512]
    num_heads = [3, 6, 12, 16]
    attention_heads = [1]*4 + [3]*6 + [8] * 7 + [10] * 7 + [15] * 6

    model = CrossCovarianceInceptionTransformer(img_size=384,
        depths=depths,
        embed_dims=embed_dims,
        num_heads=num_heads,
        attention_heads=attention_heads,
        use_layer_scale=True, layer_scale_init_value=1e-6,
        **kwargs)
    model.default_cfg = default_cfgs['xciformer_base_384']
    if pretrained:
        url = model.default_cfg['url']
        checkpoint = torch.hub.load_state_dict_from_url(url=url, map_location="cpu", check_hash=True)
        model.load_state_dict(checkpoint)
    return model

@register_model
def xciformer_large(pretrained=False, **kwargs):
    """
    86.637M  14.048G 84.752
    """
    depths = [4, 6, 18, 8]
    embed_dims = [96, 192, 448, 640]
    num_heads = [3, 6, 14, 20]
    attention_heads = [1]*4 + [3]*6 + [10] * 9 + [12] * 9 + [19] * 8

    model = CrossCovarianceInceptionTransformer(img_size=224,
        depths=depths,
        embed_dims=embed_dims,
        num_heads=num_heads,
        attention_heads=attention_heads,
        use_layer_scale=True, layer_scale_init_value=1e-6,
        **kwargs)
    model.default_cfg = default_cfgs['xciformer_large']
    if pretrained:
        url = model.default_cfg['url']
        checkpoint = torch.hub.load_state_dict_from_url(url=url, map_location="cpu", check_hash=True)
        model.load_state_dict(checkpoint)
    return model

@register_model
def xciformer_large_384(pretrained=False, **kwargs):
    depths = [4, 6, 18, 8]
    embed_dims = [96, 192, 448, 640]
    num_heads = [3, 6, 14, 20]
    attention_heads = [1]*4 + [3]*6 + [10] * 9 + [12] * 9 + [19] * 8

    model = CrossCovarianceInceptionTransformer(img_size=384,
        depths=depths,
        embed_dims=embed_dims,
        num_heads=num_heads,
        attention_heads=attention_heads,
        use_layer_scale=True, layer_scale_init_value=1e-6,
        **kwargs)
    model.default_cfg = default_cfgs['xciformer_large_384']
    if pretrained:
        url = model.default_cfg['url']
        checkpoint = torch.hub.load_state_dict_from_url(url=url, map_location="cpu", check_hash=True)
        model.load_state_dict(checkpoint)
    return model


from django.urls import path
from . import views

app_name = 'tution'
urlpatterns = [
    path('contact_with_form/', views.contact_with_form, name='contact_with_form'),
    # path('contact_with_modelform/', views.contact_with_modelform, name='contact_with_modelform'),
    path('contact/', views.ContactView.as_view(), name='ContactView'),
    path('postlist/', views.PostListView.as_view(), name='PostListView'),
    # path('postdetail/<str:slug>/', views.PostsDetailView.as_view(), name='PostsDetailView'),
    path('postdetail/<int:pk>/', views.PostsDetailView.as_view(), name='PostsDetailView'),
    path('postdelete/<int:pk>/', views.PostDeleteView.as_view(), name='PostDeleteView'),
    path('postupdate/<int:pk>/', views.PostUpdateView.as_view(), name='PostUpdateView'),
    # path('create/', views.post_create, name='post_create'),
    path('create/', views.PostCreateView.as_view(), name='PostCreateView'),
    path('posts/', views.postview, name='postview'),
    path('search/', views.search, name='search'),
    path('postfilter/', views.post_filter, name='post_filter'),

]


# Third Party
import pytest

# Database
from sqlalchemy.orm import Query

# Project
from graphene_sqlalchemy_filter import FilterSet
from tests import models
from tests.graphql_objects import UserFilter

def test_hybrid_property_automatic_filter_binding():
    msg = 'Unsupported field type for automatic filter binding'
    with pytest.raises(ValueError, match=msg):

        class TestFilter(FilterSet):
            class Meta:
                model = models.User
                fields = {'username_hybrid_property': [...]}

def test_sql_query(info):
    filters = {'username_hybrid_property': ['admin']}
    user_query = Query(models.User)
    query = UserFilter.filter(info, user_query, filters)

    where_clause = str(query.whereclause)
    ok = 'lower("user".username) = :lower_1'
    assert where_clause == ok


import time
import logging
import os
from typing import Sequence

from sunhead.conf import settings

from isk.backends.factory import backend
from isk.urldownloader import url_to_file

logger = logging.getLogger(__name__)

on_rtd = os.environ.get('READTHEDOCS', None) == 'True'  # Whether this module is imported by readthedocs.org builder
if not on_rtd:  # If so, disable loading any extension modules RTC can't hadle
    # TODO: Check rtd stuff here
    pass

# Globals
daemon_start_time = time.time()
has_shutdown = False

SUPPORTED_IMAGE_EXTENSIONS = backend.supported_image_extensions

def query_img_id(db_id: int, image_id: int, numres: int = 12, sketch: bool = False, fast: bool = False) -> tuple:
    """
    Return the most similar images to the supplied one.
    The supplied image must be already indexed, and is referenced by its ID.

    :param db_id: Database space id.
    :param image_id: Target image id.
    :param numres: Number of results to return. The target image is on the result list.
    :param sketch: False for photographs, True for hand-sketched images or low-resolution vector images.
    :param fast: if true, only the average color for each image is considered.
        Image geometry/features are ignored. Search is faster this way.
    :since: 0.7
    :change: 0.9.3: added parameter 'sketch'
    :return:  array of arrays: M{[[image id 1, score],[image id 2, score],[image id 3, score], ...]}
        (id is Integer, score is Double)
    """

    # TODO: Removed inefficicent balancer from here. Implement better one

    results = backend.query_img_id(db_id, image_id, numres, sketch, fast)
    result_tuple = tuple(results)
    return result_tuple

def query_img_blob(dbId, data, numres=12, sketch=0, fast=False):
    """
    Return the most similar images to the supplied one.
    The target image is specified by its raw binary file data.
    Most common formats are supported.

    :type  dbId: number
    :param dbId: Database space id.
    :type  data: binary data
    :param data: Target image file binary data.
    :type  numres: number
    :param numres: Number of results to return. The target image is on the result list.
    :type  sketch: number
    :param sketch: 0 for photographs, 1 for hand-sketched images or low-resolution vector images.
    :type fast: boolean
    :param fast: if true, only the average color for each image is considered. Image geometry/features are ignored.
        Search is faster this way.
    :rtype:   array

    :since: 0.9.3
    :return:  array of arrays: M{[[image id 1, score],[image id 2, score],[image id 3, score], ...]}
        (id is Integer, score is Double)
    """
    dbId = int(dbId)
    numres = int(numres)

    return backend.query_img_blob(dbId, data.data, numres, sketch, fast)

def query_img_path(dbId, path, numres=12, sketch=0, fast=False):
    """
    Return the most similar images to the supplied one.
    The target image is specified using it's full path on the server filesystem.

    :type  dbId: number
    :param dbId: Database space id.
    :type  path: string
    :param path: Target image pth on the server disk.
    :type  numres: number
    :param numres: Number of results to return. The target image is on the result list.
    :type  sketch: number
    :param sketch: 0 for photographs, 1 for hand-sketched images or low-resolution vector images.
    :type fast: boolean
    :param fast: if true, only the average color for each image is considered. Image geometry/features are ignored.
        Search is faster this way.
    :rtype:   array

    :since: 0.9.3
    :return:  array of arrays: M{[[image id 1, score],[image id 2, score],[image id 3, score], ...]}
        (id is Integer, score is Double)
    """
    dbId = int(dbId)
    numres = int(numres)

    return backend.query_img_path(dbId, path, numres, sketch, fast)

def add_img_blob(dbId, id, data):
    """
    Add image to database space. Image data is passed directly. It is then processed and indexed.

    :type  dbId: number
    :param dbId: Database space id.
    :type  id: number
    :param id: Target image id. The image located on filename will be indexed and from now on should be refered
        to isk-daemon as this supplied id.
    :type  data: binary
    :param data: Image binary data
    :rtype:   number

    :since: 0.9.3
    :return:  1 in case of success.
    """
    dbId = int(dbId)
    id = int(id)

    try:
        # TODO id should be unsigned long int or something even bigger, also must review swig declarations
        res = backend.add_image_blob(dbId, data.data, id)
    except Exception as e:
        if str(e) == 'image already in db':
            logger.warn(e)
        else:
            logger.error(e)
        return res

    return res

def add_img(db_id: int, image_id: int, filename: str, file_is_url: bool = False) -> bool:
    """
    Add image to database space. Image file is read, processed and indexed.
    After this indexing is done, image can be removed from file system.

    :param db_id: Database space id.
    :param image_id: Target image id. The image located on filename will be indexed and from now on should be
        refered to isk-daemon as this supplied id.
    :param filename: Physical full file path for the image to be indexed.
        Should be in one of the supported formats
        ('jpeg', 'jpg', 'gif', 'png', 'rgb', 'pbm', 'pgm', 'ppm', 'tiff', 'tif', 'rast', 'xbm', 'bmp').
        For better results image should have dimension of at least 128x128. Thumbnails are ok.
        Bigger images will be scaled down to 128x128.
    :param file_is_url: if true, filename is interpreted as an HTTP url and the remote image
        it points to downloaded and saved to a temporary location (same directory where database file is)
        before being added to database.

    :since: 0.7
    :return:  True in case of success.
    """
    if file_is_url: # download it first
        # TODO: May be this need to be deprecated
        tempFName = os.path.expanduser(settings.core.get('database','databasePath')) + ('_tmp_%d_%d.jpg' % (db_id, image_id))
        url_to_file(filename, tempFName)
        filename = tempFName

    res = False

    try:
        # TODO id should be unsigned long int or something even bigger, also must review swig declarations
        res = backend.add_image(db_id, filename, image_id)
    except Exception as e:
        if str(e) == 'image already in db':
            logger.warn(e)
        else:
            logger.error(e)
        return res

    if (file_is_url):
        os.remove(filename)

    return res

def remove_img(db_id: int, id: int) -> bool:
    """
    Remove image from database space.

    :param db_id: Database space id.
    :param id: Target image id.

    :since: 0.7
    :return:  1 in case of success.
    """
    return backend.remove_img(db_id, id)

def remove_img_bulk(dbId, ids):
    """
    Neat shortcut to remove whole bunch of images from database.

    :type  dbId: number
    :param dbId: Database space id.
    :type  id: list
    :param idList: List of image ids.

    :since: 0.10
    :return: True if all images was removed.
    """
    result = True
    for image_id in ids:
        result &= remove_img(dbId, image_id)
    return result

def is_img_on_db(dbId, id):
    """
    Return whether image id exists on database space.

    :type  dbId: number
    :param dbId: Database space id.
    :type  id: number
    :param id: Target image id.
    :rtype:   boolean

    :since: 0.7
    :return:  true if image id exists
    """
    dbId = int(dbId)
    id = int(id)
    return backend.is_image_on_db(dbId, id)

def get_img_dimensions(dbId, id):
    """
    Returns image original dimensions when indexed into database.

    :type  dbId: number
    :param dbId: Database space id.
    :type  id: number
    :param id: Target image id.
    :rtype:   array

    :since: 0.7
    :return:  array in the form M{[width, height]}
    """
    dbId = int(dbId)
    id = int(id)
    return backend.get_image_dimensions(dbId, id)

def calc_img_avgl_diff(dbId, id1, id2):
    """
    Return average luminance (over three color channels) difference ratio

    :type  dbId: number
    :param dbId: Database space id.
    :type  id1: number
    :param id1: Target image 1 id.
    :type  id2: number
    :param id2: Target image 2 id.
    :rtype:   number

    :since: 0.7
    :return:  float representing difference. The smaller, the most similar.
    """
    dbId = int(dbId)
    id1 = int(id1)
    id2 = int(id2)
    return backend.calc_avgl_diff(dbId, id1, id2)

def calc_img_diff(dbId, id1, id2):
    """
    Return image similarity difference ratio. One value alone for an image pair doesn't mean much.
    These values should be compared pairwise against each other.

    The smaller the value between two images is (i.e. the more negative the value is),
    the more similar the 2 images are.

    Comparing one image against itself is a degenerate case and the value returned should be ignored.

    :type  dbId: number
    :param dbId: Database space id.
    :type  id1: number
    :param id1: Target image 1 id.
    :type  id2: number
    :param id2: Target image 2 id.
    :rtype:   number

    :since: 0.7
    :return:  float representing difference. The smaller, the most similar.
    """
    dbId = int(dbId)
    id1 = int(id1)
    id2 = int(id2)

    return backend.calc_diff(dbId, id1, id2)

def get_img_avgl(dbId, id):
    """
    Return image average color levels on the three color channels (YIQ color system)

    :type  dbId: number
    :param dbId: Database space id.
    :type  id: number
    :param id: Target image id.
    :rtype:   array of double

    :since: 0.7
    :return:  values for YIQ color channels
    """
    dbId = int(dbId)
    id1 = int(id)
    return backend.get_image_avgl(dbId, id1)

def get_db_img_id_list(db_id: int) -> tuple:
    """
    Return list of image ids on database space.

    :since: 0.7
    :param db_id: Database space id.
    :return:  array of image ids
    """

    result = backend.get_img_id_list(db_id)
    return result

def add_keyword_img(db_id: int, image_id: int, keyword_id: int) -> bool:
    """
    Adds a keyword to an image.

    :param db_id: Database space id.
    :param image_id: Target image id.
    :param keyword_id: Keyword id.
    :rtype:   boolean

    :since: 0.7
    :return:  True if operation was succesful
    """
    return backend.add_keyword_img(db_id, image_id, keyword_id)

def add_keyword_img_bulk(dbId, data):
    """
    Adds keywords to images in a bulk. You pass data as dict when keywords as keys and list of image id as values::

        {
            keyword1_id: [img1_id, img2_id],
            keyword2_id: [img1_id, img3_id],
            ...
        }

    This will save some network overhead.

    :param dbId: id of the image database to use.
    :param data: Keywords and list of image id in described format.
    :since: 0.10
    :return: True if all operations was successfull
    """

    result = True

    for keyword, id_list in data.items():

        # Convert keyword to int
        try:
            keyword_id = int(keyword)
        except ValueError:
            result &= False
            continue

        # Assign that keyword to images
        for img in id_list:
            try:
                img_id = int(img)
            except ValueError:
                result &= False
                continue

            result &= add_keyword_img(dbId, img_id, keyword_id)

    return bool(result)

def get_ids_bloom_filter(dbId):
    """
    Return bloom filter containing all images on given db id.

    :type  dbId: number
    :param dbId: Database space id.
    :rtype:   bloom filter

    :since: 0.7
    :return:  bloom filter containing all images on given db id.
    """
    dbId = int(dbId)
    return backend.getIdsBloomFilter(dbId)

def get_cluster_keywords(dbId, numClusters, keywords):
    """
    Return whether image id exists on database space.

    :type  dbId: number
    :param dbId: Database space id.
    :rtype:   boolean

    :since: 0.7
    :return:  true if image id exists
    """
    dbId = int(dbId)
    return backend.get_cluster_keywords(dbId, numClusters, keywords)

def get_cluster_db(dbId, numClusters):
    """
    Return whether image id exists on database space.

    :type  dbId: number
    :param dbId: Database space id.
    :rtype:   boolean

    :since: 0.7
    :return:  true if image id exists
    """
    dbId = int(dbId)
    return backend.get_cluster_db(dbId, numClusters)

def get_keywords_popular(dbId, numres):
    """
    Return whether image id exists on database space.

    :type  dbId: number
    :param dbId: Database space id.
    :rtype:   boolean

    :since: 0.7
    :return:  true if image id exists
    """
    dbId = int(dbId)
    return backend.get_keywords_popular(dbId, numres)

def get_keywords_visual_distance(dbId, distanceType, keywords):
    """
    Return whether image id exists on database space.

    :type  dbId: number
    :param dbId: Database space id.
    :rtype:   boolean

    :since: 0.7
    :return:  true if image id exists
    """
    dbId = int(dbId)
    return backend.get_keywords_visual_distance(dbId, distanceType, keywords)

# TODO: Add defaults here and explicit kw_join_type
def get_all_imgs_by_keywords(db_id, numres: int, kw_join_type: int, keyword_id_list: Sequence[int]) -> list:
    """
    Return all images with the given keywords

    :param db_id: Database space id.
    :param numres: Number of results desired
    :param kw_join_type: Logical operator for target keywords: 1 for AND, 0 for OR
    :param keyword_id_list: List of keyword ids
    :rtype:   array

    :since: 0.7
    :return:  array of image ids
    """
    if not keyword_id_list:
        keyword_id_list = (0,)

    return backend.get_all_imgs_by_keywords(db_id, numres, kw_join_type, keyword_id_list)

def query_img_id_fast_keywords(dbId, imgId, numres, kwJoinType, keywords):
    """
    Fast query (only considers average color) for similar images considering keywords

    :type  dbId: number
    :param dbId: Database space id.
    :type  imgId: number
    :param imgId: Target image id. If '0', random images containing the target keywords will be returned.
    :type  numres: number
    :param numres: Number of results desired
    :type  kwJoinType: number
    :param kwJoinType: logical operator for keywords: 1 for AND, 0 for OR
    :type  keywords: string
    :param keywords: comma separated list of keyword ids.
    :rtype:   array

    :since: 0.7
    :return:  array of arrays: M{[[image id 1, score],[image id 2, score],[image id 3, score], ...]}
        (id is Integer, score is Double)
    """
    dbId = int(dbId)
    imgId = int(imgId)
    keywordIds = [int(x) for x in keywords.split(',') if len(x) > 0]
    return backend.query_img_id_fast_keywords(dbId, imgId, numres, kwJoinType, keywords)

def query_img_id_keywords(dbId, imgId, numres, kwJoinType, keywords):
    """
    Query for similar images considering keywords. The input keywords are used for narrowing the
    search space.

    :type  dbId: number
    :param dbId: Database space id.
    :type  imgId: number
    :param imgId: Target image id. If '0', random images containing the target keywords will be returned.
    :type  numres: number
    :param numres: Number of results desired
    :type  kwJoinType: number
    :param kwJoinType: logical operator for keywords: 1 for AND, 0 for OR
    :type  keywords: string
    :param keywords: comma separated list of keyword ids.
    :rtype:   array

    :since: 0.7
    :return:  array of arrays: M{[[image id 1, score],[image id 2, score],[image id 3, score], ...]}
        (id is Integer, score is Double)
    """
    dbId = int(dbId)
    imgId = int(imgId)
    keywordIds = [int(x) for x in keywords.split(',') if len(x) > 0]
    return backend.query_img_id_keywords(dbId, imgId, numres, kwJoinType, keywordIds)

def query_img_id_keywords_bulk(dbId, imgKwList, numres, kwJoinType):
    """
    Shortcut for querying for similar images considering keywords in bulk. You pass list of tuples::

        [
            (img1_id, 'kw1_id,kw2_id'),
            (img2_id, 'kw3_id'),
            ...
        ]

    In return you get list of results::

        [
            (img1_id, [(img2_id, img2_score), (img5_id, img5_score), ...]),
            (img2_id, [(img16_id, img16_score), ...]),
        ]

    This will save you some network overhead over calling queryImgIDKeywords one-by-one.

    :type  dbId: number
    :param dbId: Database space id
    :type imgKwList: tuple
    :param imgKwList: List of queries in described format. Keywords is a comma-separated list of keyword ids.
    :type  numres: number
    :param numres: Number of results desired
    :type  kwJoinType: number
    :param kwJoinType: logical operator for keywords: 1 for AND, 0 for OR
    :since: 0.10
    :return: List of image ids and corresponding results in format, described above.
    """

    total_results = []
    for image_id, keywords in imgKwList:
        query_result = query_img_id_keywords(dbId, image_id, numres, kwJoinType, keywords)
        total_results.append((image_id, query_result))

    return total_results

def most_popular_keywords(dbId, imgs, excludedKwds, count, mode):
    """
    Returns the most frequent keywords associated with a given set of images

    :type  dbId: number
    :param dbId: Database space id.
    :type  imgs: string
    :param imgs: Comma separated list of target image ids
    :type  excludedKwds: string
    :param excludedKwds: Comma separated list of keywords ids to be excluded from the frequency count
    :type  count: number
    :param count: Number of keyword results desired
    :type  mode: number
    :param mode: ignored, will be used on future versions.
    :rtype:   array

    :since: 0.7
    :return:  array of keyword ids and frequencies: [kwd1_id, kwd1_freq, kwd2_id, kwd2_freq, ...]
    """
    dbId = int(dbId)
    excludedKwds = [int(x) for x in excludedKwds.split(',') if len(x) > 0]
    imgs = [int(x) for x in imgs.split(',') if len(x) > 0]

    return backend.most_popular_keywords(dbId, imgs, excludedKwds, count, mode)

def get_keywords_img(dbId, imgId):
    """
    Returns all keywords currently associated with an image.

    :type  dbId: number
    :param dbId: Database space id.
    :type  imgId: number
    :param imgId: Target image id.
    :rtype:   array

    :since: 0.7
    :return:  array of keyword ids
    """
    dbId = int(dbId)
    imgId = int(imgId)
    return backend.get_keywords_img(dbId, imgId)

def remove_all_keyword_img(db_id: int, image_id: int) -> bool:
    """
    Remove all keyword associations this image has.

    Known issue: keyword based queries will continue to consider the image to be associated
    to this keyword until the database is saved and restored.

    :param db_id: Database space id.
    :param image_id: Target image id.
    :rtype:   boolean

    :since: 0.7
    :return:  true if operation succeeded
    """
    return backend.remove_all_keywords_img(db_id, image_id)

def remove_all_keyword_img_bulk(dbId, imgIdList):
    """
    Remove all keyword associations for all images in list.

    This is just convenience shortcut for removeAllKeywordImg being called in loop.
    Saves network overhead, though.

    :type  dbId: number
    :param dbId: Database space id.
    :type  imgIdList: list
    :param imgIdList: List of target image id.
    :rtype:   boolean

    :since: 0.10
    :return:  True if all calls succeeded
    """
    result = True
    for img_id in imgIdList:
        result &= remove_all_keyword_img(dbId, img_id)

    return result

def remove_keyword_img(db_id: int, image_id: int, keyword_id: int) -> bool:
    """
    Remove the association of a keyword to an image

    Known issue: keyword based queries will continue to consider the image to be associated to this
    keyword until the database is saved and restored.

    :param db_id: Database space id.
    :param image_id: Target image id.
    :param keyword_id: Keyword id.

    :since: 0.7
    :return:  True if operation succeeded
    """
    return backend.remove_keyword_img(db_id, image_id, keyword_id)

def add_keywords_img(dbId, imgId, hashes):
    """
    Associate keywords to image

    :type  dbId: number
    :param dbId: Database space id.
    :type  imgId: number
    :param imgId: Target image id.
    :type  hashes: list of number
    :param hashes: Keyword hashes to associate
    :rtype:   boolean

    :since: 0.7
    :return:  true if image id exists
    """
    dbId = int(dbId)
    imgId = int(imgId)
    return backend.add_keywords_img(dbId, imgId, hashes)

def add_dir(db_id: int, path: str, recursive: bool, fname_as_id: bool = False) -> bool:
    """
    Visits a directory recursively and add supported images into database space.

    :param db_id: Database space id.
    :param path: Target filesystem full path of the initial dir.
    :param recursive: True if should visit recursively
    :param fname_as_id: Whether to use file names as id. If false, id will be assigned automatically.

    :since: 0.7
    :return:  count of images succesfully added
    """

    added_count = backend.add_dir(db_id, path, recursive, fname_as_id)
    return added_count

exporting = (
    query_img_id,
    add_img,
    remove_img,
    remove_img_bulk,
    is_img_on_db,
    get_img_dimensions,
    calc_img_avgl_diff,
    calc_img_diff,
    get_img_avgl,
    get_db_img_id_list,
    add_dir,
    add_keyword_img,
    add_keywords_img,
    add_keyword_img_bulk,
    remove_keyword_img,
    remove_all_keyword_img,
    remove_all_keyword_img_bulk,
    get_keywords_img,
    query_img_id_keywords,
    query_img_id_keywords_bulk,
    query_img_id_fast_keywords,
    get_all_imgs_by_keywords,
    get_keywords_visual_distance,
    get_keywords_popular,
    get_ids_bloom_filter,
    most_popular_keywords,
    query_img_blob,
    query_img_path,
    add_img_blob,

    get_cluster_db,
    get_cluster_keywords,
)


import discord

from discord.ext import commands
from utils.loader import configData
from checks.cargos import has_roles

class unban(commands.Cog):

    def __init__(self, bot: commands.Bot):
        self.bot = bot

    @commands.command()
    @has_roles(
        configData["roles"]["staff"]["asmodeus"],
        configData["roles"]["staff"]["astaroth"]
    )
    async def unban(self, ctx: commands.Context, usuario: discord.User, motivo: str = "Motivo não informado"):
        await ctx.guild.unban(user=usuario, reason=motivo)

        await ctx.reply(f"{usuario.name} desbanido com sucesso")

def setup(bot: commands.Bot):
    bot.add_cog(unban(bot))


import base64
import os

import mmcv
import torch
from ts.torch_handler.base_handler import BaseHandler

from mmcls.apis import inference_model, init_model

class MMclsHandler(BaseHandler):

    def initialize(self, context):
        properties = context.system_properties
        self.map_location = 'cuda' if torch.cuda.is_available() else 'cpu'
        self.device = torch.device(self.map_location + ':' +
                                   str(properties.get('gpu_id')) if torch.cuda.
                                   is_available() else self.map_location)
        self.manifest = context.manifest

        model_dir = properties.get('model_dir')
        serialized_file = self.manifest['model']['serializedFile']
        checkpoint = os.path.join(model_dir, serialized_file)
        self.config_file = os.path.join(model_dir, 'config.py')

        self.model = init_model(self.config_file, checkpoint, self.device)
        self.initialized = True

    def preprocess(self, data):
        images = []

        for row in data:
            image = row.get('data') or row.get('body')
            if isinstance(image, str):
                image = base64.b64decode(image)
            image = mmcv.imfrombytes(image)
            images.append(image)

        return images

    def inference(self, data, *args, **kwargs):
        results = []
        for image in data:
            results.append(inference_model(self.model, image))
        return results

    def postprocess(self, data):
        for result in data:
            result['pred_label'] = int(result['pred_label'])
        return data