from dataclasses import dataclass
from typing import Optional, Tuple

import torch.nn as nn
import torch
from transformers.models.dpr.modeling_dpr import DPRReaderOutput
from transformers.modeling_outputs import QuestionAnsweringModelOutput, ModelOutput, SequenceClassifierOutput
from transformers.models.vilt.modeling_vilt import ViltForImagesAndTextClassificationOutput
from transformers import VisualBertForQuestionAnswering, VisualBertForVisualReasoning, LxmertForQuestionAnswering
from transformers import ViltProcessor, ViltForImagesAndTextClassification
from transformers import BertForQuestionAnswering

from meerqat.train.losses import _calc_mml

class Trainee(nn.Module):
    """Base class for all Trainee models (to be trained by a Trainer)
    Should implement a forward function that returns loss between output and target (as a tuple, dict or ModelOutput)
    The actual forward pass should be done using the model attribute
    """
    def __init__(self, model):
        super().__init__()
        self.model = model

@dataclass
class DPRReaderForQuestionAnsweringOutput(DPRReaderOutput):
    """Same as DPRReaderOutput with an extra loss attribute (or as QuestionAnsweringModelOutput with relevance_logits)

    N. B. unfortunately we have to redefine everything so that loss is the first attribute
    """
    loss: Optional[torch.FloatTensor] = None
    start_logits: torch.FloatTensor = None
    end_logits: torch.FloatTensor = None
    relevance_logits: torch.FloatTensor = None
    hidden_states: Optional[Tuple[torch.FloatTensor]] = None
    attentions: Optional[Tuple[torch.FloatTensor]] = None

@dataclass
class MultiPassageBERTOutput(QuestionAnsweringModelOutput):
    """
    Same as QuestionAnsweringModelOutput but with start and end log-probabilities

    (equivalent to softmax(start_logits) when there is only one passage per question)
    """
    start_log_probs: torch.FloatTensor = None
    end_log_probs: torch.FloatTensor = None

@dataclass
class BERTRankerOutput(QuestionAnsweringModelOutput):
    """
    Same as MultiPassageBERTOutput but with relevance_logits important for ranking
    """
    loss: Optional[torch.FloatTensor] = None
    relevance_logits: torch.FloatTensor = None

@dataclass
class DPRBiEncoderOutput(ModelOutput):
    """
    Outputs from the question and context encoders
    (same as DPRQuestionEncoderOutput, DPRContextEncoderOutput with prefixes)
    """
    question_pooler_output: Optional[torch.FloatTensor] = None
    question_hidden_states: Optional[Tuple[torch.FloatTensor]] = None
    question_attentions: Optional[Tuple[torch.FloatTensor]] = None
    context_pooler_output: Optional[torch.FloatTensor] = None
    context_hidden_states: Optional[Tuple[torch.FloatTensor]] = None
    context_attentions: Optional[Tuple[torch.FloatTensor]] = None

class DPRBiEncoder(nn.Module):
    """Adapted from https://github.com/facebookresearch/DPR/blob/main/dpr/models/biencoder.py"""
    def __init__(self, question_model, context_model):
        """
        Parameters
        ----------

        question_model: transformers.DPRQuestionEncoder
            Encoder based on BERT used to encode the question/query
        context_model: transformers.DPRContextEncoder
            Encoder based on BERT used to encode the context/evidence/passage
            ('context' is confusing IMO but I keep it for consistency with DPR and transformers)
        """
        super().__init__()
        self.question_model = question_model
        self.context_model = context_model

    def forward(self, question_inputs, context_inputs, return_dict=None):
        """
        Embeds questions and contexts with their respective model and returns the embeddings.

        N - number of questions in a batch
        M - number of passages per questions
        L - sequence length
        d - dimension of the model/embeddings

        Parameters
        ----------
        question_inputs: dict[torch.LongTensor]
            input_ids: torch.LongTensor
                shape (N, L)
            usual BERT inputs, see transformers.DPRQuestionEncoder
        context_inputs: dict[torch.LongTensor]
            input_ids: torch.LongTensor
                shape (N*M, L)
            usual BERT inputs, see transformers.DPRContextEncoder
        return_dict: bool, optional
        """
        return_dict = return_dict if return_dict is not None else self.question_model.config.use_return_dict

        # embed questions and contexts
        question_outputs = self.question_model(**question_inputs)
        context_outputs = self.context_model(**context_inputs)

        return DPRBiEncoderOutput(
            question_pooler_output=question_outputs.pooler_output,
            question_hidden_states=question_outputs.hidden_states,
            question_attentions=question_outputs.attentions,
            context_pooler_output=context_outputs.pooler_output,
            context_hidden_states=context_outputs.hidden_states,
            context_attentions=context_outputs.attentions)

class DPRReaderForQuestionAnswering(Trainee):
    def forward(self,
                input_ids, attention_mask,
                start_positions=None, end_positions=None, answer_mask=None,
                return_dict=None, **kwargs):
        """Based on transformers.BertForQuestionAnswering and dpr.models.Reader"""
        return_dict = return_dict if return_dict is not None else self.model.config.use_return_dict
        # notations: N - number of questions in a batch, M - number of passages per questions, L - sequence length
        N, M, L = input_ids.size()
        outputs = self.model(input_ids, attention_mask, return_dict=True, **kwargs)

        # compute loss
        total_loss = None
        if start_positions is not None and end_positions is not None:
            start_positions = start_positions.view(N * M, -1)
            end_positions = end_positions.view(N * M, -1)
            answer_mask = answer_mask.view(N * M, -1)
            start_logits, end_logits, relevance_logits = outputs[:3]
            start_logits = start_logits.view(N * M, -1)
            end_logits = end_logits.view(N * M, -1)
            relevance_logits = relevance_logits.view(N * M)

            answer_mask = answer_mask.to(device=relevance_logits.device, dtype=torch.float32)

            # sometimes the start/end positions are outside our model inputs, we ignore these terms
            ignored_index = start_logits.size(1)
            start_positions = start_positions.clamp(0, ignored_index)
            end_positions = end_positions.clamp(0, ignored_index)
            loss_fct = nn.CrossEntropyLoss(reduction='none', ignore_index=ignored_index)

            # compute switch loss
            relevance_logits = relevance_logits.view(N, M)
            switch_labels = torch.zeros(N, dtype=torch.long, device=relevance_logits.device)
            switch_loss = torch.sum(loss_fct(relevance_logits, switch_labels))

            # compute span loss
            start_losses = [(loss_fct(start_logits, _start_positions) * _span_mask)
                            for (_start_positions, _span_mask)
                            in zip(torch.unbind(start_positions, dim=1), torch.unbind(answer_mask, dim=1))]

            end_losses = [(loss_fct(end_logits, _end_positions) * _span_mask)
                          for (_end_positions, _span_mask)
                          in zip(torch.unbind(end_positions, dim=1), torch.unbind(answer_mask, dim=1))]
            loss_tensor = torch.cat([t.unsqueeze(1) for t in start_losses], dim=1) + \
                          torch.cat([t.unsqueeze(1) for t in end_losses], dim=1)

            loss_tensor = loss_tensor.view(N, M, -1).max(dim=1)[0]
            span_loss = _calc_mml(loss_tensor)
            total_loss = span_loss + switch_loss

        if not return_dict:
            outputs = outputs.to_tuple()
            return ((total_loss,) + outputs) if total_loss is not None else outputs

        return DPRReaderForQuestionAnsweringOutput(loss=total_loss, **outputs)

class MultiPassageBERT(BertForQuestionAnswering):
    """
    PyTorch/Transformers implementation of Multi-passage BERT by Wang et. al (based on the global normalization by Clark et. al)
    i.e. groups passages per question before computing the softmax (and the NLL loss)
    so that spans scores are comparable across passages

    Code based on transformers.BertForQuestionAnswering, dpr.models.Reader
    and https://github.com/allenai/document-qa/blob/master/docqa/nn/span_prediction.py

    N. B. differences with DPRReaderForQuestionAnswering:
    * no projection layer between BERT and QA-extraction
    * no re-ranking (TODO implement MultiPassageDPRReader?)
    * global normalization

    References
    ----------
    @inproceedings{wang_multi-passage_2019,
        address = {Hong Kong, China},
        title = {Multi-passage {BERT}: {A} {Globally} {Normalized} {BERT} {Model} for {Open}-domain {Question} {Answering}},
        shorttitle = {Multi-passage {BERT}},
        url = {https://www.aclweb.org/anthology/D19-1599},
        doi = {10.18653/v1/D19-1599},
        urldate = {2021-06-14},
        booktitle = {Proceedings of the 2019 {Conference} on {Empirical} {Methods} in {Natural} {Language} {Processing} and the 9th {International} {Joint} {Conference} on {Natural} {Language} {Processing} ({EMNLP}-{IJCNLP})},
        publisher = {Association for Computational Linguistics},
        author = {Wang, Zhiguo and Ng, Patrick and Ma, Xiaofei and Nallapati, Ramesh and Xiang, Bing},
        month = nov,
        year = {2019},
        pages = {5878--5882}
    }

    @inproceedings{clark_simple_2018,
        address = {Melbourne, Australia},
        title = {Simple and {Effective} {Multi}-{Paragraph} {Reading} {Comprehension}},
        url = {https://aclanthology.org/P18-1078},
        doi = {10.18653/v1/P18-1078},
        urldate = {2021-07-08},
        booktitle = {Proceedings of the 56th {Annual} {Meeting} of the {Association} for {Computational} {Linguistics} ({Volume} 1: {Long} {Papers})},
        publisher = {Association for Computational Linguistics},
        author = {Clark, Christopher and Gardner, Matt},
        month = jul,
        year = {2018},
        pages = {845--855},
    }
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.log_softmax = nn.LogSoftmax(1)

    def forward(self,
                input_ids,
                start_positions=None, end_positions=None, answer_mask=None,
                return_dict=None, **kwargs):
        """
        notations:
            N - number of distinct questions
            M - number of passages per question in a batch
            L - sequence length

        Parameters
        ----------
        input_ids: Tensor[int]
            shape (N * M, L)
            There should always be a constant number of passages (relevant or not) per question
        start_positions, end_positions: Tensor[int], optional
            shape (N, M, max_n_answers)
            The answer might be found several time in the same passage, maximum `max_n_answers` times
            Defaults to None (i.e. don’t compute the loss)
        answer_mask: Tensor[int], optional
            shape (N, M, max_n_answers)
            Used to mask the loss for answers that are not `max_n_answers` times in the passage
            Required if start_positions and end_positions are specified
        **kwargs: additional arguments are passed to BERT after being reshape like
        """
        return_dict = return_dict if return_dict is not None else self.config.use_return_dict
        outputs = self.bert(input_ids, return_dict=True, **kwargs)
        sequence_output = outputs[0]
        logits = self.qa_outputs(sequence_output)
        start_logits, end_logits = logits.split(1, dim=-1)
        start_logits = start_logits.squeeze(-1).contiguous()
        end_logits = end_logits.squeeze(-1).contiguous()

        # compute loss
        total_loss, start_log_probs, end_log_probs = None, None, None
        if start_positions is not None and end_positions is not None:
            n_times_m, L = input_ids.size()
            M = start_positions.size(1)
            assert n_times_m % M == 0
            N = n_times_m//M
            # sometimes the start/end positions are outside our model inputs, we ignore these terms
            ignored_index = L
            start_positions = start_positions.clamp(0, ignored_index)
            end_positions = end_positions.clamp(0, ignored_index)
            loss_fct = nn.NLLLoss(reduction='none', ignore_index=ignored_index)

            # reshape from (N * M, L) to (N, M * L) so that all M passages related to the same question
            # will share the same softmax normalization
            start_logits, end_logits = start_logits.view(N, M*L), end_logits.view(N, M*L)
            start_log_probs, end_log_probs = self.log_softmax(start_logits), self.log_softmax(end_logits)
            # after computing the softmax, reshape back to (N * M, L)
            # because the last dimension, L, must match the position indices (i.e. class label) in start_positions, end_positions
            start_log_probs, end_log_probs = start_log_probs.view(N*M, L), end_log_probs.view(N*M, L)
            start_logits, end_logits = start_logits.view(N*M, L), end_logits.view(N*M, L)

            # reshape to match model output
            start_positions, end_positions = start_positions.view(N*M, -1), end_positions.view(N*M, -1)
            answer_mask = answer_mask.to(device=input_ids.device, dtype=torch.float32).view(N*M, -1)

            # compute span loss for each answer position in passage (in range `max_n_answers`)
            start_losses = [(loss_fct(start_log_probs, _start_positions) * _span_mask)
                            for (_start_positions, _span_mask)
                            in zip(torch.unbind(start_positions, dim=1), torch.unbind(answer_mask, dim=1))]

            end_losses = [(loss_fct(end_log_probs, _end_positions) * _span_mask)
                          for (_end_positions, _span_mask)
                          in zip(torch.unbind(end_positions, dim=1), torch.unbind(answer_mask, dim=1))]
            loss_tensor = torch.cat([t.unsqueeze(1) for t in start_losses], dim=1) + \
                          torch.cat([t.unsqueeze(1) for t in end_losses], dim=1)

            # keep the maximum per passage for each question
            loss_tensor = loss_tensor.view(N, M, -1).max(dim=1)[0]
            total_loss = _calc_mml(loss_tensor)

        if not return_dict:
            output = (start_logits, end_logits, start_log_probs, end_log_probs) + outputs[2:]
            return ((total_loss,) + output) if total_loss is not None else output

        return MultiPassageBERTOutput(
            loss=total_loss,
            start_logits=start_logits,
            end_logits=end_logits,
            start_log_probs=start_log_probs,
            end_log_probs=end_log_probs,
            hidden_states=outputs.hidden_states,
            attentions=outputs.attentions,
        )

class BERTRanker(BertForQuestionAnswering):
    """
    BERT-based Ranker Based on transformers.BertForQuestionAnswering
    and https://github.com/allenai/document-qa/blob/master/docqa/nn/span_prediction.py

    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.qa_classifier = nn.Linear(self.config.hidden_size, 1)

    def forward(self,
                input_ids,
                switch_labels=None,
                N=None, M=None,
                indices=None, relevants=None,
                return_dict=None, **kwargs):
        """
        notations:
            N - number of distinct questions
            M - number of passages per question in a batch
            L - sequence length

        Parameters
        ----------
        input_ids: Tensor[int]
            shape (N * M, L)
            There should always be a constant number of passages (relevant or not) per question
        **kwargs: additional arguments are passed to BERT after being reshape like
        """
        return_dict = return_dict if return_dict is not None else self.config.use_return_dict

        outputs = self.bert(input_ids, return_dict=True, **kwargs)
        sequence_output = outputs[0]
        relevance_logits = self.qa_classifier(sequence_output[:, 0, :])

        switch_loss = None
        if len(switch_labels) > 0:

            loss_fct = nn.CrossEntropyLoss(reduction='mean')

            # compute switch loss
            relevance_logits = relevance_logits.view(N, M)
            switch_loss = loss_fct(relevance_logits, switch_labels)

        if not return_dict:
            output = (relevance_logits) + outputs[2:]
            return ((switch_loss,) + output) if switch_loss is not None else output

        return BERTRankerOutput(
            loss=switch_loss,
            hidden_states=outputs.hidden_states,
            attentions=outputs.attentions,
            relevance_logits=relevance_logits,
        )

class ViLTRanker(ViltForImagesAndTextClassification):
    """
    ViLT-based Ranker Based on transformers.ViltForImagesAndTextClassification
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # Classifier head
        num_images = self.config.num_images
        self.qa_classifier = nn.Sequential(
            nn.Linear(self.config.hidden_size * num_images, self.config.hidden_size * num_images),
            nn.LayerNorm(self.config.hidden_size * num_images),
            nn.GELU(),
            nn.Linear(self.config.hidden_size * num_images, 1),
        )

    def forward(self,
                input_ids, pixel_values, pixel_mask,
                output_attentions=None,
                output_hidden_states=None,
                switch_labels=None,
                N=None, M=None,
                indices=None, relevants=None,
                return_dict=None, **kwargs):
        """
        notations:
            N - number of distinct questions
            M - number of passages per question in a batch
            L - sequence length

        Parameters
        ----------
        input_ids: Tensor[int]
            shape (N * M, L)
            There should always be a constant number of passages (relevant or not) per question
        **kwargs: additional arguments are passed to BERT after being reshape like
        """

        output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions
        output_hidden_states = (
            output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
        )
        return_dict = return_dict if return_dict is not None else self.config.use_return_dict

        if pixel_values is not None and pixel_values.ndim == 4:
            # add dummy num_images dimension
            pixel_values = pixel_values.unsqueeze(1)
        num_images = pixel_values.shape[1]
        if num_images != self.config.num_images:
            raise ValueError(
                "Make sure to match the number of images in the model with the number of images in the input."
            )

        pooler_outputs = []
        hidden_states = [] if output_hidden_states else None
        attentions = [] if output_attentions else None

        for i in range(num_images):
            # forward every image through the model
            outputs = self.vilt(
                input_ids,
                pixel_values=pixel_values[:, i, :, :, :],
                pixel_mask=pixel_mask[:, i, :, :] if pixel_mask is not None else None,
                image_token_type_idx=i + 1,
                output_attentions=output_attentions,
                output_hidden_states=output_hidden_states,
                return_dict=return_dict,
                **kwargs
            )
            pooler_output = outputs.pooler_output if return_dict else outputs[1]
            pooler_outputs.append(pooler_output)
            if output_hidden_states:
                hidden_states.append(outputs.hidden_states)
            if output_attentions:
                attentions.append(outputs.attentions)

        pooled_output = torch.cat(pooler_outputs, dim=-1)
        relevance_logits = self.qa_classifier(pooled_output)

        switch_loss = None
        if len(switch_labels) > 0:

            loss_fct = nn.CrossEntropyLoss(reduction='mean')

            # compute switch loss
            relevance_logits = relevance_logits.view(N, M)
            switch_loss = loss_fct(relevance_logits, switch_labels)

        if not return_dict:
            output = (relevance_logits, hidden_states, attentions)
            return ((switch_loss,) + output) if switch_loss is not None else output

        return ViltForImagesAndTextClassificationOutput(
            loss=switch_loss,
            logits=relevance_logits,
            hidden_states=hidden_states,
            attentions=attentions,
        )


import hashlib
import json
import urllib.parse
from typing import Union, Dict

import asks
from asks.response_objects import Response

from spins_halp_line.constants import Credentials
from spins_halp_line.util import get_logger, SynchedCache

_cred_key = "resource_space"
_field_ids = {
    "adventure_name": 86,
    "player": 87
}

_base_url = "base_url"
_user = "user"
_secret = "secret"

_l = get_logger()

# search:
# [
# 	{
# 		"score":"0",
# 		"ref":"1001",
# 		"resource_type":"4",
# 		"has_image":"0",
# 		"is_transcoding":"0",
# 		"creation_date":"2020-11-18 19:13:51",
# 		"rating":"",
# 		"user_rating":"",
# 		"user_rating_count":"",
# 		"user_rating_total":"",
# 		"file_extension":"mp3",
# 		"preview_extension":"jpg",
# 		"image_red":"",
# 		"image_green":"",
# 		"image_blue":"",
# 		"thumb_width":"",
# 		"thumb_height":"",
# 		"archive":"0",
# 		"access":"0",
# 		"colour_key":"",
# 		"created_by":"1",
# 		"file_modified":"2020-11-18 19:13:51",
# 		"file_checksum":"",
# 		"request_count":"0",
# 		"new_hit_count":"8",
# 		"expiry_notification_sent":"0",
# 		"preview_tweaks":"0|1",
# 		"file_path":"",
# 		"modified":"2020-11-19 03:58:07",
# 		"group_access":"",
# 		"user_access":"",
# 		"field12":"2020-11-18 19:13",
# 		"field8":"Shipwreck Front Yard",
# 		"field3":"",
# 		"order_by":"",
# 		"total_hit_count":"8"
# 	}
# ]
# get_resource_data
# {
# 	"ref":"1001", // both
# 	"title":"", // not the title field in the ui lol
# 	"resource_type":"4", // both
# 	"has_image":"0", // both
# 	"is_transcoding":"0", // both
# 	"hit_count":"8",
# 	"new_hit_count":"8", // both
# 	"creation_date":"2020-11-18 19:13:51", // both
# 	"rating":"", // both
# 	"user_rating":"", // both
# 	"user_rating_count":"", // both
# 	"user_rating_total":"", // both
# 	"country":"",
# 	"file_extension":"mp3", // both
# 	"preview_extension":"jpg", // both
# 	"image_red":"", // both
# 	"image_green":"", // both
# 	"image_blue":"", // both
# 	"thumb_width":"", // both
# 	"thumb_height":"", // both
# 	"archive":"0", // both
# 	"access":"0", // both
# 	"colour_key":"", // both
# 	"created_by":"1", // both
# 	"file_path":"", // both
# 	"file_modified":"2020-11-18 19:13:51", // both
# 	"file_checksum":"", // both
# 	"request_count":"0", // both
# 	"expiry_notification_sent":"0", // both
# 	"preview_tweaks":"0|1", // both
# 	"geo_lat":"",
# 	"geo_long":"",
# 	"mapzoom":"",
# 	"disk_usage":"623803",
# 	"disk_usage_last_updated":"2020-11-18 19:13:52",
# 	"file_size":"623803",
# 	"preview_attempts":"1",
# 	"field12":"2020-11-18 19:13", // both (?)
# 	"field8":"Shipwreck Front Yard", // both (title)
# 	"field3":"", // both (?)
# 	"modified":"2020-11-19 03:58:07",
# 	"last_verified":"",
# 	"integrity_fail":"0",
# 	"google_vision_processed":"",
# 	"lock_user":""
# }

# Community boards are useful: https://groups.google.com/g/resourcespace?pli=1

# static cache
_global_cache = SynchedCache()

class RSResource(object):

    @classmethod
    async def for_room(cls, room_name):
        # https://www.resourcespace.com/knowledge-base/user/special-search-terms
        # todo: The caching logic here could use improvement. We cache the data from a particular room
        # todo: so if we load the same room again we won't repeat those requests...but it seems wrong to
        # todo: cache searches? This caching model is based on a read-only assumption - that the server
        # todo: will be restarted if we make changes in the CMS. Maybe we should cache search results?
        # todo: In any case, since these calls should mostly be made once, it's possible that any caching
        # todo: is properly viewed as premature optimization.
        files = await cls._get(
            'do_search',
            {
                'search': f'room:{room_name}'
            }
        )
        files = [f for f in files if f['field8'] == room_name]
        return await cls._from_list(files)

    @classmethod
    async def _from_list(cls, resources):
        result = []
        for r in resources:
            obj = RSResource(r)
            await obj.load()
            result.append(obj)

        return result

    _k_id = 'ref'
    _k_ext = 'file_extension'
    _k_ui_title = 'field8'
    _k_d_url = 'data_url'
    _k_adventure = 'adventure_name'
    _k_player = 'player'
    _k_room = 'room'
    _k_date = 'date'
    _k_duration = 'duration'
    _k_path = 'path'

    _extended_fields = [
        _k_adventure,
        _k_player,
        _k_room,
        _k_date,
        _k_duration,
        _k_path
    ]

    _resource_types = {
        '1': 'photo',
        '2': 'document',
        '3': 'video',
        '4': 'audio'
    }

    def __init__(self, data: Union[Dict[str, str], str, int]):
        global _global_cache
        self._cache = _global_cache
        self._data = {}
        self._loaded = False
        self._id = None
        if isinstance(data, dict):
            self._data = data
            self._loaded_basic = True
            # self._id = data.get(self._k_id)
        elif isinstance(data, int) or isinstance(data, str):
            self._id = int(data)

    def _throw_if_not_loaded(self):
        if not self._loaded:
            raise ValueError(f'{self} has not had its fields loaded!')

    async def load(self):
        cache_key = self.id

        # support caching results
        # todo: this cache doesn't work
        data = await self._cache.get(cache_key)
        print(f'loading resource {self.id}')
        if data is None:
            data = await self.get_info()
            data = await self.load_extended_fields(data)

            self._data = data
            # do this last so the extension is loaded
            data[self._k_d_url] = await self.get_data_url()

            await self._cache.set(cache_key, data)

        self._data = data
        self._loaded = True

    async def load_extended_fields(self, data):
        for field in (await self.get_all_fields()):
            name = field['name']

            if name in self._extended_fields:
                data[name] = field['value']

        return data

    @property
    def id(self):
        return self._data.get(self._k_id, self._id)

    @property
    def ext(self):
        return self._data.get(self._k_ext)

    @property
    def title(self):
        return self._data.get(self._k_ui_title)

    @property
    def url(self):
        return self._data.get(self._k_d_url)

    @property
    def adventure(self):
        return self._data.get(self._k_adventure)

    @property
    def player(self):
        return self._data.get(self._k_player)

    @property
    def room(self):
        return self._data.get(self._k_room)

    @property
    def date(self):
        return self._data.get(self._k_date)

    @property
    def duration(self):
        return self._data.get(self._k_duration)

    @property
    def path(self):
        return self._data.get(self._k_path)

    async def get_data_url(self):
        return await self._get(
            'get_resource_path',
            {
                'ref': self.id,
                'getfilepath': 0,
                'extension': self.ext,
                # 'generate': True,
                # 'alternative': -1,
                # 'size': ''
            }
        )

    async def get_info(self):

        return await self._get(
            'get_resource_data',
            {
                'resource': self.id
            }
        )

    # Example response JSON:
    # [
    #     {"value": "Shipwreck Adventure", "resource_type_field": "86", "ref": "86", "name": "adventure_name",
    #      "title": "Adventure Name", "field_constraint": "0", "type": "3", "order_by": "0", "keywords_index": "1",
    #      "partial_index": "0", "resource_type": "0", "resource_column": "", "display_field": "1",
    #      "use_for_similar": "1", "iptc_equiv": "", "display_template": "", "tab_name": "", "required": "0",
    #      "smart_theme_name": "", "exiftool_field": "", "advanced_search": "1", "simple_search": "0", "help_text": "",
    #      "display_as_dropdown": "0", "external_user_access": "1", "autocomplete_macro": "", "hide_when_uploading": "0",
    #      "hide_when_restricted": "0", "value_filter": "", "exiftool_filter": "", "omit_when_copying": "0",
    #      "tooltip_text": "", "regexp_filter": "", "sync_field": "", "display_condition": "", "onchange_macro": "",
    #      "linked_data_field": "", "automatic_nodes_ordering": "0", "fits_field": "", "personal_data": "0",
    #      "include_in_csv_export": "1", "browse_bar": "1", "read_only": "0", "active": "1", "full_width": "0",
    #      "frequired": "0", "fref": "86"},
    #     {"value": "", "resource_type_field": "87", "ref": "87", "name": "player", "title": "Player",
    #      "field_constraint": "0", "type": "0", "order_by": "0", "keywords_index": "1", "partial_index": "0",
    #      "resource_type": "0", "resource_column": "", "display_field": "1", "use_for_similar": "1", "iptc_equiv": "",
    #      "display_template": "", "tab_name": "", "required": "0", "smart_theme_name": "", "exiftool_field": "",
    #      "advanced_search": "1", "simple_search": "0", "help_text": "", "display_as_dropdown": "0",
    #      "external_user_access": "1", "autocomplete_macro": "", "hide_when_uploading": "0", "hide_when_restricted": "0",
    #      "value_filter": "", "exiftool_filter": "", "omit_when_copying": "0", "tooltip_text": "", "regexp_filter": "",
    #      "sync_field": "", "display_condition": "", "onchange_macro": "", "linked_data_field": "",
    #      "automatic_nodes_ordering": "0", "fits_field": "", "personal_data": "0", "include_in_csv_export": "1",
    #      "browse_bar": "1", "read_only": "0", "active": "1", "full_width": "0", "frequired": "0", "fref": "87"},
    #     {"value": "Shipwreck Yard Front", "resource_type_field": "88", "ref": "88", "name": "room", "title": "Room",
    #      "field_constraint": "", "type": "3", "order_by": "0", "keywords_index": "1", "partial_index": "0",
    #      "resource_type": "0", "resource_column": "", "display_field": "1", "use_for_similar": "1", "iptc_equiv": "",
    #      "display_template": "", "tab_name": "", "required": "0", "smart_theme_name": "", "exiftool_field": "",
    #      "advanced_search": "1", "simple_search": "0", "help_text": "", "display_as_dropdown": "0",
    #      "external_user_access": "1", "autocomplete_macro": "", "hide_when_uploading": "0", "hide_when_restricted": "0",
    #      "value_filter": "", "exiftool_filter": "", "omit_when_copying": "0", "tooltip_text": "", "regexp_filter": "",
    #      "sync_field": "", "display_condition": "", "onchange_macro": "", "linked_data_field": "",
    #      "automatic_nodes_ordering": "0", "fits_field": "", "personal_data": "0", "include_in_csv_export": "1",
    #      "browse_bar": "1", "read_only": "0", "active": "1", "full_width": "0", "frequired": "0", "fref": "88"},
    #     {"value": "Description", "resource_type_field": "8", "ref": "8", "name": "title", "title": "Title",
    #      "field_constraint": "", "type": "0", "order_by": "10", "keywords_index": "1", "partial_index": "0",
    #      "resource_type": "0", "resource_column": "title", "display_field": "0", "use_for_similar": "1",
    #      "iptc_equiv": "2#005", "display_template": "", "tab_name": "", "required": "1", "smart_theme_name": "",
    #      "exiftool_field": "Title", "advanced_search": "1", "simple_search": "0", "help_text": "",
    #      "display_as_dropdown": "0", "external_user_access": "1", "autocomplete_macro": "", "hide_when_uploading": "0",
    #      "hide_when_restricted": "0", "value_filter": "", "exiftool_filter": "", "omit_when_copying": "",
    #      "tooltip_text": "", "regexp_filter": "", "sync_field": "", "display_condition": "", "onchange_macro": "",
    #      "linked_data_field": "", "automatic_nodes_ordering": "0", "fits_field": "", "personal_data": "0",
    #      "include_in_csv_export": "1", "browse_bar": "1", "read_only": "0", "active": "1", "full_width": "0",
    #      "frequired": "1", "fref": "8"},
    #     ...
    # ]

    async def get_all_fields(self):
        return await self._get(
            'get_resource_field_data',
            {
                'resource': self.id
            }
        )

    def _add_extended_field(self, field):
        self._data[field['name']] = field['']

    @staticmethod
    async def _get(function, params, unwrap=True) -> dict:
        base_url = Credentials[_cred_key][_base_url]

        params['function'] = function
        params['user'] = Credentials[_cred_key][_user]
        qstring = urllib.parse.urlencode(params)

        secret = Credentials[_cred_key][_secret]
        signer = hashlib.sha256()
        signer.update(f'{secret}{qstring}'.encode("utf-8"))

        request = f'{base_url}?{qstring}&sign={signer.hexdigest()}'
        result: Response = await asks.get(request)
        # print("-" * 60)
        # print(request)
        # print(">" * 5)
        # print(result)
        # print("\\/" * 5)
        # print(result.content.decode("utf-8"))
        # print("-" * 60)

        # if unwrap and result.status_code >= 200 and result.status_code < 300:
        result: dict = json.loads(result.content.decode("utf-8"))

        return result

    def __str__(self):
        return f'RSR[{self.id}] {self.url}'

    def __repr__(self):
        return str(self)


from typing import Optional

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        dummy = ListNode()

        while head:
            cur = head
            head = head.next
            cur.next = dummy.next
            dummy.next = cur

        return dummy.next


'''
Terminal
!pip install dash==0.26.5  # The core dash backend
!pip install dash-html-components==0.12.0  # HTML components
!pip install dash-core-components==0.28.0  # Supercharged components
!pip install dash_bootstrap_components==0.13.1
'''

# Run this app with `python app.py` and
# visit http://127.0.0.1:8050/ in your web browser.

from dash import Dash, dcc, html
import plotly.express as px
import pandas as pd
import dash_bootstrap_components as dbc
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
# create dash
app = Dash(__name__)

colors = {
    'background': '#FFFFFF',
    'text': '#288CC2'
}

### bar chart example
df = pd.DataFrame({
    "Fruit": ["Apples", "Oranges", "Bananas", "Apples", "Oranges", "Bananas"],
    "Amount": [4, 1, 2, 2, 4, 5],
    "City": ["SF", "SF", "SF", "Montreal", "Montreal", "Montreal"]
})
fig = px.bar(df, x="Fruit", y="Amount", color="City", barmode="group")
fig.update_layout(
    #plot_bgcolor=colors['background'],
    #paper_bgcolor=colors['background'],
    font_color=colors['text']
)

### scatter plot example
df2 = pd.read_csv('https://gist.githubusercontent.com/chriddyp/5d1ea79569ed194d432e56108a04d188/raw/a9f9e8076b837d541398e999dcbac2b2826a81f8/gdp-life-exp-2007.csv')
fig2 = px.scatter(df2, x="gdp per capita", y="life expectancy",
                 size="population", color="continent", hover_name="country",
                 log_x=True, size_max=60)

fig2.update_layout(
	font_color=colors['text'])

### violin plot example 1
df3 = pd.DataFrame(
    {'x':np.tile(['no', 'yes'], 80000),
     'y':np.random.normal(0, 1, 160000),
     'cl':np.repeat([0, 1], 80000)

    }
)

fig3 = px.violin(df3, x="x", y="y", color='cl', box=True, hover_data=df3.columns)
fig4 = px.violin(df3, y="y", color='cl',
                violinmode='overlay', # draw violins on top of each other
                # default violinmode is 'group' as in example above
                hover_data=df3.columns)

### violin plot example 2
df4 = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/violin_data.csv")

fig5 = go.Figure()

fig5.add_trace(go.Violin(x=df4['day'][ df4['smoker'] == 'Yes' ],
                        y=df4['total_bill'][ df4['smoker'] == 'Yes' ],
                        legendgroup='Yes', scalegroup='Yes', name='Yes',
                        side='negative',
                        line_color='blue')
             )
fig5.add_trace(go.Violin(x=df4['day'][ df4['smoker'] == 'No' ],
                        y=df4['total_bill'][ df4['smoker'] == 'No' ],
                        legendgroup='No', scalegroup='No', name='No',
                        side='positive',
                        line_color='orange')
             )
fig5.update_traces(meanline_visible=True) # orientation='h' -> horizontal
fig5.update_layout(violingap=0, violinmode='overlay')

### subplot example
df5 = px.data.iris()
fig6 = make_subplots(rows=1,
                    cols=2,
                    subplot_titles=[
                        'Fruit',  # 1. subplot title
                        'City'  # 2. subplot title
])

fig6.add_trace(go.Bar(x=df['Fruit'], y=df['Amount']),row=1, col=1)

fig6.add_trace(go.Bar(x=df['City'], y=df['Amount'], text=df['Amount'],
            textposition='auto',), row=1, col=2)

fig6.update_layout(title='Count', title_x=0.5)

# set the web layout
app.layout = html.Div(style={'backgroundColor': colors['background']}, children=[

    html.H1(
        children='Hello Dash',
        style={
            'textAlign': 'center',
            'color': colors['text']
        }
    ),

    html.Div(children='Dash: A web application framework for your data.', style={
        'textAlign': 'center',
        'color': colors['text']
    }),

    dcc.Graph(
        id='example-graph-1',
        figure=fig
    ),

    dcc.Graph(
        id='example-graph-2',
        figure=fig2
    ),

    dcc.Graph(
		id='example-graph-3',
		figure=fig3
	),

    dcc.Graph(
        id='example-graph-5',
        figure=fig5
    ),

    dcc.Graph(
        id='example-graph-6',
        figure=fig6
    ),

])

if __name__ == '__main__':
    app.run_server(debug=True)


"""
This file is used to perform a random hyperparameter search on the Coco dataset using the baseline image captioner.

For more info on the ImageCaptionerBaseline class, please check out the docstrings in the image_captioning.py file.
"""

# Package loading
import argparse
import os
import sys
sys.path.append('..')

# Depending on the platform/IDE used, the home directory might be the socraticmodels or the
# socraticmodels/scripts directory. The following ensures that the current directory is the scripts folder.
try:
    os.chdir('scripts')
except FileNotFoundError:
    pass

# Local imports
from scripts.image_captioning import ImageCaptionerBaseline

def parse_arguments():
    """
    Parses the arguments for the baseline COCO captioning hyperparameter tuning.

    :return:
    """
    # init argparser
    parser = argparse.ArgumentParser(description='Baseline Image Captioning Hyperparameter tuning')

    # Additional variables
    parser.add_argument('--n-images', type=int, default=50, help='# images to include in the dataset')
    parser.add_argument('--set-type',  type=str, default='train', help='train/valid/test set')
    parser.add_argument('--n-iterations', type=int, default=100, help='# of run iterations')
    parser.add_argument('--n-captions', type=int, default=10, help='# captions the LM should generate')
    parser.add_argument('--lm-max-length', type=int, default=40, help='max output length the LM should generate')
    parser.add_argument('--lm-do-sample', type=bool, default=True, help='whether to use sampling during generation')
    parser.add_argument('--lm-temp-min', type=float, default=0.5, help='minimum temperature param for the lm')
    parser.add_argument('--lm-temp-max', type=float, default=1, help='maximum temperature param for the lm')
    parser.add_argument('--n-objects-min', type=int, default=5, help='minimum number of objects in the LM prompt')
    parser.add_argument('--n-objects-max', type=int, default=15, help='maximum number of objects in the LM prompt')
    parser.add_argument('--n-places-min', type=int, default=1, help='minimum number of places in the LM prompt')
    parser.add_argument('--n-places-max', type=int, default=6, help='maximum number of places in the LM prompt')
    parser.add_argument('--caption-strategies', nargs="+", default=None)

    # parse args
    args = parser.parse_args()

    return args

if __name__ == '__main__':

    # Parse the arguments.
    args = parse_arguments()

    # Instantiate the baseline image captioner class.
    image_captioner = ImageCaptionerBaseline(n_images=args.n_images, set_type=args.set_type)

    # Run the hyperparameter search
    image_captioner.random_parameter_search(
        n_iterations=args.n_iterations, n_captions=args.n_captions, lm_max_length=args.lm_max_length,
        lm_do_sample=args.lm_do_sample, lm_temp_min=args.lm_temp_min, lm_temp_max=args.lm_temp_max,
        n_objects_min=args.n_objects_min, n_objects_max=args.n_objects_max, n_places_min=args.n_places_min,
        n_places_max=args.n_places_max, caption_strategies=args.caption_strategies
    )


from socket import *
import time
import osascript
from multiprocessing import Process, Manager, Value
import os
#osascript -e 'display notification "{}" with title "{}"'

volume = 0

def recieve_data(val):
    serverSock = socket(AF_INET, SOCK_STREAM)
    serverSock.bind(('', 7777))
    serverSock.listen(1)
    connectionSock, addr = serverSock.accept()
    print("Client address : ", str(addr))

    while True:
        print("val : ", val.value)
        try :
            vol = int(connectionSock.recv(4).decode('utf-8'))
            if vol == 1111:
                print("mute")
                osascript.osascript('set volume output muted TRUE')
                val.value = 0
                while True:
                    vol = int(connectionSock.recv(4).decode('utf-8'))
                    if vol == 2222:
                        osascript.osascript('set volume output muted FALSE')
                        break

            if vol == 3333:
                print("screenshot")
                os.system("screencapture screen.png")
                vol = 0

            if vol == 4444:
                print("fix volume")
                osascript.osascript('tell app "System Events" to shut down')
                time.sleep(5)
            if vol < 300:
                val.value = vol
        except:
            pass

def volume_control(val):
    while True:
        print("volume : ", val.value)
        osascript.osascript("set volume output volume " + str(val.value))
        time.sleep(0.1)

if __name__ == '__main__':
    v = Value('i', 0)
    p0 = Process(target = recieve_data, args = (v,))
    p0.start()

    p1 = Process(target = volume_control, args = (v,))
    p1.start()

    p0.join()
    p1.join()


import networkx as nx
from sklearn.cluster import SpectralClustering

def spectral_clustering(G, n_clusters=2):
    adj_mat = nx.to_numpy_matrix(G)
    sc = SpectralClustering(n_clusters, affinity='precomputed', n_init=100)
    sc.fit(adj_mat)
    clusters = {}
    for i in range(len(sc.labels_)):
        if sc.labels_[i] not in clusters:
            clusters[sc.labels_[i]] = []
        clusters[sc.labels_[i]].append(i)
    return clusters.values()


# Created on 24 September 2019

from square import Square, getScaledFont
from random import randint
from math import cos, sin, pi, atan, copysign
from pygame.mixer import *
from pygame.draw import rect
from pygame.locals import *
from pygame.time import Clock
from pygame.display import update
from pygame.mouse import get_pos

class GameDriver:
    def __init__(self, dim, w):
        self.dim = dim
        self.w = w
        self.squares = []
        self.vals = []
        # Stores sets of ((x,y): (#Squares to slide, surface))
        self.slides = {}
        self.slide_duration = 300
        self.v = (0, 0)
        self.score = 0
        self.prev_score = 0
        for y in range(dim[1]):
            row = []
            val = []
            for x in range(dim[0]):
                row.append(None)
                val.append(-1)
            self.squares.append(row)
            self.vals.append(val)

    def drawBoard(self, display):
        for y, row in enumerate(self.squares):
            for x, s in enumerate(row):
                if s != None:
                    display.blit(s.surface, (x * self.w, y * self.w))
                else:
                    rect(display, (0, 0, 0), (x * self.w, y * self.w, self.w, self.w))
                    rect(display, (255, 255, 255), (x * self.w, y * self.w, self.w, self.w), 2)
        update()

    def move(self, display, undo):
        if len(self.slides) == 0:
            return
        if undo:
            self.score = self.prev_score
            for y, (row1, row2) in enumerate(zip(self.squares, self.vals)):
                for x, (s, val) in enumerate(zip(row1, row2)):
                    if val == -1 and s != None:
                        self.squares[y][x] = None
                    elif val != -1 and s == None:
                        self.squares[y][x] = Square(val, self.w)
                    elif val != -1 and s != None:
                        self.squares[y][x].changeVal(val)
        updates = 20
        for i in range(updates):
            for x, y in self.slides.keys():
                v, surface = self.slides[(x, y)]
                xf, yf = x + v[0], y + v[1]
                if undo:
                    v = (-v[0], -v[1])
                    x, xf = xf, x
                    y, yf = yf, y
                v = (v[0] * self.w, v[1] * self.w)
                x1, y1 = x * self.w, y * self.w
                dx, dy = v[0] * i / updates, v[1] * i / updates
                rect(display, (0, 0, 0), (x1 + dx, y1 + dy, self.w, self.w))
                dx, dy = v[0] * (i + 1) / updates, v[1] * (i + 1) / updates
                display.blit(surface, (x1 + dx, y1 + dy))
            update()
            Clock().tick(updates * 1000 / self.slide_duration)
        self.drawBoard(display)

    def addSquares(self, display):
        nones = []
        for y, row in enumerate(self.squares):
            for x, s in enumerate(row):
                if s == None:
                    nones.append((x, y))
        for i in range(min(len(nones), 2)):
            idx = randint(0, len(nones) - 1)
            x, y = nones[idx]
            s = Square(2, self.w)
            self.squares[y][x] = s
            display.blit(s.surface, (x * self.w, y * self.w))
            nones.pop(idx)
        update()

    def lost(self):
        for y, row in enumerate(self.squares):
            for x, s in enumerate(row):
                if s == None:
                    return False
                else:
                    adjacent = []
                    for delta in ((-1, 0), (1, 0), (0, -1), (0, 1)):
                        x1, y1 = x + delta[0], y + delta[1]
                        in_range = 0 <= x1 < self.dim[0] and 0 <= y1 < self.dim[1]
                        if in_range and self.squares[y1][x1] != None:
                            adjacent.append(self.squares[y1][x1].val)
                    if s.val in adjacent:
                        return False
        return True

    def updateScore(self, display, score_rect):
        font = getScaledFont("Times New Roman", (score_rect.w, score_rect.h), str(self.score))
        text = font.render(str(self.score), 1, (255, 255, 255))
        text_rect = text.get_rect(center=(score_rect.centerx, score_rect.centery))
        rect(display, (0, 0, 0), text_rect)
        display.blit(text, text_rect)

    def run(self, display, events, undo_rect, score_rect):
        for e in events:
            if e.type == MOUSEBUTTONUP and e.button == BUTTON_LEFT and \
                    undo_rect.collidepoint(get_pos()):
                self.move(display, True)
                self.slides.clear()
            elif e.type == KEYUP:
                if e.key == K_LEFT:
                    self.v = (-1, 0)
                elif e.key == K_RIGHT:
                    self.v = (1, 0)
                elif e.key == K_UP:
                    self.v = (0, -1)
                elif e.key == K_DOWN:
                    self.v = (0, 1)
                else:
                    continue
                self.slides.clear()
                self.prev_score = self.score
                move_x = self.v[0] != 0
                is_neg = -1 in self.v
                idx = 0 if move_x else 1
                lb = 0 if is_neg else -abs(self.dim[idx] * self.v[idx]) + 1
                ub = abs(self.dim[idx] * self.v[idx]) if is_neg else 1
                blanks = []
                merges = []
                prev = []
                for v1 in range(lb, ub):
                    for v2 in range(self.dim[1 - idx]):
                        if len(blanks) <= v2:
                            blanks.append(0)
                            prev.append(0)
                            merges.append(0)
                        x = abs(v1) if move_x else v2
                        y = v2 if move_x else abs(v1)
                        s = self.squares[y][x]
                        self.vals[y][x] = -1 if s == None else s.val
                        if s == None:
                            blanks[v2] += 1
                        else:
                            offset = blanks[v2] + merges[v2]
                            dx, dy = self.v[0] * offset, self.v[1] * offset
                            last_val = prev[v2]
                            prev[v2] = s.val
                            if last_val == s.val:
                                offset += 1
                                dx1, dy1 = self.v[0] * offset, self.v[1] * offset
                                self.slides[(x, y)] = ((dx1, dy1), s.surface)
                                self.squares[y + dy1][x + dx1].upgrade()
                                self.squares[y][x] = None
                                prev[v2] = 0
                                merges[v2] += 1
                                self.score += s.val * 2
                            elif offset != 0:
                                self.slides[(x, y)] = ((dx, dy), s.surface)
                                self.squares[y + dy][x + dx] = s
                                self.squares[y][x] = None
                self.move(display, False)
                self.addSquares(display)
                if sum(merges) >= 3:
                    music.load("bomb.mp3")
                    music.play()
            self.updateScore(display, score_rect)
            return self.lost()


from typing import Dict, List, Type
from src.domain.models.pets import Pets
from src.domain.use_cases import FindPet as FindPetInterface
from src.data.interfaces import PetRepositoryInterface

class FindPet(FindPetInterface):
    """Use case for Find pet"""

    def __init__(self, pets_repository: Type[PetRepositoryInterface]):
        self.pets_repository = pets_repository

    def by_id(self, pet_id: int) -> Dict[bool, List[Pets]]:
        """Method By id"""
        response = None
        validate = isinstance(pet_id, int)

        if validate:
            response = self.pets_repository.select_pet(pet_id=pet_id)

        return {"Success": validate, "Data": response}

    def by_user_id(self, user_id: int) -> Dict[bool, List[Pets]]:
        """Get pet by name"""
        response = None
        validate = isinstance(user_id, int)

        if validate:
            response = self.pets_repository.select_pet(user_id=user_id)

        return {"Success": validate, "Data": response}

    def by_pet_id_and_user_id(
        self, pet_id: int, user_id: int
    ) -> Dict[bool, List[Pets]]:
        """Get pet by name"""
        response = None
        validate = isinstance(user_id, int) and isinstance(pet_id, int)

        if validate:
            response = self.pets_repository.select_pet(user_id=user_id, pet_id=pet_id)

        return {"Success": validate, "Data": response}


###############################################################################
#  Process to read Customer Updates                                           #
#
#  Pre-requisites: Kafka server should be running                             #
###############################################################################
import os
import sys
import logging
import json

import settings as SETTINGS

curpath = os.path.dirname(__file__)
sys.path.append(os.path.abspath(os.path.join (curpath, "../")))

from app_messaging_utils import SimpleKafkaConsumer, SimpleKafkaMessage
from app_models import Customer, AppEventType
from app_utils import MongoRepository, DbEntity

###############################################################################

class MessageProcessor():

    def __init__(self, process_func=None):

        #Create and configure logger
        logfile = os.path.abspath('{0}/{1}'.format(SETTINGS.Logging["LogFolder"],SETTINGS.Logging["LogFile"]))
        os.makedirs(os.path.dirname(logfile), exist_ok=True)

        logging.basicConfig(
            filename=logfile,
            format='%(asctime)s %(message)s',
            filemode='a'
        )

        #Creating an object
        self.logger=logging.getLogger()

        #Setting the threshold of logger to DEBUG
        self.logger.setLevel(SETTINGS.Logging["LogLevel"])

        self.config = SETTINGS.KafkaService
        self.topic  = SETTINGS.MESSAGE_TOPIC

        self.customer_repo = MongoRepository(
            logger=self.logger,
            server=SETTINGS.MongoDB["Url"],
            port=SETTINGS.MongoDB["Port"],
            database=SETTINGS.MongoDB["Db"],
            collection=SETTINGS.MongoDB["Collection"],
            session_id=1)

    ###########################################################################
    def process_message(self, evt_msg: SimpleKafkaMessage):
        '''
            Function to process SimpleKafkaMessage

            Deserialize the SimpleKafkaMessage,
            extract and process relevant payload
        '''
        try:

            evt = json.loads(evt_msg.message)

            if evt["app_event_type"] == AppEventType.Insert:
                entity = evt["after_change"]
                customer = Customer(
                    id=entity["id"],
                    name=entity["name"],
                    phone=entity["phone"],
                    email=entity["email"]
                )

                msg="Processing INSERT message for customer id:{0}".format(customer.id)
                print(msg)

                eid = self.customer_repo.create(customer)  # expect to get back an ObjectId

                msg="Created customer id:{0}".format(customer.id)
                print(msg)
                self.logger.debug(msg)

            elif evt["app_event_type"] == AppEventType.Update:
                entity = evt["after_change"]
                customer = Customer(
                    id=entity["id"],
                    name=entity["name"],
                    phone=entity["phone"],
                    email=entity["email"]
                )

                msg="Processing UPDATE message for customer id:{0}".format(customer.id)
                print(msg)

                self.customer_repo.update_by_id(customer.id, customer)
                msg="Updated customer id:{0}".format(customer.id)
                print(msg)
                self.logger.debug(msg)

            elif evt["app_event_type"] == AppEventType.Delete:
                entity = evt["after_change"]
                customer = Customer(
                    id=entity["id"],
                    name=entity["name"],
                    phone=entity["phone"],
                    email=entity["email"]
                )

                msg="Processing DELETE message for customer id:{0}".format(customer.id)
                print(msg)

                self.customer_repo.delete_by_id(customer.id)

                msg="Deleted customer id:{0}".format(customer.id)
                print(msg)
                self.logger.debug(msg)

            else:
                pass

        except Exception as e:
            msg = "Error in process_message function: {0}".format(str(e))
            print(msg)
            self.logger.error(msg)

    ###########################################################################
    def read_messages(self):
        '''
            Function to read messages from kafka queue
        '''
        reader_id = self.config["group.id"]
        counter=0

        try:
            msg = "Starting Process:{0} to read topic:{1} from Kafka Queue".format( reader_id , self.topic )
            self.logger.info(msg)
            print(msg)

            consumer = SimpleKafkaConsumer(logger=self.logger)
            consumer.configure(config=self.config)

            print ("Starting Consumer")
            for evt_msg in consumer.consume(topics=['MICROSERVICE-CUSTOMER-UPDATES']):

                counter +=1

                # msg = "Received msg: {0} # {1}".format(counter, evt_msg.message)
                # print(msg)
                # self.logger.debug(msg)

                # Process the message
                self.process_message(evt_msg)

        except KeyboardInterrupt:
            msg = "\n\n Exiting Process:'{0}'. {1} message(s) read on topic from Kafka Queue:'{2}'".format( reader_id, counter, self.topic )
            print (msg)
            self.logger.info(msg)

        except Exception as e:
            msg = "Error in {0} : {1}".format(reader_id, str(e))
            print(msg)
            self.logger.error(msg)

###############################################################################
if __name__ == "__main__":

    MessageProcessor().read_messages()

###############################################################################


from typing import List
from collections import Counter
from time import time

import matplotlib.pyplot as plt
import numpy as np

# constants
ENGLISH_ALPHABET_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ '

def get_string_size(string: str, format: str='utf8') -> int:
    '''Returns size of string in bytes'''
    return len(string.encode('utf-8'))

def get_words_from_text(text: str, approved_chars=ENGLISH_ALPHABET_CHARS) -> List[str]:
    '''Returns list of filtered words from a text'''

    # filter unwanted characters from text
    text = ''.join(char for char in text if char in approved_chars)

    # split and format words into list
    words = [word.lower() for word in text.split(' ') if len(word) > 0]

    return words

# read in file
with open('input.txt', 'r') as input_file:

    # iterate through file entries and extract words
    words = []
    for i, entry_text in enumerate(input_file):

        # get words from text and append
        entry_words = get_words_from_text(entry_text)
        words.extend(entry_words)

    # count and rank words
    word_count_rank = dict(Counter(words).most_common())
    n_unique_words = len(word_count_rank)

    # plot
    fig, ax = plt.subplots()
    plot = ax.plot(range(n_unique_words), list(word_count_rank.values()))
    ax.set_xticks(np.arange(1, n_unique_words+1, 25))

    n_labels = 6
    for i, word in enumerate(list(word_count_rank)[:n_labels]):
        ax.text(i, word_count_rank[word], word, fontsize=8)

    plt.show()


from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from simple_menu import MenuItem

submenu_items = [
    MenuItem(
        _("customers").capitalize(),
        reverse("packs:sales_customer_list"),
        weight=20,
        icon="bx-right-arrow-alt",
    ),
    MenuItem(
        _("invoices").capitalize(),
        reverse("packs:sales_invoice_list"),
        weight=20,
        icon="bx-right-arrow-alt",
    ),
]

sales_item = MenuItem(
    _("sales").capitalize(), "#", icon="bxs-shopping-bag", children=submenu_items
)


# Adapted from pytorch examples

from __future__ import print_function
from torch import nn, optim
from railrl.core import logger
import numpy as np
from railrl.pythonplusplus import identity
from railrl.torch.core import PyTorchModule
from railrl.torch.networks import Mlp
import railrl.torch.pytorch_util as ptu

class ReprojectionNetworkTrainer():
    def __init__(
            self,
            train_dataset,
            test_dataset,
            model,
            batch_size=128,
            log_interval=0,
            lr=1e-3,
            **kwargs
    ):
        self.log_interval = log_interval
        self.batch_size = batch_size

        if ptu.gpu_enabled():
            model.cuda()

        self.model = model
        self.representation_size = model.representation_size

        self.optimizer = optim.Adam(self.model.parameters(), lr=lr)
        self.train_dataset, self.test_dataset = train_dataset, test_dataset
        assert self.train_dataset['z'].dtype == np.float32
        assert self.test_dataset['z'].dtype ==np.float32
        assert self.train_dataset['z_proj'].dtype == np.float32
        assert self.test_dataset['z_proj'].dtype == np.float32
        self.mse = nn.MSELoss()

    def get_batch(self, train=True):
        dataset = self.train_dataset if train else self.test_dataset
        ind = np.random.randint(0, len(dataset['z']), self.batch_size)
        return {
            'z': ptu.np_to_var(dataset['z'][ind, :]),
            'z_proj': ptu.np_to_var(dataset['z_proj'][ind, :]),
        }

    def mse_loss(self, z_proj_hat, z_proj):
        return self.mse(z_proj_hat, z_proj)

    def train_epoch(self, epoch, batches=100):
        self.model.train()
        mses = []
        losses = []
        for batch_idx in range(batches):
            data = self.get_batch()
            z = data["z"]
            z_proj = data['z_proj']
            self.optimizer.zero_grad()
            z_proj_hat = self.model(z)
            mse = self.mse_loss(z_proj_hat, z_proj)
            loss = mse
            loss.backward()

            mses.append(mse.data[0])
            losses.append(loss.data[0])

            self.optimizer.step()

        logger.record_tabular("train/epoch", epoch)
        logger.record_tabular("train/MSE", np.mean(mses))
        logger.record_tabular("train/loss", np.mean(losses))

    def test_epoch(self, epoch, save_network=True, batches=100):
        self.model.eval()
        mses = []
        losses = []
        for batch_idx in range(batches):
            data = self.get_batch(train=False)
            z = data["z"]
            z_proj = data['z_proj']
            z_proj_hat = self.model(z)
            mse = self.mse_loss(z_proj_hat, z_proj)
            loss = mse

            mses.append(mse.data[0])
            losses.append(loss.data[0])

        logger.record_tabular("test/epoch", epoch)
        logger.record_tabular("test/MSE", np.mean(mses))
        logger.record_tabular("test/loss", np.mean(losses))

        logger.dump_tabular()
        if save_network:
            logger.save_itr_params(epoch, self.model, prefix='reproj', save_anyway=True)

class ReprojectionNetwork(PyTorchModule):
    def __init__(
            self,
            vae,
            hidden_sizes=list([64, 128, 64]),
            init_w=1e-3,
            hidden_init=ptu.fanin_init,
            output_activation=identity,
            layer_norm=False,
            **kwargs
    ):
        self.save_init_params(locals())
        super().__init__()
        self.vae = vae
        self.representation_size = self.vae.representation_size
        self.hidden_init = hidden_init
        self.output_activation = output_activation
        # self.dist_mu = np.zeros(self.representation_size)
        # self.dist_std = np.ones(self.representation_size)
        self.dist_mu = self.vae.dist_mu
        self.dist_std = self.vae.dist_std
        self.relu = nn.ReLU()
        self.init_w = init_w
        hidden_sizes = list(hidden_sizes)
        self.network=Mlp(hidden_sizes,
                         self.representation_size,
                         self.representation_size,
                         layer_norm=layer_norm,
                         hidden_init=hidden_init,
                         output_activation=output_activation,
                         init_w=init_w)

    def forward(self, z):
        z = z.view(-1, self.representation_size)
        return self.network(z)

    def __getstate__(self):
        d = super().__getstate__()
        # Add these explicitly in case they were modified
        d["_dist_mu"] = self.dist_mu
        d["_dist_std"] = self.dist_std
        return d

    def __setstate__(self, d):
        super().__setstate__(d)
        self.dist_mu = d["_dist_mu"]
        self.dist_std = d["_dist_std"]


"""

Author: JW
Date: 07/26/2023
Module Name: picture_capture_controls_uplink.py

Description:
    This Python script is part of an image processing and classification application.
    It provides various functions for interacting with images, databases, and user stacks.
    The script includes functionalities such as simulating image classification, checking classification progress, and updating image labels in a database.
    It is designed to work with Anvil, tkinter, multiprocessing, and PIL (Python Imaging Library) libraries.

Functions:
- `open_file_explorer`: Opens a file explorer dialog for selecting directories.
- `classify_images_simulate`: Simulates image classification and stores results in a database.
- `start_classifier_build`: Initiates the image classification process, handling new or existing image stacks.
- `check_classifier_progress`: Monitors the progress of image classification and retrieves completed labels and images.
- `submit_labels_to_db`: Handles the submission of labels to a database, updates labels, and moves files based on labels.

For detailed information on each function's purpose and usage, please refer to the function definitions and comments within the script.

"""
from time import sleep
import random
import json
import uuid
import multiprocessing
from PIL import Image
import anvil.media
import os
import io
import shutil

# Uplink imports:
try:
    import utils.mySQL_utils as localSQL
    from uplink_scripts.stack import Stack
# Local host imports
except (ModuleNotFoundError) as mod_err:
    print("Trying local host imports in picture_capture_controls.py")
    from ..utils import mySQL_utils as localSQL
    from .stack import Stack

# NOTE: When running from a docker container, we will be unable to import tkinter:
try:
    import tkinter as tk
    from tkinter import filedialog
except(ImportError) as err:
    print("Unable to import tkinter")

# Set up our stack:
image_stack = Stack()

def open_file_explorer():
    """
        Opens a file explorer navigator for the user to select the source and / or destination directory.

        Returns str(file_path)
        *Depending on when function is called, file_path could be either the source or destination dir.
    """
    try:
        root = tk.Tk()
        root.withdraw()

        file_path = filedialog.askdirectory()

        if not file_path:
            file_path = "N/A"

        root.destroy()

        return file_path
    except (Exception) as err:
        print("tikinter not installed...returning empty path")
        return ""

def classify_images_simulate(image_full_path, img_name_list, job_id):
    """ Test function to simulate classify_images()
        1. sleep 5 seconds
        2. randomly pick a class
        3. write result and job id to data-table
    """

    labels = ["Cotton", "Plastic", "HID", "Tray", "Other"]
    cnx = localSQL.sql_connect()

    for index, img in enumerate(image_full_path):
        # 1 sleep
        sleep(10)
        # 2 randomly select a label
        rand = random.randint(0, 4)
        label = labels[rand]
        # write label & job_id to data-table:
        img_name = img_name_list[index]

        insert_query = f"INSERT INTO anvil_imgProcessor (job_id, img_name, img_label) VALUES ('{job_id}','{img_name}','{label}')"

        localSQL.sql_insert(cnx, insert_query)

    # Close db connection
    localSQL.sql_closeConnection(cnx)
    print("Finished classyfing")

def start_classifier_build(json_data):
    """
        json_data: {image_path, num_images}
    """

    # convert json dict to python dict
    python_dict_classifier = json.loads(json_data)

    # Unpack the dictionary:
    page_num = python_dict_classifier.get("page_num")
    user_id = python_dict_classifier.get("user_id")
    num_images = python_dict_classifier.get("num_images")
    file_path = python_dict_classifier.get("file_path_src")

    # IF user wants to grab previous images (back_button press or jump_to_page) -> "pop" images from stack, ELSE get new images
    try:
        # Try getting images from the users stack using page_num as the list index.
        labels, img_names, images, update_database = image_stack.pop(user_id, page_num)

        # If the number of images retrieved == to number of images user currently wants to retrieve, return the images:
        if (int(num_images) == len(img_names)):
            print(f"Retrieved previous images for page {page_num}")
            return images, labels, img_names, update_database
        # If the user changed the number of images to display on each page -> reset stack and grab new images.
        else:
            print(f"Number of images changed... reseting users stack")
            # TODO: If user changed the number of images to grab, reset the users stack:
            image_stack.reset_stack(user_id)
    # If we get a KeyError or IndexError -> grab new images from directory.
    except (KeyError, IndexError) as err:
        print(f"{err}: Grabbing new images for page {page_num}")

    # Set up a job ID:
    job_id = str(uuid.uuid4())

    job_id = job_id.replace("-", "")

    try:
        # NOTE: with large n we may want to only a subset of all images
        all_files_in_dir = os.listdir(file_path)
        # Filter to select only image files:
        all_images = [file for file in all_files_in_dir if file.endswith(".jpg") or file.endswith(".png")]
    except (Exception) as e:
        print("Could not access directory")
        return None

    num_images_found = len(all_images)

    # Check to make sure images were found in the directory:
    if(num_images_found == 0):
        print("Dir does not contain any images")
        return None, None, None, None
    # If for whatever reason the directory has < 10 images -> grab all found images
    if(num_images_found < int(num_images)):
        # Randomly select n images:
        rand_n_imgs = random.sample(all_images, int(num_images_found))
        # now that we've selected our images, lets move them to a seperate folder such that they are not re-used
    else:
        # Randomly select n images:
        rand_n_imgs = random.sample(all_images, int(num_images))
        # now that we've selected our images, lets move them to a seperate folder such that they are not re-used

    #Establish Connection to the Databse:
    cnx = localSQL.sql_connect()

    # Write job ID to anvil_img_Classifier data-table:
    insert_query = f"INSERT INTO anvil_imgProcessor (job_id) VALUES ('{job_id}')"

    localSQL.sql_insert(cnx, insert_query)

    #Close connection to the database:
    localSQL.sql_closeConnection(cnx)

    imgs_full_path, img_name_list  = [], []

    # Loop accomplishes two things:
    # 1) Creates the full image path for each randomly selected image
    # 2) Reads in the image and converts to anvil.BlobMedia
    for image in rand_n_imgs:

        # Get the full image path
        img_full_path = file_path + "/" + image

        # Keep track of all the img paths
        imgs_full_path.append(img_full_path)
        img_name_list.append(image)

    ##############
    # NOTE: SPAWN new process here:
    #classify_images(imgs_full_path, job_id)
    ##############
    process = multiprocessing.Process(target=classify_images_simulate, args=(imgs_full_path, img_name_list, job_id))
    # Start the process
    process.start()

    return job_id

def check_classifier_progress(json_data):
    """
        This function will be called every n seconds once timer reaches 0...
        1. Every n seconds go out and check database to see how many images / n are ready
            1a. if > n images are done, return % finished and update progress bar.
            1b. if n images are done retrieve labels, set flag HIGH indiciating we are ready to display the images to the user

    """

    MAX_STACK_HEIGHT = 50 # Starting with 50, could be increased... (100*num_images) = # of elem ents in each stack

    # convert json dict to python dict
    python_dict_classifier = json.loads(json_data)

    # Unpack the dictionary:
    user_id = python_dict_classifier.get("user_id")
    num_images = python_dict_classifier.get("num_images")
    job_id = python_dict_classifier.get("job_id")
    file_path = python_dict_classifier.get("file_path_src")

    # Check database using job_id to see how many images are ready.
    # Establish Connection to the Databse:
    cnx = localSQL.sql_connect()
    # Create a cursor
    cursor = cnx.cursor()

    search_query = f"SELECT * FROM anvil_imgProcessor WHERE job_id = ('{job_id}')"

    cursor.execute(search_query)
    rows = cursor.fetchall()

    # Close the connection
    cnx.close()

    num_rows_ready = len(rows)
    print(num_rows_ready)

    img_labels_list, img_name_list, img_list = [], [], []
    img_labels_dict = {}

    if(num_rows_ready == (num_images + 1)):
        done_classifying = True # Set our flag to true
        pct_ready = 1
        # Once images are done get the assigned labels:
        for row in rows:
            # Get the assigned label for each image:
            img_labels_list.append(row[-1])
            img_name_list.append(row[-2])

        # Delete the first element of each list (first element has NULL label and img name values)
        del img_labels_list[0]
        del img_name_list[0]

        # Store key-value pair (img_name: label) in dict data-structure
        for i in range(len(img_name_list)):
            img_labels_dict[img_name_list[i]] = img_labels_list[i]

            # Using the image name and file path, import the image to type anvil.BlobMedia
            # Get the full image path
            img_full_path = file_path + "/" + img_name_list[i]

            # Retrieve our image using PIL
            pil_img = Image.open(img_full_path)

            # resize image to 1280 x 960
            resized_image = pil_img.resize((960,720))

            bs = io.BytesIO()
            # Convert to bytes:
            resized_image.save(bs, format="png")

            # Conver to type anvil.BlobMedia so that we can display it for the client
            anvil_image = anvil.BlobMedia("image/png", bs.getvalue(), name="cotton")
            img_list.append(anvil_image)

        print(img_labels_list)
        print(img_labels_dict)

        # Set-up the "stack" here:
        # Pythonic: If user does not have a stack created, create one
        try:
            print(f"Adding images for user {user_id} to stack...")
            image_stack.push(user_id,
                             img_labels_dict,
                             img_name_list,
                             img_list)
        except (KeyError) as ke:
            print("No ID found!")
            print(f"Creating stack for user: {user_id} ")
            image_stack.init_user(user_id,
                                  img_labels_dict,
                                  img_name_list,
                                  img_list)

        #Check length of stack if stack is > max_len --> start removing elements
        try:
            stack_height = image_stack.size(user_id)
            if(stack_height > MAX_STACK_HEIGHT):
                print(f"Users stack reached max height of {MAX_STACK_HEIGHT}, Removing first element...")
                # Delete first [0] from stack
                image_stack.delete_element(user_id)
        except (KeyError) as err:
            print(f"Unable to get height of users stack: {err}")

        return [done_classifying, pct_ready, img_labels_dict, img_name_list, img_list]
    else:
        done_classifying = False
        pct_ready = ((num_rows_ready - 1) / (num_images)) * 100

        return [done_classifying, pct_ready, False, False, False]

def submit_labels_to_db(json_data):
    """
        Retrieves images from src directory, runs through classifier, adds images to users stack, and returns images and labels.

        Function Outline:
        1. Unpack JSON data
        2. Determine if retreiving previously used images, or grabing new images from directory.
            Using a Try / Except statement, that returns a IndexError if the index (page_num) is not valid (aka grab new images then)
        3. Access the source directory (file_path) and randomly selected num_images_to_get from directory.
        4. Convert each image to type Anvil.BlobMedia so that we can display them in a Canvas component.
            4a. TEMPORARY: assign image a "dummy" label of either HID or Cotton
            4b. TODO: ADD in classifers to replace "dummy" labels
        5. Check if user already has a stack made for them, if not create one using user_id
            5a. Add images to already made or newly created user stack
        6. Check if MAX_STACK_HEIGHT has been exceeded, if so remove first entry from stack.
        6. Return the images (img_list), img_labels (img_label_dict), img names (img_name_list), and update_database BOOLEAN indicator

    """

    #Extract our json data into a python dict
    python_dict = json.loads(json_data)

    processed_dir = python_dict.get("file_path_dst")
    #processed_dir = "/home/pi/Desktop/Jon_workspace/Anvil/processed_images" # NOTE: ONLY USED FOR TESTING (REMOVE FOR DEPLOYMENT)

    # Create the destination directory if it doesn't exist
    if not os.path.exists(processed_dir):
        os.makedirs(processed_dir)

    keys_list = []

    # Unpack the dict:
    classifier_labels = python_dict.get("original_labels")
    #human modified labels
    modified_labels = python_dict.get("modified_labels")
    selected_folder = python_dict.get("selected_folder")
    page_num = python_dict.get("page_num")
    user_id = python_dict.get("user_id")
    use_sub_folders = python_dict.get("proc_sub_folders")

    #If user manually specified the path, enter:
    if(selected_folder == "dir"):

        # Add the user modified labels to their stack:
        try:
            print(f"Adding modified labels for user {user_id} to stack...")
            image_stack.push(user_id,
                             user_labels=modified_labels)
        except (KeyError) as ke:
            print("No ID found!")
            print(f"Creating modified labels stack for user: {user_id} ")
            image_stack.init_user(user_id,
                                  user_labels=modified_labels)

        file_path = python_dict.get("file_path_src")
        #file_path = "/home/pi/Desktop/Jon_workspace/Anvil/Cotton" # NOTE: ONLY USED FOR TESTING (REMOVE FOR DEPLOYMENT)

        # Check if we need to set up sub-folders:
        if(use_sub_folders):
            print("Setting up sub folders")
            # Set-up sub-folders for the processed images
            proc_cotton_dir = processed_dir + "/cotton"
            proc_tray_dir = processed_dir + "/tray"
            proc_plastic_dir = processed_dir + "/plastic"
            proc_hid_dir = processed_dir + "/HID"
            proc_other_dir = processed_dir + "/other"
            proc_mislabeled_dir = processed_dir + "/mislabeled"

            # Create the destination directory if it doesn't exist
            if not os.path.exists(proc_cotton_dir):
                os.makedirs(proc_cotton_dir)
            if not os.path.exists(proc_tray_dir):
                os.makedirs(proc_tray_dir)
            if not os.path.exists(proc_plastic_dir):
                os.makedirs(proc_plastic_dir)
            if not os.path.exists(proc_hid_dir):
                os.makedirs(proc_hid_dir)
            if not os.path.exists(proc_other_dir):
                os.makedirs(proc_other_dir)
            if not os.path.exists(proc_mislabeled_dir):
                os.makedirs(proc_mislabeled_dir)

        # get all the keys (image names)
        for key in classifier_labels:
            keys_list.append(key)

        # Next, Establish Connection to the Databse:
        cnx = localSQL.sql_connect()

        # Loop through each key(image name) and add to correct db column
        for key in range(len(keys_list)):
            image_name = keys_list[key]
            orginal_label = classifier_labels[keys_list[key]]
            corrected_label = modified_labels[keys_list[key]]

            # Get our source path (used with moving the image):
            source_path = os.path.join(file_path, image_name)
            # Get our processed img path:
            dest_path = os.path.join(processed_dir, image_name)

            if(orginal_label == corrected_label):
                correctP = True
                # Add to to columns: Correct_column, JOINT, and Path
                add_query = f"INSERT INTO anvil_imgClassification ({corrected_label}, JOINT, Path) VALUES ('{str(keys_list[key])}', '{str(orginal_label)}' ,'{str(source_path)}')"
                localSQL.sql_insert(cnx, add_query)
            else:
                # If the classifier got the prediction wrong, add img file name to GotWrong column and correct column in database
                correctP = False
                # Add to to columns: GotWrong, Correct_column, JOINT, and Path
                gotWrong_query = f"INSERT INTO anvil_imgClassification (GotWrong, {corrected_label}, JOINT, Path) VALUES ('{str(keys_list[key])}', '{str(keys_list[key])}', '{str(orginal_label)}' , '{str(source_path)}')"
                localSQL.sql_insert(cnx, gotWrong_query)

            #Lastly, move image to new processed directory:
            try:
                if(use_sub_folders):
                    if(corrected_label == "Cotton"):
                        shutil.copy(source_path, proc_cotton_dir)
                    elif(corrected_label == "Plastic" ):
                        shutil.copy(source_path, proc_plastic_dir)
                    elif(corrected_label == "HID" ):
                        shutil.copy(source_path, proc_hid_dir)
                    elif(corrected_label == "Tray" ):
                        shutil.copy(source_path, proc_tray_dir)
                    elif(corrected_label == "Other" ):
                        shutil.copy(source_path, proc_other_dir)

                    # Check if we also need to move file to the GotWrong fodler:
                    if(correctP):
                        # Delete the file from the src directory
                        if os.path.exists(source_path):
                            os.remove(source_path)
                    else:
                        # move file to the GotWrong folder:
                        shutil.move(source_path, proc_mislabeled_dir)
                else:
                    shutil.move(source_path, dest_path)
            except (FileNotFoundError) as e_file:
                return

        #Close connection to the database:
        localSQL.sql_closeConnection(cnx)

        return

    elif(selected_folder == "update"):

        # Need to update modified labels stack:
        print(f"Updating modified labels from page {page_num} for user {user_id}")
        image_stack.update_stack(user_id, page_num, user_labels=modified_labels)

        # Names of table columns, will be iterated over
        column_names = ["Cotton","Plastic", "HID", "Tray", "Other"]

        print("Updating database...")

        # Search through CSV and find the lines that need to be altered:

        # get all the keys
        for key in classifier_labels:
            keys_list.append(key)

        # Next, Establish Connection to the Databse:
        cnx = localSQL.sql_connect()
        # Create a cursor
        cursor = cnx.cursor()

        for key in range(len(keys_list)):
            image_name = keys_list[key]
            corrected_label = modified_labels[keys_list[key]]

            #Iterate over the possible column (labels) in the table:
            for column in column_names:
                #Search for img name in each column to get the row:
                search_query = f"SELECT * FROM anvil_imgClassification WHERE {column} = ('{str(keys_list[key])}')"

                cursor.execute(search_query)
                result = cursor.fetchone()
                try:
                    cnx.commit()
                except (Exception) as err:
                    pass

                # RESULT RETURNED FORMAT: (row_number(id), user_id, Cotton, Plastic, Tray, HID, Other, GotWrong, PATH, JOINT) of type tuple
                if result:

                    if corrected_label == column:
                        print(f"No need to update img {image_name} found in {column} with label {corrected_label}, breaking out...")
                        break

                    # print(f"result value returned: {result}")
                    # print(f"Image name {str(keys_list[key])}")

                    # Get row number:
                    row_number = str(result[0])
                    # print(f"column value: {row_number}")
                    # Get JOINT value:
                    joint_value = str(result[-1])
                    # Set row value in previous column and GotWrong column to None:
                    update_query = "UPDATE anvil_imgClassification SET %s = NULL, GotWrong = NULL WHERE id = %s"%(column, row_number)
                    cursor.execute(update_query)
                    cnx.commit()

                    # check if joint == new_label
                    if(joint_value == corrected_label):
                        print("Joint == Correct!")
                        # Add img name to the corrected_label colum in row_number:
                        update_query = f"UPDATE anvil_imgClassification SET {str(corrected_label)} = '{str(keys_list[key])}' WHERE id = '{row_number}'"
                        cursor.execute(update_query)
                        cnx.commit()
                    else:
                        update_query =f"UPDATE anvil_imgClassification SET {str(corrected_label)} = '{str(keys_list[key])}', GotWrong = '{str(keys_list[key])}' WHERE id = '{row_number}'"
                        cursor.execute(update_query)
                        cnx.commit()

                    # print("breaking..")
                    break

                else:
                    print(f"result not found in column {column}")

    #Close connection to the database:
    cnx.close()

    return


from dataclasses import dataclass, field

from .. import docker
from .. import exceptions
from .. import utils
from ..runtime import register, RuntimePlugin

@register
@dataclass
class Docker(RuntimePlugin):
    name: str = field(init=False, default="Docker")

    def init(self, graph, outputs):
        # Parse the users docker conf file
        # and record a list of logins we know about
        self.auths = set()
        self.cfg = None
        self.graph = graph
        self.image_pull_secrets = {}
        cfg = docker.parse_config()
        if cfg:
            self.auths |= set(cfg.get("auths").keys())
            self.cfg = cfg

    def image_secrets_for(self, image):
        m = docker.parse_docker_tag(image)
        if not m or m["domain"] not in self.auths:
            return None
        r = utils.AttrAccess(
            auth=docker.auth_for(self.cfg, m["domain"]),
            key=f"{self.graph.name}-{m['domain']}",
        )
        self.image_pull_secrets[r.key] = r
        return r


from collections import defaultdict

T = int(input())

for i in range(T):
    N = int(input())
    c = list(map(int, input().split(' ')))
    g = defaultdict(list)
    for _ in range(N - 1):
        edge = list(map(int, input().split(' ')))
        g[edge[0]].append(edge[1])
        g[edge[1]].append(edge[0])

    def dfs(u, pere):
        maxi = 0
        for v in g[u]:
            if v != pere:
                maxi = max(maxi, dfs(v, u))
        return maxi + c[u - 1]

    L = []
    for v in g[1]:
        L.append(dfs(v, 1))
    L.sort()
    res = c[0]
    if len(L) > 0:
        res += L[-1]
    if len(L) > 1:
        res += L[-2]

    print(f"Case #{i + 1}: {res}")


from django.contrib import admin
from .models import Division, Farm

# Register your models here.

class DivisionAdmin(admin.ModelAdmin):
    list_display = (
        "division_name",
        "division_code",
    )

admin.site.register(Division, DivisionAdmin)
admin.site.register(Farm)


import pydantic
from pydantic import validator
import typing
from uuid import UUID, uuid4

class SchemaCustomer(pydantic.BaseModel):
    id: str
    name: str
    last_name: str
    email: pydantic.EmailStr
    age: pydantic.PositiveInt

    @validator('id', pre=True, always=True)
    def convert_id_to_str(cls, v):
        return str(v)

class SchemaCustomerCreation(pydantic.BaseModel):
    name: str
    last_name: str
    email: pydantic.EmailStr
    age: pydantic.PositiveInt

class SchemaCustomerUpdate(pydantic.BaseModel):
    name: typing.Union[str, None]
    last_name: typing.Union[str, None]
    email: typing.Union[pydantic.EmailStr, None]
    age: typing.Union[pydantic.PositiveInt, None]