import logging
import math
from typing import List, Dict, Optional

import torch
from adet.layers import DFConv2d, NaiveGroupNorm
from adet.utils.comm import compute_locations
from detectron2.layers import ShapeSpec, NaiveSyncBatchNorm
from detectron2.modeling.proposal_generator.build import PROPOSAL_GENERATOR_REGISTRY
from sylph.modeling.meta_fcos.fcos_outputs import FCOSOutputs
from sylph.modeling.utils import init_norm
from torch import nn
from torch.nn import functional as F
from detectron2.utils.file_io import PathManager
from detectron2.data import MetadataCatalog
from .head_utils import CondConv, CondConvBasic

from .head_utils import CondConvBlock, Scale

logger = logging.getLogger(__name__)

__all__ = ["MetaFCOS"]

INF = 100000000

def load_pretrained_class_conv_kernel_weights(weight_path: str):
    if not PathManager.exists(weight_path):
        logger.info(f"Weight path: {weight_path} does not exist")
        return None, None

    local_weight_path = PathManager.get_local_path(weight_path)
    pretrained_model = torch.load(
        local_weight_path, map_location=torch.device("cpu")
    )
    # find : proposal_generator.fcos_head.cls_logits.weight
    weight, bias = None, None
    for key, value in pretrained_model["model"].items():
        # print(f"key {key}, value: {value}")
        if "proposal_generator.fcos_head.cls_logits" in key:
            if "weight" in key:
                weight = value
            elif "bias" in key:
                bias = value
    return weight, bias

class ModuleListDial(nn.ModuleList):
    def __init__(self, modules=None):
        super(ModuleListDial, self).__init__(modules)
        self.cur_position = 0

    def forward(self, x):
        result = self[self.cur_position](x)
        self.cur_position += 1
        if self.cur_position >= len(self):
            self.cur_position = 0
        return result

def _build_tower_module(
    head_configs: Dict,
    in_channels: int,
    norm: str,
    num_feature_levels: int,
):
    head_tower_sequential = {}
    for head, head_config in head_configs.items():
        num_convs, use_deformable = head_config
        tower = []
        for i in range(num_convs):
            if use_deformable and i == num_convs - 1:
                conv_func = DFConv2d
            else:
                conv_func = nn.Conv2d
            tower.append(
                conv_func(
                    in_channels,
                    in_channels,
                    kernel_size=3,
                    stride=1,
                    padding=1,
                    bias=True,
                )
            )
            if norm == "GN":
                tower.append(nn.GroupNorm(32, in_channels))
            elif norm == "NaiveGN":
                tower.append(NaiveGroupNorm(32, in_channels))
            elif norm == "BN":
                tower.append(
                    ModuleListDial(
                        [nn.BatchNorm2d(in_channels)
                         for _ in range(num_feature_levels)]
                    )
                )
            elif norm == "SyncBN":
                tower.append(
                    ModuleListDial(
                        [
                            NaiveSyncBatchNorm(in_channels)
                            for _ in range(num_feature_levels)
                        ]
                    )
                )
            tower.append(nn.ReLU())
        # pop out the last layer's ReLU in cls_tower
        # if use_l2_norm_conv2d and head == "cls":
        #     tower.pop()
        head_tower_sequential[head] = nn.Sequential(*tower)
    return head_tower_sequential

class BaseFewShotDetectorHead(nn.Module):
    def __init__(self, cfg, input_shape: List[ShapeSpec]):
        super().__init__()
        self.cfg = cfg
        self.input_shape = input_shape
        # get submodule configs
        self._init_base_detector_config()
        # get meta_learning
        self.episodic_learning = cfg.MODEL.META_LEARN.EPISODIC_LEARNING
        if self.episodic_learning:
            self._init_code_generator_config(
                name=cfg.MODEL.META_LEARN.CODE_GENERATOR.NAME
            )
            self._init_merge_module_config()

    def _init_base_detector_config(self, name: Optional[str] = None):
        pass

    def _init_code_generator_config(self, name: Optional[str] = None):
        pass

    def _init_merge_module_config(self, name: Optional[str] = None):
        pass

    #  forward
    # def forward_base_train(self, x, top_module=None, yield_bbox_towers=False):
    #     raise NotImplementedError()

    # def forward(self, x: List[Any],top_module=None,yield_bbox_towers: bool =False, support_set_class_codes: Dict[torch.Tensor]=None):
    #     raise NotImplementedError()

# Head outputs calls few-shot predictor head
@PROPOSAL_GENERATOR_REGISTRY.register()
class MetaFCOS(nn.Module):  # TODO: convert it to Base Few Shot Detector Head
    """
    Implement Meta FCOS.
    FCOS: (https://arxiv.org/abs/1904.01355).
    """

    def __init__(self, cfg, input_shape: Dict[str, ShapeSpec]):
        """
        DetectorHead (forward)
        Detector outputs (losses or proposals)
        """
        super().__init__()
        self.cfg = cfg
        self.episodic_learning = cfg.MODEL.META_LEARN.EPISODIC_LEARNING
        self.in_features = cfg.MODEL.FCOS.IN_FEATURES
        self.fpn_strides = cfg.MODEL.FCOS.FPN_STRIDES
        self.yield_proposal = cfg.MODEL.FCOS.YIELD_PROPOSAL

        # TODO: test more modualized head
        self.fcos_head = MetaFCOSHead(
            cfg, [input_shape[f] for f in self.in_features])
        self.in_channels_to_top_module = self.fcos_head.in_channels_to_top_module

        self.fcos_outputs = FCOSOutputs(cfg)

    def forward(
        self,
        images,
        features,
        gt_instances=None,
        top_module=None,
        support_set_per_class_code=None,
        support_set_targets=None,
    ):
        """
        Arguments:
            images (list[Tensor] or ImageList): query set images to be processed
            features: query set features.
            targets (list[BoxList]): ground-truth boxes present in the image (optional)

        Returns:
            result (list[BoxList] or dict[Tensor]): the output from the model.
                During training, it returns a dict[Tensor] which contains the losses.
                During testing, it returns list[BoxList] contains additional fields
                like `scores`, `labels` and `mask` (for Mask R-CNN models).

        """

        features = [features[f] for f in self.in_features]
        locations = self.compute_locations(features)
        logits_pred, reg_pred, ctrness_pred, iou_pred, top_feats, bbox_towers = self.fcos_head(
            features, top_module, self.yield_proposal, support_set_per_class_code
        )

        results = {}
        if self.yield_proposal:
            results["features"] = {f: b for f, b in zip(
                self.in_features, bbox_towers)}

        if self.training:
            # TODO(neogong): This need to be optimized. Find out a way to copy params after loading the checkpoint.
            if (
                self.episodic_learning
                and self.cfg.MODEL.META_LEARN.CODE_GENERATOR.DISTILLATION_LOSS_WEIGHT
            ):
                cls_logits_kernel = (
                    self.fcos_head.cls_logits.weight.data.clone().detach(),
                    self.fcos_head.cls_logits.bias.data.clone().detach(),
                )
            else:
                cls_logits_kernel = None
            # If support_set_targets is not None, it goes to meta_loss, else, pretraining loss

            results, losses = self.fcos_outputs.losses(
                logits_pred,
                reg_pred,
                ctrness_pred,
                iou_pred,
                locations,
                gt_instances,
                top_feats,
                support_set_targets=support_set_targets,
                support_set_per_class_code=support_set_per_class_code,
                cls_logits_kernel=cls_logits_kernel,
            )

            if self.yield_proposal:
                with torch.no_grad():
                    results["proposals"] = self.fcos_outputs.predict_proposals(
                        logits_pred,
                        reg_pred,
                        ctrness_pred,
                        iou_pred,
                        locations,
                        images.image_sizes,
                        top_feats,
                    )
            return results, losses
        else:
            results = self.fcos_outputs.predict_proposals(
                logits_pred,
                reg_pred,
                ctrness_pred,
                iou_pred,
                locations,
                images.image_sizes,
                top_feats,
            )

            return results, {}

    def compute_locations(self, features):
        """
        For each location(x,y)on the feature map Fi, we can map it back onto the input image as(⌊s/2⌋+xs,⌊s/2⌋+ys)
        locations: it has dimension [h*w, 2]
        """
        locations = []
        for level, feature in enumerate(features):
            h, w = feature.size()[-2:]
            locations_per_level = compute_locations(
                h, w, self.fpn_strides[level], feature.device
            )
            locations.append(locations_per_level)
        return locations

class MetaFCOSHead(BaseFewShotDetectorHead):
    """
    Define Meta-FCOS heads,  it initalize the base detector for training on base detector
    and it also prepare "class-conditional conv" for the meta-learning stage.
    """

    def __init__(self, cfg, input_shape: List[ShapeSpec]):
        super().__init__(cfg, input_shape)

    def _evaluate_with_base_class_codes(self, eval_dataset_name: str):
        if "all" not in eval_dataset_name:
            print(
                f"tried to eval with base class codes, but the dataset split is not 'all'")
            return None, None
        if self.base_weight is not None and self.base_bias is not None:
            print(f"evaluating with base weights.")
            base_dataset_name = self.cfg.DATASETS.BASE_CLASSES_SPLIT
            novel_dataset_name = self.cfg.DATASETS.NOVEL_CLASSES_SPLIT
            if base_dataset_name == '' or novel_dataset_name == '':
                print(f"evaluating with base weights.")
                return None, None
            assert base_dataset_name != '', "base data set name is not provided"
            assert novel_dataset_name != '', "novel data set name is not provided"
            # base_classes = MetadataCatalog.get(base_dataset_name).thing_classes
            base_dataset_id_to_continous_id = MetadataCatalog.get(
                base_dataset_name).thing_dataset_id_to_contiguous_id
            # training data classes
            novel_dataset_id_to_continous_id = MetadataCatalog.get(
                novel_dataset_name).thing_dataset_id_to_contiguous_id
            # combine base class codes with current class_codes

            current_dataset_id_to_continous_id = MetadataCatalog.get(
                eval_dataset_name).thing_dataset_id_to_contiguous_id
            N = len(current_dataset_id_to_continous_id)
            assert self.base_weight.size(1) == self.cls_logits.weight.size(1)
            assert self.base_weight.size(2) == self.cls_logits.weight.size(2)
            assert self.base_weight.size(3) == self.cls_logits.weight.size(3)
            new_weights = torch.zeros(N, self.base_weight.size(
                1), self.base_weight.size(2), self.base_weight.size(3))
            new_bias = torch.zeros(N)
            # new_weights = self.cls_logits.weight.data
            # new_bias = self.cls_logits.bias.data
            # copy base classes
            for base_dataset_id, base_index in base_dataset_id_to_continous_id.items():
                if base_dataset_id in current_dataset_id_to_continous_id.keys():
                    current_index = current_dataset_id_to_continous_id[base_dataset_id]
                    new_weights[current_index] = self.base_weight[base_index]
                    new_bias[current_index] = self.base_bias[base_index]
            # copy novel classes, save the novels,
            for novel_dataset_id, novel_index in novel_dataset_id_to_continous_id.items():
                if novel_dataset_id in current_dataset_id_to_continous_id.keys():
                    current_index = current_dataset_id_to_continous_id[novel_dataset_id]
                    new_weights[current_index] = self.cls_logits.weight[novel_index]
                    new_bias[current_index] = self.cls_logits.bias[novel_index]
            new_weights = new_weights.to(self.cls_logits.weight.device)
            new_bias = new_bias.to(self.cls_logits.weight.device)
            print(f"final weights: {new_weights.size()}")
        return new_weights, new_bias

    def _preload_cls_logits_weights(self):
        use_pretrained_base_cls_logits = self.cfg.MODEL.TFA.USE_PRETRAINED_BASE_CLS_LOGITS
        # if not self.cfg.MODEL.TFA.FINETINE:
        #     return
        if not use_pretrained_base_cls_logits:
            return
        # weight_path = self.cfg.MODEL.WEIGHTS
        # print(f"load class logits from weights path: {weight_path}")
        # base_weight, base_bias = load_pretrained_class_conv_kernel_weights(weight_path)
        if self.base_weight is not None and self.base_bias is not None:
            base_dataset_name = self.cfg.DATASETS.BASE_CLASSES_SPLIT
            novel_dataset_name = self.cfg.DATASETS.NOVEL_CLASSES_SPLIT
            print(f"Loading pretrained weights on cls_logits")
            if base_dataset_name == '' or novel_dataset_name == '':
                return
            assert base_dataset_name != '', "base data set name is not provided"
            assert novel_dataset_name != '', "novel data set name is not provided"
            # base_classes = MetadataCatalog.get(base_dataset_name).thing_classes
            base_dataset_id_to_continous_id = MetadataCatalog.get(
                base_dataset_name).thing_dataset_id_to_contiguous_id
            # replace
            new_weights = self.cls_logits.weight.data
            new_bias = self.cls_logits.bias.data
            current_dataset_id_to_continous_id = MetadataCatalog.get(
                self.cfg.DATASETS.TRAIN[0]).thing_dataset_id_to_contiguous_id
            # replace base dataset id to current weights
            for base_dataset_id, base_index in base_dataset_id_to_continous_id.items():
                if base_dataset_id in current_dataset_id_to_continous_id.keys():
                    current_index = current_dataset_id_to_continous_id[base_dataset_id]
                    new_weights[current_index] = self.base_weight[base_index]
                    new_bias[current_index] = self.base_bias[base_index]
            with torch.no_grad():
                self.cls_logits.weight = nn.Parameter(new_weights)
                self.cls_logits.bias = nn.Parameter(new_bias)
                print(
                    f"done Loading pretrained weights on cls_logits, number of classes: {self.cls_logits.weight.data.size(0)}")
        return

    def _init_base_detector_config(self, name: Optional[str] = None):
        """
        Extract useful FCOS config
        """
        self.num_classes = self.cfg.MODEL.FCOS.NUM_CLASSES
        self.fpn_strides = self.cfg.MODEL.FCOS.FPN_STRIDES
        head_configs = {
            "cls": (
                self.cfg.MODEL.FCOS.NUM_CLS_CONVS,
                self.cfg.MODEL.FCOS.USE_DEFORMABLE,
            ),
            "bbox": (
                self.cfg.MODEL.FCOS.NUM_BOX_CONVS,
                self.cfg.MODEL.FCOS.USE_DEFORMABLE,
            ),
            "share": (self.cfg.MODEL.FCOS.NUM_SHARE_CONVS, False),
        }
        norm = None if self.cfg.MODEL.FCOS.NORM == "none" else self.cfg.MODEL.FCOS.NORM
        self.num_levels = len(self.input_shape)
        in_channels = [s.channels for s in self.input_shape]
        assert len(set(in_channels)
                   ) == 1, "Each level must have the same channel!"
        in_channels = in_channels[0]

        self.in_channels_to_top_module = in_channels
        # Build all layers befor the predictor layer
        head_tower_sequential = _build_tower_module(
            head_configs, in_channels, norm, self.num_levels,
        )
        for head in head_configs.keys():
            self.add_module(f"{head}_tower", head_tower_sequential[head])
        # The original clssification head
        self.use_l2_norm_conv2d = self.cfg.MODEL.FCOS.L2_NORM_CLS_WEIGHT
        if self.use_l2_norm_conv2d:  # will not be used
            from .head_utils import CosineSimilarityConv2d

            self.cls_logits = CosineSimilarityConv2d(
                in_channels, out_channel=self.num_classes
            )
        else:
            self.cls_logits = nn.Conv2d(
                in_channels,
                self.num_classes,
                kernel_size=self.cfg.MODEL.FCOS.CLS_LOGITS_KERNEL_SIZE,
                stride=1,
                padding=self.cfg.MODEL.FCOS.CLS_LOGITS_KERNEL_SIZE // 2,
            )  # default is 3 x 3

        self.bbox_pred = nn.Conv2d(
            in_channels, 4, kernel_size=3, stride=1, padding=1)
        self.ctrness = nn.Conv2d(
            in_channels, 1, kernel_size=3, stride=1, padding=1)
        self.iou_overlap = nn.Conv2d(
            in_channels, 1, kernel_size=3, stride=1, padding=1)

        if self.cfg.MODEL.FCOS.USE_SCALE:
            self.scales = nn.ModuleList(
                [Scale(init_value=1.0) for _ in range(self.num_levels)]
            )
        else:
            self.scales = None

        # Module initialization
        for modules in [
            self.cls_tower,
            self.bbox_tower,
            self.share_tower,
            self.cls_logits,
            self.bbox_pred,
            self.ctrness,
            self.iou_overlap
        ]:
            if modules is None:
                continue
            for layer in modules.modules():
                if isinstance(layer, nn.Conv2d):
                    print(f"init {layer} in {modules.modules()}")
                    torch.nn.init.normal_(layer.weight, std=0.01)
                    if layer.bias is not None:
                        torch.nn.init.constant_(layer.bias, 0)

        # initialize the bias for focal loss
        prior_prob = self.cfg.MODEL.FCOS.PRIOR_PROB
        bias_value = -math.log((1 - prior_prob) / prior_prob)
        if not self.use_l2_norm_conv2d:
            torch.nn.init.constant_(self.cls_logits.bias, bias_value)
        else:
            torch.nn.init.constant_(self.cls_logits.cls_layer.bias, bias_value)

        # TFA: load pretrained weights
            # print(f"load class logits from weights path: {weight_path}")
        # Always save it
        weight_path = self.cfg.MODEL.WEIGHTS
        print(f"weight path: {weight_path}")
        self.base_weight, self.base_bias = load_pretrained_class_conv_kernel_weights(
            weight_path)
        self._preload_cls_logits_weights()
        # save
        self.evaluate_with_base_class_codes = self.cfg.MODEL.TFA.EVAL_WITH_PRETRAINED_BASE_CLS_LOGITS
        print(
            f"eval with clss codes path: {self.evaluate_with_base_class_codes}")
        if self.evaluate_with_base_class_codes:
            self.eval_conv = CondConvBasic()

    def _init_code_generator_config(self, name: Optional[str]):
        """
        Will only be called while episodic learning is set to True
        """
        # all code generator has some basic configs

        if name == "CodeGenerator":
            # self.cls_logits will not be updated in episodic_learning.
            self.class_code_channel = (
                self.cfg.MODEL.META_LEARN.CODE_GENERATOR.OUT_CHANNEL
            )
            for param in self.cls_logits.parameters():
                param.requires_grad = False
            k_s = self.cfg.MODEL.META_LEARN.CODE_GENERATOR.CLS_LAYER[2]
            padding_size = k_s // 2
            # conditional classification head (takes in weight and bias)

            # self.cond_cls_logits = CondConvBlock(
            #     padding=padding_size, weight_len=self.class_code_channel
            # )

            self.cond_cls_logits = CondConvBasic(
                padding=padding_size,
                use_bias=self.cfg.MODEL.META_LEARN.CODE_GENERATOR.USE_BIAS
            )
            # self.score_scale = Scale(init_value=1.0)

            # CondConv(
            #     padding=padding_size,
            #     scale_score=True,
            #     l2_norm_weight=self.use_l2_norm_conv2d,
            #     scale_value=1.0,
            # )

        elif name == "ROIEncoder":
            self.class_code_channel = (
                self.cfg.MODEL.META_LEARN.CODE_GENERATOR.HEAD.OUTPUT_DIM
            )
            k_s = 1
            padding_size = 0
            # conditional classification head (takes in weight and bias)
            self.cond_cls_logits = CondConvBlock(
                padding=padding_size, weight_len=self.class_code_channel
            )
        else:
            raise NotImplementedError(f"{name} is not implemented")

    # def _build_base_detector(self, *args, **kwargs):
    #     # Build all layers befor the predictor layer
    #     self._build_tower_module(head_configs, in_channels, norm)

    # def _build_code_generator(self, *args, **kwargs):
    #     raise NotImplementedError()

    # def _build_merge_module(self, *args, **kwargs):
    #     raise NotImplementedError()

    #  forward
    def forward_base_train(self, x, top_module=None, yield_bbox_towers=False):
        logits = []
        bbox_reg = []
        ctrness = []
        ious = []
        top_feats = []
        bbox_towers = []
        for level, feature in enumerate(x):
            feature = self.share_tower(feature)
            cls_tower = self.cls_tower(feature)
            bbox_tower = self.bbox_tower(feature)
            if yield_bbox_towers:
                bbox_towers.append(bbox_tower)
            # TODO: only support the first evaluation dataset and it has to be split "all"
            # slow now because every single time, it has to redo the loading.
            if not self.training and self.evaluate_with_base_class_codes:
                new_weight, new_bias = self._evaluate_with_base_class_codes(
                    self.cfg.DATASETS.TEST[0])
                if new_weight is not None:
                    logger.info(
                        f"evaluating using new weight and new bias, in total {new_weight.size(0)}")
                    logits.append(self.eval_conv(
                        feature=cls_tower, weight=new_weight, bias=new_bias))
                else:
                    logits.append(self.cls_logits(cls_tower))
            else:
                logits.append(self.cls_logits(cls_tower))
            reg = self.bbox_pred(bbox_tower)
            ctrness.append(self.ctrness(bbox_tower))
            ious.append(self.iou_overlap(bbox_tower))
            if self.scales is not None:
                reg = self.scales[level](reg)
            # Note that we use relu, as in the improved FCOS, instead of exp.
            bbox_reg.append(F.relu(reg))
            if top_module is not None:
                top_feats.append(top_module(bbox_tower))
        return logits, bbox_reg, ctrness, ious, top_feats, bbox_towers

    def forward(
        self,
        x,
        top_module=None,
        yield_bbox_towers=False,
        support_set_per_class_code=None,
    ):
        """
        support_set_per_class_code a tensor of dimension t*c, 256, 1, 1
        """
        if support_set_per_class_code is None:
            return self.forward_base_train(x, top_module, yield_bbox_towers)
        logits = []
        bbox_reg = []
        ctrness = []
        ious = []

        top_feats = []
        bbox_towers = []

        support_set_per_class_conv = support_set_per_class_code["cls_conv"]
        S = support_set_per_class_conv.size(0)
        bias = support_set_per_class_code["cls_bias"]
        box_weight = None
        # replace the base class codes
        #print(f"training stage: {self.training}, self.evaluate_with_base_class_codes: {self.evaluate_with_base_class_codes}")
        if (not self.training) and self.evaluate_with_base_class_codes:
            #print(f"replacing {self.training}")
            new_weights, new_bias = self._evaluate_with_base_class_codes(
                self.cfg.DATASETS.TEST[0])
            if new_weights is not None:
                print(
                    f"evaluating using new weight and new bias, in total {new_weights.size(0)}")
                support_set_per_class_conv = new_weights
                bias = new_bias

        if "loc_conv" in support_set_per_class_code:
            box_weight = support_set_per_class_code["loc_conv"]
            S, C, H, W = box_weight.size()
            # assert H * W == 4
            box_weight = box_weight.view(S, int(C // 4), 4, 1, 1)
            box_weight = box_weight.permute(0, 2, 1, 3, 4)  # S, 4, C, 1, 1

        for level, feature in enumerate(x):
            feature = self.share_tower(feature)
            cls_tower = self.cls_tower(feature)
            bbox_tower = self.bbox_tower(feature)
            if yield_bbox_towers:
                bbox_towers.append(bbox_tower)

            # logit for pretraining and meta-finetuning differs only at dimension 1, it has a size t*c
            # support_set_per_class_code CONV cls_tower
            if self.episodic_learning and support_set_per_class_code is not None:
                logit = self.cond_cls_logits(
                    cls_tower, support_set_per_class_conv, bias
                )
                # logit = self.score_scale(logit)
            else:
                assert self.cls_logits is not None, "cls_logits is None"
                logit = self.cls_logits(cls_tower)

            # location reg
            # if self.box_on and box_weight is not None:
            #     reg_all_class = []
            #     for i in range(S):
            #         reg = self.bbox_pred(bbox_tower, box_weight[i])
            #         if self.scales is not None:
            #             reg = self.scales[level](reg)
            #         reg = F.relu(reg)
            #         reg_all_class.append(reg)
            #     reg_all_class = torch.cat(reg_all_class, dim=1)
            #     # Note that we use relu, as in the improved FCOS, instead of exp.
            #     bbox_reg.append(reg_all_class)  # a list of dim b, c X4, h, w
            # else:
            reg = self.bbox_pred(bbox_tower)
            if self.scales is not None:
                reg = self.scales[level](reg)
            # Note that we use relu, as in the improved FCOS, instead of exp.
            bbox_reg.append(F.relu(reg))  # a list of dim b, 4, h, w

            logits.append(logit)
            ctrness.append(self.ctrness(bbox_tower))
            ious.append(self.iou_overlap(bbox_tower))
            if top_module is not None:
                top_feats.append(top_module(bbox_tower))
        return logits, bbox_reg, ctrness, ious, top_feats, bbox_towers

class FCOSHead(nn.Module):
    def __init__(self, cfg, input_shape: List[ShapeSpec]):
        """
        Arguments:
            in_channels (int): number of channels of the input feature
        """
        super().__init__()
        self.num_classes = cfg.MODEL.FCOS.NUM_CLASSES
        self.fpn_strides = cfg.MODEL.FCOS.FPN_STRIDES
        head_configs = {
            "cls": (cfg.MODEL.FCOS.NUM_CLS_CONVS, cfg.MODEL.FCOS.USE_DEFORMABLE),
            "bbox": (cfg.MODEL.FCOS.NUM_BOX_CONVS, cfg.MODEL.FCOS.USE_DEFORMABLE),
            "share": (cfg.MODEL.FCOS.NUM_SHARE_CONVS, False),
        }
        self.box_on = cfg.MODEL.META_LEARN.CODE_GENERATOR.BOX_ON
        logger.info(f"box_on: {self.box_on}")

        norm = None if cfg.MODEL.FCOS.NORM == "none" else cfg.MODEL.FCOS.NORM
        self.num_levels = len(input_shape)

        in_channels = [s.channels for s in input_shape]
        assert len(set(in_channels)
                   ) == 1, "Each level must have the same channel!"
        in_channels = in_channels[0]

        self.in_channels_to_top_module = in_channels

        # Build all layers befor the predictor layer
        self._build_tower_module(head_configs, in_channels, norm)
        # Join predictor head with support set, Only replace class right now, no bias right now
        self.episodic_learning = cfg.MODEL.META_LEARN.EPISODIC_LEARNING

        # The original classification head will be loaded in meta-learning stage
        self.cls_logits = nn.Conv2d(
            in_channels,
            self.num_classes,
            kernel_size=cfg.MODEL.FCOS.CLS_LOGITS_KERNEL_SIZE,
            stride=1,
            padding=cfg.MODEL.FCOS.CLS_LOGITS_KERNEL_SIZE // 2,
        )  # default is 3 x 3
        if self.episodic_learning:
            self.class_code_channel = cfg.MODEL.META_LEARN.CODE_GENERATOR.OUT_CHANNEL
            # self.cls_logits will not be updated in episodic_learning.
            for param in self.cls_logits.parameters():
                param.requires_grad = False
            k_s = cfg.MODEL.META_LEARN.CODE_GENERATOR.CLS_LAYER[2]
            padding_size = k_s // 2
            # conditional classification head
            self.cond_cls_logits = CondConvBlock(
                padding=padding_size, weight_len=self.class_code_channel
            )

        if self.episodic_learning and self.box_on:
            self.bbox_pred = CondConvBlock()
        else:
            self.bbox_pred = nn.Conv2d(
                in_channels, 4, kernel_size=3, stride=1, padding=1
            )

        self.ctrness = nn.Conv2d(
            in_channels, 1, kernel_size=3, stride=1, padding=1)

        if cfg.MODEL.FCOS.USE_SCALE:
            self.scales = nn.ModuleList(
                [Scale(init_value=1.0) for _ in range(self.num_levels)]
            )
        else:
            self.scales = None

        # Module initialization
        for modules in [
            self.cls_tower,
            self.bbox_tower,
            self.share_tower,
            self.cls_logits,
            self.bbox_pred,
            self.ctrness,
        ]:
            if modules is None:
                continue
            for layer in modules.modules():
                if isinstance(layer, nn.Conv2d):
                    torch.nn.init.normal_(layer.weight, std=0.01)
                    if layer.bias is not None:
                        torch.nn.init.constant_(layer.bias, 0)

                if cfg.MODEL.META_LEARN.CODE_GENERATOR.INIT_NORM_LAYER:
                    init_norm(layer)

        # initialize the bias for focal loss
        prior_prob = cfg.MODEL.FCOS.PRIOR_PROB
        bias_value = -math.log((1 - prior_prob) / prior_prob)
        torch.nn.init.constant_(self.cls_logits.bias, bias_value)

    def _build_tower_module(self, head_configs: Dict, in_channels: int, norm: str):
        for head, head_config in head_configs.items():
            num_convs, use_deformable = head_config
            tower = []
            for i in range(num_convs):
                if use_deformable and i == num_convs - 1:
                    conv_func = DFConv2d
                else:
                    conv_func = nn.Conv2d
                tower.append(
                    conv_func(
                        in_channels,
                        in_channels,
                        kernel_size=3,
                        stride=1,
                        padding=1,
                        bias=True,
                    )
                )
                if norm == "GN":
                    tower.append(nn.GroupNorm(32, in_channels))
                elif norm == "NaiveGN":
                    tower.append(NaiveGroupNorm(32, in_channels))
                elif norm == "BN":
                    tower.append(
                        ModuleListDial(
                            [
                                nn.BatchNorm2d(in_channels)
                                for _ in range(self.num_levels)
                            ]
                        )
                    )
                elif norm == "SyncBN":
                    tower.append(
                        ModuleListDial(
                            [
                                NaiveSyncBatchNorm(in_channels)
                                for _ in range(self.num_levels)
                            ]
                        )
                    )
                tower.append(nn.ReLU())

            self.add_module(f"{head}_tower", nn.Sequential(*tower))

    def forward_base_train(self, x, top_module=None, yield_bbox_towers=False):
        logits = []
        bbox_reg = []
        ctrness = []
        top_feats = []
        bbox_towers = []
        for level, feature in enumerate(x):
            feature = self.share_tower(feature)
            cls_tower = self.cls_tower(feature)
            bbox_tower = self.bbox_tower(feature)
            if yield_bbox_towers:
                bbox_towers.append(bbox_tower)

            logits.append(self.cls_logits(cls_tower))
            ctrness.append(self.ctrness(bbox_tower))
            reg = self.bbox_pred(bbox_tower)
            if self.scales is not None:
                reg = self.scales[level](reg)
            # Note that we use relu, as in the improved FCOS, instead of exp.
            bbox_reg.append(F.relu(reg))
            if top_module is not None:
                top_feats.append(top_module(bbox_tower))
        return logits, bbox_reg, ctrness, top_feats, bbox_towers

    def forward(
        self,
        x,
        top_module=None,
        yield_bbox_towers=False,
        support_set_per_class_code=None,
    ):
        """
        support_set_per_class_code a tensor of dimension t*c, 256, 1, 1
        """
        if support_set_per_class_code is None:
            return self.forward_base_train(x, top_module, yield_bbox_towers)
        logits = []
        bbox_reg = []
        ctrness = []
        top_feats = []
        bbox_towers = []

        support_set_per_class_conv = support_set_per_class_code["cls_conv"]
        S = support_set_per_class_conv.size(0)
        bias = support_set_per_class_code["cls_bias"]
        box_weight = None

        if "loc_conv" in support_set_per_class_code:
            box_weight = support_set_per_class_code["loc_conv"]
            S, C, H, W = box_weight.size()
            # assert H * W == 4
            box_weight = box_weight.view(S, int(C // 4), 4, 1, 1)
            box_weight = box_weight.permute(0, 2, 1, 3, 4)  # S, 4, C, 1, 1

        for level, feature in enumerate(x):
            feature = self.share_tower(feature)
            cls_tower = self.cls_tower(feature)
            bbox_tower = self.bbox_tower(feature)
            if yield_bbox_towers:
                bbox_towers.append(bbox_tower)

            # logit for pretraining and meta-finetuning differs only at dimension 1, it has a size t*c
            # support_set_per_class_code CONV cls_tower
            if self.episodic_learning and support_set_per_class_code is not None:
                logit = self.cond_cls_logits(
                    cls_tower, support_set_per_class_conv, bias
                )
                # logit = self.score_scale
            else:
                assert self.cls_logits is not None, "cls_logits is None"
                logit = self.cls_logits(cls_tower)

            # location reg
            if self.box_on and box_weight is not None:
                reg_all_class = []
                for i in range(S):
                    reg = self.bbox_pred(bbox_tower, box_weight[i])
                    if self.scales is not None:
                        reg = self.scales[level](reg)
                    reg = F.relu(reg)
                    reg_all_class.append(reg)
                reg_all_class = torch.cat(reg_all_class, dim=1)
                # Note that we use relu, as in the improved FCOS, instead of exp.
                bbox_reg.append(reg_all_class)  # a list of dim b, c X4, h, w
            else:
                reg = self.bbox_pred(bbox_tower)
                if self.scales is not None:
                    reg = self.scales[level](reg)
                # Note that we use relu, as in the improved FCOS, instead of exp.
                bbox_reg.append(F.relu(reg))  # a list of dim b, 4, h, w

            logits.append(logit)
            ctrness.append(self.ctrness(bbox_tower))
            if top_module is not None:
                top_feats.append(top_module(bbox_tower))
        return logits, bbox_reg, ctrness, top_feats, bbox_towers


import string
import nltk
from bs4 import BeautifulSoup
from nltk.tokenize import word_tokenize
from nltk.stem import WordNetLemmatizer
from nltk.corpus import stopwords
from sklearn.feature_extraction.text import TfidfVectorizer
import pandas as pd

nltk.download('wordnet')
nltk.download('omw-1.4')
nltk.download('stopwords')
nltk.download('averaged_perceptron_tagger')

lem = WordNetLemmatizer()
stop_words = stopwords.words('english')
punc = list(string.punctuation)

file_path = 'news.xml'

with open(file_path, 'r', encoding='utf-8') as file:

    soup = BeautifulSoup(file, 'xml')

    heads = soup.find_all('value', {'name': 'head'})
    contents = soup.find_all('value', {'name': 'text'})

    # Getting the news text:

    news_text = [sorted(word_tokenize(contents[i].text.lower()), reverse=True)for i in range(len(contents))]

    # lemmatization:

    lem_news = [[lem.lemmatize(word) for word in list_words] for list_words in news_text]

    # POS for individual word in each news story:

    pos_news = [[nltk.pos_tag([word])[0] for word in list_words] for list_words in lem_news]

    # Filtering for nouns only:

    nouns_news = [[tup[0] for tup in tup_list if tup[1] == 'NN'] for tup_list in pos_news]

    # Cleaning the nouns list into sentence: Last Stage

    sentence_list = [[' '.join(word)] for word in nouns_news]

    dataset = [sent for element in sentence_list for sent in element]

    # Count the TF-IDF metric for each word in all stories, i.e. apply it to the whole collection of news documents.

    vectorizer = TfidfVectorizer()

    tfidf_matrix = vectorizer.fit_transform(dataset)

    terms = vectorizer.get_feature_names()

    # The following code was copied from another solution: I have no idea what's going on here, but it gives the result.
    # Again, because there is no topic in the Academy explaining how the get the most frequent words properly based on the score of each term

    for i in range(len(heads)):
        print(f'{heads[i].text}:')
        df = pd.DataFrame(tfidf_matrix[i].T.todense(), index=terms, columns=["tfidf"])
        df.index.name = 'word'
        df = df.sort_values(['tfidf', 'word'], ascending=[False, False]).head(5)
        print(' '.join(list(df.index)), '\n')


# -*- coding: utf-8 -*-

import numpy as np
from sklearn.datasets import load_iris
import matplotlib.pyplot as plot

"""
Ejercicio 1
"""

matriz = load_iris()

a=matriz.data
b=matriz.target_names

x1=a[0:49,-2:]
x2=a[50:99,-2:]
x3=a[100:149,-2:]

plot.scatter(x1[:,0],x1[:,1],c=[[1,0,0]],label=b[0])
plot.scatter(x2[:,0],x2[:,1],c=[[0,1,0]],label=b[1])
plot.scatter(x3[:,0],x3[:,1],c=[[0,0,1]],label=b[2])
plot.title('Iris database')
plot.legend(loc=2)
plot.show()

input('Pulse enter para pasar al ejercicio 2')
"""
Ejercicio 2
"""

x12=np.random.permutation(x1)
x22=np.random.permutation(x2)
x32=np.random.permutation(x3)
training=np.array(np.concatenate((x12[:40,:],x22[:40,:],x32[:40,:]),axis=0,out=None))
test=np.array(np.concatenate((x12[-10:,:],x22[-10:,:],x32[-10:,:]),axis=0,out=None))

plot.scatter(training[:,0],training[:,1],c=[[1,1,0]],label="training")
plot.scatter(test[:,0],test[:,1],c=[[0,1,1]],label="test")
plot.legend(loc=2)
plot.show()
print("Tamaño training: ",training.shape[0], ". Tamaño test: ",test.shape[0])

input('Pulse enter para pasar al ejercicio 3')
"""
Ejercicio 3
"""

n=np.linspace(0,2*np.pi,100)
seno=np.sin(n)
coseno=np.cos(n)
suma=np.sin(n)+np.cos(n)

plot.plot(n,seno,"k--",label="Sin(x)")
plot.plot(n,coseno,"b--",label="Cos(x)")
plot.plot(n,suma,"r--",label="Sin(x)+Cos(x)")
plot.legend(loc=2)
plot.show()
#n,a b-- ,r r--


import argparse
import logging

from elixir import feedstock

__version__ = '0.0.1'

def _config_logging(logging_level='INFO', logging_file=None):

    allowed_levels = {
        'DEBUG': logging.DEBUG,
        'INFO': logging.INFO,
        'WARNING': logging.WARNING,
        'ERROR': logging.ERROR,
        'CRITICAL': logging.CRITICAL
    }

    logging_config = {
        'level': allowed_levels.get(logging_level, 'INFO'),
        'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    }

    if logging_file:
        logging_config['filename'] = logging_file

    logging.basicConfig(**logging_config)

def main(pid, source_dir='.', logging_level='Info', logging_file=None, deposit_dir=None):

    _config_logging(logging_level, logging_file)

    logging.info('Starting to pack a document')

    if feedstock.is_valid_pid(pid):
        xml = feedstock.loadXML(pid)
        raw_data = feedstock.load_rawdata(pid)
        article = feedstock.Article(pid, xml, raw_data, source_dir, deposit_dir)

        article.wrap_document()

def argp():
    parser = argparse.ArgumentParser(
        description="Create a article package from the legacy data")

    parser.add_argument(
        '--pid',
        '-p',
        default=None,
        help='Document ID, must be the PID number'
    )

    parser.add_argument(
        '--source_dir',
        '-s',
        default='.',
        help='Source directory where the pdf, images and html\'s cold be fetched'
    )

    parser.add_argument(
        '--logging_file',
        '-o',
        default=None,
        help='File to record all logging data, if None the log will be send to the standard out'
    )

    parser.add_argument(
        '--logging_level',
        '-l',
        default='INFO',
        choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
        help='File to record all logging data, if None the log will be send to the standard out'
    )

    parser.add_argument(
        '--deposit_dir',
        '-d',
        default=None,
        help='Directory to receive the packages'
    )

    args = parser.parse_args()

    main(
        args.pid,
        source_dir=args.source_dir,
        logging_level=args.logging_level,
        logging_file=args.logging_file,
        deposit_dir=args.deposit_dir
    )

if __name__ == "__main__":

    argp()


import csv
import os
import sys

class Movie:

    def __inti__(self, movie_id, mov_tittle, zhanra_1, zhanra_2, zhanra_3, zhanra_4, movie_rating, user_id):
        self.id = movie_id
        self.tittle = tittle
        self.zhanra_1 = zhanra_1
        self.zhanra_2 = zhanra_2
        self.zhanra_3 = zhanra_3
        self.zhanra_4 = zhanra_4
        self.movie_rating = movie_rating
        self.user_id = user_id

    def __str__(self):

        return ('{}: {}'.format(self.id, self.tittle))

class User:

    def __inti__(self, user_id, user_age, user_gender, user_occup, user_zip, user_mov_amt, user_rating, movie_id):

        self.user_id = user_id
        self.user_age = user_age
        self.user_gender = user_gender
        self.user_occup = user_occup
        self.user_zip = user_zip
        self.user_mov_amt = user_mov_amt
        self.user_rating = user_rating
        self.movie_id = movie_id

class MovieRatings:
    def __init__(self, user_id, movie_id, rating):
        self.user_id = user_id
        self.movie_id = movie_id
        self.rating = rating

    #def find_movie(self, dict_of_ratings):
    #    pass
#-----------------------------------------------

def print_there(x, y, text):
     sys.stdout.write("\x1b7\x1b[%d;%df%s\x1b8" % (x, y, text))
     sys.stdout.flush()

#-------------------------------------------

def catch_invalid_input(user_input_1, user_input_2):

    if user_input_1 != 0:
        if not user_input_1.isdigit() or int(user_input_1) > 4:
            input("Invalid input. Press enter to continue!... " )
            return True
        else:
            return False

    if user_input_2.isdigit():
        return user_input_2
    else:
        print('\n')
        input('Invalid entry..!!')
        return False

#-----------------------------

def get_ratings_by_movie_id(user_movie_id):

    again = False
    not_found = True

    while True:
        os.system('clear')
        if again:
            print('\n')
            user_movie_id = catch_invalid_input(0,input('Enter the movie ID: '))
            if not user_movie_id:
                return 0

        with open ('u.data') as ratings_file:

            reader = csv.DictReader(ratings_file, fieldnames=('user_id', 'movie_id', 'rating', ""), delimiter="\t")

            ratings_count = 0
            count_line = 4
            print_there(2,35,'----------------------------------------------')
            print_there(3,35,'No.  -  Movie Id  -  Movie Rating  -  User ID')
            print_there(4,35,'----------------------------------------------')

            sum_ratings = 0
            for row in reader:

                if user_movie_id == row['movie_id']:
                    ratings_count += 1
                    count_line += 1
                    info = str(ratings_count) + '        ' + row['movie_id'] + '              ' + row['rating'] + '            ' + row['user_id']
                    print_there(count_line,35, info)
                    info = ''
                    sum_ratings += float(row['rating'])
                    not_found = False

                if count_line == 23:
                    os.system('clear')
                    print_there(2,35,'----------------------------------------------')
                    print_there(3,35,'No.  -  Movie Id  -  Movie Rating  -  User ID')
                    print_there(4,35,'----------------------------------------------')
                    count_line = 4

        if not_found:
            print('\n')
            input('Movie not found..!!')

        elif not not_found:
            total_movie = str(ratings_count)
            ave_rating = str(round((sum_ratings / ratings_count),1))
            print_there(25,27,'|-----------------------------------------------------------|')
            print_there(26,27,'  Movie ID: ' + user_movie_id + '   Total ratings: ' + total_movie + '    ' + 'Average Rating: ' + ave_rating)
            print_there(27,27,'|-----------------------------------------------------------|')
            not_found = True

        print('\n')
        other_movie_again = input("Another Movie? Y/N..:").lower()
        os.system('clear')
        if other_movie_again == 'y':
            again = True
            continue
        else:
            return 0

#----------------------------------------------------------

def get_movie_name(user_movie_id):

    again = False
    not_found = True
    while True:
        os.system('clear')

        print_there(4,35,'---------------------------------------------------------------')
        print_there(5,42,'     Movie Id  -  Movie Name')
        print_there(6,35,'--------------------------------------------------------------')

        if again:
            print('\n')
            user_movie_id = catch_invalid_input(0,input('Enter the movie ID: '))
            if not user_movie_id:
                return 0

        with open ('u.item', encoding='latin_1') as movie_file:

            reader = csv.DictReader(movie_file, fieldnames=('movie_id', 'movie_tittle', ""), delimiter="|")

            for row in reader:

                if user_movie_id == row['movie_id']:
                    print_there(8,49, row['movie_id'] + '        ' + row['movie_tittle'])
                    not_found = False

        if not_found:
            print('\n')
            input('Movie not found..!!')
            not_found = True

        print('\n')
        other_movie_again = input("Another Movie? Y/N..:").lower()
        os.system('clear')
        if other_movie_again == 'y':
            again = True
            continue
        else:
            return 0

#-----------------------------------------------------------

def get_ratings_of_user(user_id):

    again = False
    not_found = True
    while True:
        os.system('clear')
        if again:
            print('\n')
            user_id = catch_invalid_input(0,input('Enter the User ID: '))
            if not user_id:
                return 0

        with open ('u.data') as ratings_file:

            reader = csv.DictReader(ratings_file, fieldnames=('user_id', 'movie_id', 'rating', ""), delimiter="\t")

            ratings_count = 0
            count_line = 4
            print_there(2,35,'----------------------------------------------')
            print_there(3,35,'No.  -  User Id  -  Movie Ratings  -  Movie ID')
            print_there(4,35,'----------------------------------------------')

            user_sum_ratings = 0
            for row in reader:

                if user_id == row['user_id']:
                    ratings_count += 1
                    count_line += 1
                    info = str(ratings_count) + '        ' + row['user_id'] + '              ' + row['rating'] + '            ' + row['movie_id']
                    print_there(count_line,35, info)
                    info = ''
                    user_sum_ratings += float(row['rating'])
                    not_found = False

                if count_line == 29:
                    os.system('clear')
                    print_there(2,35,'----------------------------------------------')
                    print_there(3,35,'No.  -  User Id  -  Movie Ratings  -  Movie ID')
                    print_there(4,35,'----------------------------------------------')
                    count_line = 4
        if not_found:
            print('\n')
            input('User not found..!!')

        elif not not_found:
            total_ratings = str(ratings_count)
            print_there(31,27,'|-----------------------------------------------------------|')
            print_there(32,27,'               User ID: ' + user_id + '   Total ratings: ' + total_ratings)
            print_there(33,27,'|-----------------------------------------------------------|')
            not_found = True

        print('\n')
        other_movie_again = input("Another User? Y/N..:").lower()
        os.system('clear')
        if other_movie_again == 'y':
            again = True
            continue
        else:
            return 0

#-----------------------------------------------------------

def get_menu_choices(choice):

    if catch_invalid_input(choice, 0):
        return 0

    # Find all ratings of a movie by mopvie ID and rating average
    elif int(choice) == 1:

        user_movie_id = catch_invalid_input(0,input('Enter the movie ID: '))

        if user_movie_id != False:
            return get_ratings_by_movie_id(user_movie_id)

    #Find a movie by ID
    elif int(choice) == 2:

        user_movie_id = catch_invalid_input(0,input('Enter the movie ID: '))

        return get_movie_name(user_movie_id)

    # Find all ratings of an user by User ID
    elif int(choice) == 3:

        user_id = catch_invalid_input(0,input('Enter the movie ID: '))
        return get_ratings_of_user(user_id)

#-----------------------------------------------------------

def main():

    while True:

        os.system('clear')
        print('WELCOME TO MOVIE_FLIX')
        print('\n\n')
        print('MAIN MENU:\n')
        print('1.) Find all ratings of a movie by movie ID its average rating \n')
        print('2.) Find a movie by ID\n')
        print('3.) Find all ratings of an user by User ID\n')
        print('4.) Quit Program\n\n\n')

        user_menu_choice = input("Choose a number option and press enter: ")

        if catch_invalid_input(user_menu_choice,0):
            continue
        else:
            if get_menu_choices(user_menu_choice) == 0:
                continue
            else:
                print('\n\n')
                print("Bye... Have a Nice Day!")
                print('\n\n')
                sys.exit()

if __name__ == '__main__':
    main()

        #     #
        # print(count)
#         ratings_dict = (MovieRatings(row['user_id'], row['movie_id'], row['rating']))
#         movie_rartings_list.append(ratings_dict.movie_id)
#         movie_rartings_list.append(ratings_dict.rating)
#
# user_movie_id = input('Enter the movie ID: ')
# #print(ratings_dict.movie_id)
#input()
#
#
# count = 0
# for list_row in movie_rartings_list:
#     count += 1
#     print(movie_rartings_list)
#     input('hey')
#     print(len(movie_rartings_list))
#     input()
#     print(list_row)
#     print(len(list_row))
#     print(type(list_row))
#
#
#
#     input()
#     if user_movie_id == list_row:
#         print(list_row.movie_id, ' ', list_row.rating)
#         input()
#     #
# print(count)

# print(ratings_dict)
# #input()
#print(ratings_dict)
#print(ratings_dict.user_id, " ", ratings_dict.movie_id)
        #print(ratings_dict.user_id)

        #movies[movie_id] = movie
#print(ratings_dict)
#type(ratings_dict)
    #
    # movie_list = [movies[key] for key in movies]
    #
    # print(movie_list)
    #
    #
    # for movie in movies_list
    #     print(movie.tittle)
    #
    # user_input = input('Enter part of movie tittle: ').lower()
    #
    # for movie in movies_list
    #
    #     if user_input == movies_list.tittle.lower():
    #         print(movie)
    #
    #
    #
    # id = input("Enter your movie id: ")
    #

#
# with open (u.item, encoding='latin_1') as item_file :
#     reader = csv.reader(f)
#     #reader = csv.DictReader(item_file, delimiter="|", fieldname=['movie_i', 'tittle'])
#     headers = next(reader)
#     print(headers)
#     print('------')
#     for row in reader
#         movie = (Movie(row['movie_id'], row['tittle']))
#         movies[movie_id] = movie
#

#
# movie_list = [movies[key] for key in movies]
#
# print(movie_list)
#
#
# for movie in movies_list
#     print(movie.tittle)
#
# user_input = input('Enter part of movie tittle: ').lower()
#
# for movie in movies_list
#
#     if user_input == movies_list.tittle.lower():
#         print(movie)
#
#
#
# id = input("Enter your movie id: ")
#

#
#
#
# ____________________________
# for key in movies:
#      movies_list.append(movies[key])
#
#
#
#
# id = input("enter id: ")
#
#
# print(str(movies[id])


from tkinter import *
from tkinter import ttk

def main():
	window = Window()
	window.mainloop()

class Window(Tk):
	def __init__(self):
		super().__init__()
		# object attributes
		self.title("Show Grid Contents & Info")
		# populate
		mainframe = MainFrame(self)

class MainFrame(ttk.Frame):
	def __init__(self, window):
		super().__init__(window)
		# configure
		self.grid(column = 0, row = 0, sticky = (N, W, E, S))
		self.config(padding = "10 5 10 20")
		# populate
		information_label = HelloLabel(self)
		show_slaves_button = ShowSlavesButton(self, information_label)
		show_info_button = ShowInfoButton(self, information_label)
		# layout
		information_label.grid(row = 0, column = 0, padx = 10, pady = 10, columnspan = 2)
		show_slaves_button.grid(row = 1, column = 0, padx = 10, pady = 10)
		show_info_button.grid(row = 1, column = 1, padx = 10, pady = 10)

class HelloLabel(ttk.Label):
	def __init__(self, parent):
		super().__init__(parent)
		# object attributes
		self.original_text = "Click a button for more information about the grid."
		# configure

class ShowInfoButton(ttk.Button):
	def __init__(self, parent, label):
		super().__init__(parent)
		# object attributes
		self.text = "Info about Inhabitants"
		# configure
		self.config(text = self.text, command = lambda:self.show_info(label, parent))

	def show_info(self, label, parent):
		for slave in parent.grid_slaves():
			print(slave)
			#print(slave.grid_info())

			for info in slave.grid_info():
				print(f"{info}, {slave.grid_info()[info]}")

			print() # blank line to separate items

class ShowSlavesButton(ttk.Button):
	def __init__(self, parent, label):
		super().__init__(parent)
		# object attributes
		self.text = "Grid Inhabitants"
		self.label = label
		# configure
		self.config(text = self.text, command = lambda:self.print_slaves(parent))

	def print_slaves(self, parent):
		print(parent.grid_slaves())

		for slave in parent.grid_slaves():
			print(slave)

if __name__ == "__main__":
	main()

'''
.!mainframe.!showinfobutton
{'in': <__main__.MainFrame object .!mainframe>, 'column': 1, 'row': 1, 'columnspan': 1, 'rowspan': 1, 'ipadx': 0, 'ipady': 0, 'padx': 10, 'pady': 10, 'sticky': ''}
.!mainframe.!showslavesbutton
{'in': <__main__.MainFrame object .!mainframe>, 'column': 0, 'row': 1, 'columnspan': 1, 'rowspan': 1, 'ipadx': 0, 'ipady': 0, 'padx': 10, 'pady': 10, 'sticky': ''}
.!mainframe.!hellolabel
{'in': <__main__.MainFrame object .!mainframe>, 'column': 0, 'row': 0, 'columnspan': 2, 'rowspan': 1, 'ipadx': 0, 'ipady': 0, 'padx': 10, 'pady': 10, 'sticky': ''}

'''

'''
another way to do it?

for item in widget.keys():
        print(item)
        print(widget.cget(item))
'''


import torch
from torch import nn
from torch import optim
import torch.nn.functional as F
from torchvision import datasets, transforms, models
from collections import OrderedDict
import time

data_dir = './images_category/images_category'
train_transforms = transforms.Compose([transforms.RandomRotation(30),
                                       transforms.RandomResizedCrop(224),
                                       transforms.RandomHorizontalFlip(),
                                       transforms.ToTensor(),
                                       transforms.Normalize([185.78795853, 183.04217856, 182.72907307],
                                                            [69.36995286, 70.42220844, 70.29462924])])

test_transforms = transforms.Compose([transforms.Resize(255),
                                      transforms.CenterCrop(224),
                                      transforms.ToTensor(),
                                      transforms.Normalize([185.78795853, 183.04217856, 182.72907307],
                                                           [69.36995286, 70.42220844, 70.29462924])])

# Pass transforms in here, then run the next cell to see how the transforms look
train_data = datasets.ImageFolder(data_dir + '/train', transform=train_transforms)
test_data = datasets.ImageFolder(data_dir + '/test', transform=test_transforms)

trainloader = torch.utils.data.DataLoader(train_data, batch_size=32, shuffle=True)
testloader = torch.utils.data.DataLoader(test_data, batch_size=32, shuffle=True)

model = models.resnet101(pretrained=True)

for param in model.parameters():
    param.requires_grad = False

for param in model.layer4.parameters():
    param.requires_grad = True

from collections import OrderedDict
classifier = nn.Sequential(OrderedDict([
                          ('fc1', nn.Linear(2048, 800)),
                          ('relu', nn.ReLU()),
                          ('dropout', nn.Dropout(0.2)),
                          ('fc2', nn.Linear(800, 300)),
                          ('relu', nn.ReLU()),
                          ('dropout', nn.Dropout(0.2)),
                          ('fc3', nn.Linear(300, 121)),
                          ('output', nn.LogSoftmax(dim=1))
                          ]))

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)

model.fc = classifier
criterion = nn.NLLLoss()

# Only train the classifier parameters, feature parameters are frozen
optimizer = optim.Adam(model.fc.parameters(), lr=0.0008)
model.to(device);

epochs = 15
steps = 0
running_loss = 0
for epoch in range(epochs):
    for inputs, labels in trainloader:
        # Move input and label tensors to the default device
        inputs, labels = inputs.to(device), labels.to(device)

        optimizer.zero_grad()

        logps = model.forward(inputs)
        loss = criterion(logps, labels)
        loss.backward()
        optimizer.step()

        running_loss += loss.item()

    test_loss = 0
    accuracy = 0
    model.eval()
    with torch.no_grad():
        for inputs, labels in testloader:
            inputs, labels = inputs.to(device), labels.to(device)
            logps = model.forward(inputs)
            batch_loss = criterion(logps, labels)

            test_loss += batch_loss.item()

            # Calculate accuracy
            ps = torch.exp(logps)
            top_p, top_class = ps.topk(1, dim=1)
            equals = top_class == labels.view(*top_class.shape)
            accuracy += torch.mean(equals.type(torch.FloatTensor)).item()

    print(f"Epoch {epoch+1}/{epochs}.. "
          f"Test loss: {test_loss/len(testloader):.3f}.. "
          f"Test accuracy: {accuracy/len(testloader):.3f}")
    running_loss = 0
    model.train()
    if accuracy/len(testloader) > 0.812:
        torch.save(model.state_dict(), '/warehouse/weight_resnet101_pytorch.pth')


# BOJ_1251
# 단어 나누기

def solution():
    def swap(w):
        out_word = ""
        for c in range(len(w) - 1, -1, -1):
            out_word += w[c]
        return out_word

    word = input()
    word_list = list()

    for i in range(1, len(word) - 1):
        word_1 = word[:i]
        for j in range(i + 1, len(word)):
            word_2 = word[i:j]
            word_3 = word[j:]
            word_list.append(swap(word_1) + swap(word_2) + swap(word_3))
    word_list.sort()
    print(word_list[0])

if __name__ == "__main__":
    solution()


"""
移除链表元素
"""

from typing import Optional

# 定义链表
class ListNode:
    def __init__(self, val=0, next=None):
        # 值
        self.val = val
        # 下一个结点
        self.next = next

class Solution:
    def removeElements(self, head: ListNode, val: int) -> ListNode:
        # 构建一个虚拟的头节点
        dummy_head = ListNode(next=head)
        # 构建一个遍历结点的工具
        cur = dummy_head
        # 后续结点不为空继续运行
        while cur.next is not None:
            # 如果此结点的下一个结点val值与cur的值相同
            if cur.next.val == val:
                # 删除结点
                cur.next = cur.next.next
            # 否则
            else:
                # 移动一次
                cur = cur.next
        # 返回头结点
        return dummy_head.next


import csv
import pandas as pd
import matplotlib.pyplot as plt

CSV_LOAD_PATH = "data_C3-C4.csv"

ROW_INDEX = {
	'name': 0,
	'time': 1,
	'stim_intensity': 6,
	'rep_rate': 8
}

def plot_signal(datapoints):
	plt.plot(datapoints)
	# plt.title('Max: ', max(datapoints), 'min: ', min(datapoints))
	plt.show()

def percent_change(trial_a_signal, trial_b_signal):
	amp_a = max(trial_a_signal) - min(trial_a_signal)
	amp_b = max(trial_b_signal) - min(trial_b_signal)
	return (amp_b - amp_a) / amp_a

class Trial:

	def __init__(self, column):
		self.channel_name = column[ROW_INDEX['name']]
		self.time = column[ROW_INDEX['time']]
		self.stim_intensity = column[ROW_INDEX['stim_intensity']]
		self.rep_rate = column[ROW_INDEX['rep_rate']]

		self.signal = self.read_signal(column)

	def read_signal(self, column):
		# return a list of floats of signal
		list_of_strings = list(column[9:])
		return [float(i) for i in list_of_strings]

def main():
	# load csv
	df = pd.read_csv('data_C3-C4.csv')

	print(df.head(20))

	list_of_trials = []
	for col_index in range(1, 19):
		index = str(col_index)
		curr_trial = Trial(df[index])
		list_of_trials.append(curr_trial)

	trial_1_signal = list_of_trials[0].signal # -> do what you want here.

	trial_2_signal = list_of_trials[1].signal

	print(percent_change(trial_1_signal, trial_2_signal))

	# for trial in list_of_trials:
		# plot_signal(trial.signal)

	# print(type(trial_1.time))

if __name__ == "__main__":
	main()


prime = 101
def pattern_matching(text, pattern):
    m = len(pattern)
    n = len(text)
    pattern_hash = create_hash(pattern, m - 1)
    text_hash = create_hash(text, m - 1)

    for i in range(1, n - m + 2):
        if pattern_hash == text_hash:
            if check_equal(text[i-1:i+m-1], pattern[0:]) is True:
                return i - 1;
        if i < n - m + 1:
            text_hash = recalculate_hash(text, i-1, i+m-1, text_hash, m)
    return -1;

def check_equal(str1, str2):
    if len(str1) != len(str2):
        return False;
    i = 0
    j = 0
    for i, j in zip(str1, str2):
        if i != j:
            return False;
    return True

def create_hash(input, end):
    hash = 0
    for i in range(end + 1):
        hash = hash + ord(input[i])*pow(prime, i)
    return hash

def recalculate_hash(input, old_index, new_index, old_hash, pattern_len):
    new_hash = old_hash - ord(input[old_index])
    new_hash = new_hash/prime
    new_hash += ord(input[new_index])*pow(prime, pattern_len - 1)
    return new_hash;

index = pattern_matching("TusharRoy", "sharRoy")
print("Index ", index)
index = pattern_matching("TusharRoy", "Roy")
print("Index ", index)
index = pattern_matching("TusharRoy", "shar")
print("Index ", index)
index = pattern_matching("TusharRoy", "usha")
print("Index ", index)
index = pattern_matching("TusharRoy", "Tus")
print("Index ", index)
index = pattern_matching("TusharRoy", "Roa")
print("Index ", index)

# 数据结构:
# hash_t, hash_s: 子串哈希值, 长度 len(t)

# 初始化:
# base = 31
# MOD = 10 ** 7
# power = base ** len(t) % MOD
# hash_t = hash_s = 0

# 缩写 t: target, s: source

# 采用字符串哈希,将字符串映射为数字，
# hash_t = (hash_t * 26 + ord(t[i]) % MOD; 哈希函数, 字符串转化成数字.
# 对于要匹配的子串算出一个哈希值，然后再遍历主串,
# 循环, i 从 len(t) 开始,
# 利用滑动窗口, 维护中间 len(t) 长度的子串的哈希值, 子串 s[i - len(t): i],

# 加进低位, 滚动累乘, hash_s = hash_s * base + ord(s[i])
# 减去高位, - power * ord(s[i - nt]
# 如果与 target 串相同, 哈希本身不安全, 取出来 lent(t) 长度的子串再进行比较, 完全相同即可.
# 注意, 减高位 取模, 可能得到负数, 需要加 MOD, 将负数变为正数。
class Solution:
    def strStr2(self, source, target):
        s, t = source, target
        if t is None or s is None:
            return -1
        ns, nt = len(s), len(t)
        if nt > ns:
            return -1
        base = 31
        MOD = 10 ** 7
        power = 31**nt % MOD
        ht = hs = 0
        for i in range(nt):
            ht = (ht * base + ord(t[i])) % MOD
            hs = (hs * base + ord(s[i])) % MOD
        for i in range(nt, ns + 1):
            if ht == hs and s[i - nt: i] == t:
                return i - nt
            if i == ns:
                return -1
            hs = (hs * base + ord(s[i])) % MOD
            hs = (hs - power * ord(s[i - nt])) % MOD
            if hs < 0:
                hs += MOD


import re

from collections import namedtuple
from typing import Any, Callable, Union

from .common import Position, Range

class LexError(Exception):
    pass

Rule = namedtuple('Rule', ['regex', 'callback'])
Token = namedtuple('Token', ['type', 'value', 'range'])

class Lexer:
    """A generic lexer

    Example:
        >>> l = Lexer('aab')
        >>> l.add_rule('a', lambda x: x.upper())
        >>> l.add_rule('b', lambda x: x.upper())
        >>> l.lex()
        'A'
        >>> l.lex()
        'A'
        >>> l.lex()
        'B'
        >>> l.lex()
        None
    """

    def __init__(self, program: str):
        self._program = program
        self._rules = []
        self._lexing = False

    def add_rule(self, regex: str, callback: Callable[[str], Any]):
        """Adds a rule for lexing.

        Args:
            regex: The regular expression string describing the desired lexeme

            callback: A function that processes the matched lexeme text
        """
        if self._lexing:
            raise LexError('Unable to add rules during lexing')

        # Sanitize regex. Make capturing groups non-capturing.
        regex = re.sub('\((?!\?:)', '(?:', regex)

        self._rules.append(Rule(f'({regex})', callback))

    def lex(self) -> Union[Any, None]:
        """Get the next lexeme result.

        Multiple lexemes might be processed until a result is yielded.

        Returns:
            The result of the given callback for the matching lexeme.
            None if the entire source text has been processed.
        """
        try:
            return next(self._generator)

        except AttributeError:
            self._lexing = True

            if not self._rules:
                raise LexError('Cannot lex without rules')

            def generator():
                pattern = re.compile('|'.join([r.regex for r in self._rules]))

                for matches in pattern.findall(self._program):
                    matches = (matches,) if not type(matches) == tuple else matches
                    match, rule = [pair for pair in zip(matches, self._rules) if pair[0]][0]

                    result = rule.callback(match)
                    if result:
                        yield result

                while True:
                    yield None

            self._generator = generator()

        return next(self._generator)

line = 0
character = 0
last_position = Position(line, character)
current_position = Position(line, character)

def update_position(string):
    """Updates the current position of the Lexer.

    Args:
        string: The match string being processed
    """
    global line, character, last_position, current_position
    last_position = Position(line, character)

    for c in string:
        if c == '\n':
            line += 1
            character = 0
        else:
            character += 1

    current_position = Position(line, character)

def process_lexeme(type: str=None) -> Callable[[str], Union[Token, None]]:
    """Construct a function for processing the given type

    Args:
        type: The type of the matched lexeme. If omitted the inner function
            will not return a Token.

    Returns:
        A function
    """

    def callback(lexeme: str) -> Union[Token, None]:
        """Process the given lexeme.

        Args:
            lexeme: The matched lexeme text

        Returns:
            A Token if type was provided by the outer function, otherwise None.
        """
        update_position(lexeme)

        if type:
            range = Range(last_position, current_position)
            return Token(type, lexeme, range)

    return callback

def new_lexer(program: str) -> Lexer:
    """Constructs a Lexer suitable for processing C style structs.

    Args:
        program: The source text

    Returns:
        A Lexer
    """

    global line, character, last_position, current_position
    line = 0
    character = 0
    last_position = Position(line, character)
    current_position = Position(line, character)

    lexer = Lexer(program)

    # Comments
    lexer.add_rule('\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*\/', process_lexeme('comment'))
    #lexer.add_rule('\/\/.*', process_lexeme('comment'))
    lexer.add_rule('\/\/.*(\n\s*\/\/.*)*', process_lexeme('comment'))

    # Literals
    lexer.add_rule('[0-9]+', process_lexeme('number'))

    # Types
    lexer.add_rule('\\b(char|signed char|unsigned char|bool|short|unsigned short|int|unsigned int|long long|unsigned long long|long|unsigned long|float|double)\\b', process_lexeme('type'))

    # Names
    lexer.add_rule('[A-Za-z_]+[A-Za-z0-9_]*', process_lexeme('name'))

    # Operators
    lexer.add_rule('(\[|\]|\{|\}|;|,)', process_lexeme('operator'))

    # Whitespace
    lexer.add_rule('[\s]+', process_lexeme())

    # Everything else. This is for keeping correct positioning.
    lexer.add_rule('.', process_lexeme())

    return lexer


#!/usr/bin/env python
# coding: utf-8
# @Author  : Mr.K
# @Software: PyCharm Community Edition
# @Time    : 2020/1/3 12:19
# @Description:

#统计list中出现的元素个数
#参考：https://www.zybang.com/question/2fa278ce7f89fb437759d57ab4b20594.html
# numbers=["cc","cc","ct","ct","ac"]
# res = {}
# for i in numbers:
#     res[i] = res.get(i, 0) + 1
# print([k for k in res.keys()])
# print([v for v in res.values()])

#返回list中指定元素的所有索引
# a=["cc","cc","ct","ct","ac"]
# print([i for i,x in enumerate(a) if x=='cc'])

#统计list中出现的元素个数并获取其索引
#参考https://www.jianshu.com/p/c107f3d3c6ba
from collections import defaultdict
a=["cc","cc","ct","ct","ac"]
#a=[1,2,3,1,2,3]
d = defaultdict(list)
for i, v in enumerate(a):
    d[v].append(i)
print(d)


'''

Problem 85 | Maximal Rectangle
https://leetcode.com/problems/maximal-rectangle/

'''

class Solution:
    def maximalRectangle(self, matrix: List[List[str]]) -> int:
        if not len(matrix):
            return 0
        for row in range(len(matrix)):
            for col in range(len(matrix[0])):
                matrix[row][col] = int(matrix[row][col])
        for row in range(1, len(matrix)):
            for col in range(len(matrix[0])):
                if matrix[row][col] != 0:
                    matrix[row][col] = int(matrix[row][col]) + int(matrix[row-1][col])
                else:
                    matrix[row][col] = 0
        max_by_row = []
        for i in range(len(matrix)):
            max_by_row.append(self.largest_rectangle_in_histogram(matrix[i]))

        return max(max_by_row)

    def largest_rectangle_in_histogram(self, heights):
        left_stack = []
        left = []
        for i in range(len(heights)):
            left_idx = i
            left_flag = False
            while len(left_stack):
                if heights[left_stack[-1]] >= heights[i]:
                    left_idx = left_stack.pop(-1)
                else:
                    left_flag = True
                    left_idx = left_stack[-1]
                    break
            if left_flag:
                left.append(left_idx + 1)
            else:
                left.append(0)
            left_stack.append(i)

        right_stack = []
        right = [i for i in range(len(heights))]
        for i in range(len(heights)-1, -1, -1):
            right_idx = i
            right_flag = False
            while len(right_stack):
                if heights[right_stack[-1]] >= heights[i]:
                    right_idx = right_stack.pop(-1)
                else:
                    right_flag = True
                    right_idx = right_stack[-1]
                    break
            if right_flag:
                right[i] = right_idx - 1
            else:
                right[i] = len(heights) - 1
            right_stack.append(i)

        max_len_by_idx = []
        for i in range(len(heights)):
            max_len_by_idx.append((right[i] - left[i] + 1)*heights[i])

        return max(max_len_by_idx)