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

from timm.models.layers import DropPath, trunc_normal_
import math
import numpy as np
from models.head import *

up_kwargs = {'mode': 'bilinear', 'align_corners': False}

def load_state_dict(module, state_dict, strict=False):
    """Load state_dict to a module.
    This method is modified from :meth:`torch.nn.Module.load_state_dict`.
    Default value for ``strict`` is set to ``False`` and the message for
    param mismatch will be shown even if strict is False.
    Args:
        module (Module): Module that receives the state_dict.
        state_dict (OrderedDict): Weights.
        strict (bool): whether to strictly enforce that the keys
            in :attr:`state_dict` match the keys returned by this module's
            :meth:`~torch.nn.Module.state_dict` function. Default: ``False``.
        logger (:obj:`logging.Logger`, optional): Logger to log the error
            message. If not specified, print function will be used.
    """
    unexpected_keys = []
    all_missing_keys = []
    err_msg = []

    metadata = getattr(state_dict, '_metadata', None)
    state_dict = state_dict.copy()
    if metadata is not None:
        state_dict._metadata = metadata

    # use _load_from_state_dict to enable checkpoint version control
    def load(module, prefix=''):
        # recursively check parallel module in case that the model has a
        # complicated structure, e.g., nn.Module(nn.Module(DDP))
        local_metadata = {} if metadata is None else metadata.get(
            prefix[:-1], {})
        module._load_from_state_dict(state_dict, prefix, local_metadata, True,
                                     all_missing_keys, unexpected_keys,
                                     err_msg)
        for name, child in module._modules.items():
            if child is not None:
                load(child, prefix + name + '.')

    load(module)
    load = None  # break load->load reference cycle
    # ignore "num_batches_tracked" of BN layers
    missing_keys = [
        key for key in all_missing_keys if 'num_batches_tracked' not in key
    ]

    if unexpected_keys:
        err_msg.append('unexpected key in source '
                       f'state_dict: {", ".join(unexpected_keys)}\n')
    if missing_keys:
        err_msg.append(
            f'missing keys in source state_dict: {", ".join(missing_keys)}\n')

    if len(err_msg) > 0:
        err_msg.insert(
            0, 'The model and loaded state dict do not match exactly\n')
        err_msg = '\n'.join(err_msg)
        if strict:
            raise RuntimeError(err_msg)
        else:
            print(err_msg)

def resize_pos_embed_4d(posemb, posemb_new):
    '''return new position embedding'''
    # Rescale the grid of position embeddings when loading from state_dict. Adapted from
    # https://github.com/google-research/vision_transformer/blob/00883dd691c63a6830751563748663526e811cee/vit_jax/checkpoint.py#L224
    gs_old = posemb.shape[1]  # 14
    gs_new = posemb_new.shape[1]  # 24
    posemb_grid = posemb
    posemb_grid = posemb_grid.permute(0, 3, 1,
                                      2)  # [1, 14, 14, dim]->[1, dim, 14, 14]
    posemb_grid = F.interpolate(posemb_grid, size=(gs_new, gs_new), mode='bicubic')  # [1, dim, 14, 14] -> [1, dim, 24, 24]
    posemb_grid = posemb_grid.permute(0, 2, 3, 1)  # [1, dim, 24, 24]->[1, 24, 24, dim]
    return posemb_grid

def load_checkpoint(model,
                    filename,
                    map_location='cpu',
                    strict=False,
                    ):
    """Load checkpoint from a file or URI.
    Args:
        model (Module): Module to load checkpoint.
        filename (str): Accept local filepath, URL, ``torchvision://xxx``,
            ``open-mmlab://xxx``. Please refer to ``docs/model_zoo.md`` for
            details.
        map_location (str): Same as :func:`torch.load`.
        strict (bool): Whether to allow different params for the model and
            checkpoint.
        logger (:mod:`logging.Logger` or None): The logger for error message.
    Returns:
        dict or OrderedDict: The loaded checkpoint.
    """
    checkpoint = torch.load(filename, map_location=map_location)
    # OrderedDict is a subclass of dict
    if not isinstance(checkpoint, dict):
        raise RuntimeError(
            f'No state_dict found in checkpoint file {filename}')
    # get state_dict from checkpoint
    if 'state_dict' in checkpoint:
        state_dict = checkpoint['state_dict']
    elif 'model' in checkpoint:
        state_dict = checkpoint['model']
    else:
        state_dict = checkpoint
    # strip prefix of state_dict
    if list(state_dict.keys())[0].startswith('module.'):
        state_dict = {k[7:]: v for k, v in state_dict.items()}

    # for MoBY, load model of online branch
    if sorted(list(state_dict.keys()))[0].startswith('encoder'):
        state_dict = {k.replace('encoder.', ''): v for k, v in state_dict.items() if k.startswith('encoder.')}

    old_posemb = state_dict['pos_embed']
    if model.pos_embed.shape != old_posemb.shape:  # need resize the position embedding by interpolate
        new_posemb = resize_pos_embed_4d(old_posemb, model.pos_embed)
        state_dict['pos_embed'] = new_posemb

    # load state_dict
    load_state_dict(model, state_dict, strict)
    print('load pretrained weight strct={}'.format(strict))
    return checkpoint

class OutlookAttention(nn.Module):
    """
    Implementation of outlook attention
    --dim: hidden dim
    --num_heads: number of heads
    --kernel_size: kernel size in each window for outlook attention
    return: token features after outlook attention
    """

    def __init__(self, dim, num_heads, kernel_size=3, padding=1, stride=1,
                 qkv_bias=False, qk_scale=None, attn_drop=0., proj_drop=0.):
        super().__init__()
        head_dim = dim // num_heads
        self.num_heads = num_heads
        self.kernel_size = kernel_size
        self.padding = padding
        self.stride = stride
        self.scale = qk_scale or head_dim**-0.5

        self.v = nn.Linear(dim, dim, bias=qkv_bias)
        self.attn = nn.Linear(dim, kernel_size**4 * num_heads)

        self.attn_drop = nn.Dropout(attn_drop)
        self.proj = nn.Linear(dim, dim)
        self.proj_drop = nn.Dropout(proj_drop)

        self.unfold = nn.Unfold(kernel_size=kernel_size, padding=padding, stride=stride)
        self.pool = nn.AvgPool2d(kernel_size=stride, stride=stride, ceil_mode=True)

    def forward(self, x):
        B, H, W, C = x.shape

        v = self.v(x).permute(0, 3, 1, 2)  # B, C, H, W

        h, w = math.ceil(torch.true_divide(H, self.stride)), math.ceil(torch.true_divide(W, self.stride))
        v = self.unfold(v).reshape(B, self.num_heads, C // self.num_heads,
                                   self.kernel_size * self.kernel_size,
                                   h * w).permute(0, 1, 4, 3, 2)  # B,H,N,kxk,C/H

        attn = self.pool(x.permute(0, 3, 1, 2)).permute(0, 2, 3, 1)
        attn = self.attn(attn).reshape(
            B, h * w, self.num_heads, self.kernel_size * self.kernel_size,
            self.kernel_size * self.kernel_size).permute(0, 2, 1, 3, 4)  # B,H,N,kxk,kxk
        attn = attn * self.scale
        attn = attn.softmax(dim=-1)
        attn = self.attn_drop(attn)

        x = (attn @ v).permute(0, 1, 4, 3, 2).reshape(
            B, C * self.kernel_size * self.kernel_size, h * w)
        x = F.fold(x, output_size=(H, W), kernel_size=self.kernel_size,
                   padding=self.padding, stride=self.stride)

        x = self.proj(x.permute(0, 2, 3, 1))
        x = self.proj_drop(x)

        return x

class Outlooker(nn.Module):
    """
    Implementation of outlooker layer: which includes outlook attention + MLP
    Outlooker is the first stage in our VOLO
    --dim: hidden dim
    --num_heads: number of heads
    --mlp_ratio: mlp ratio
    --kernel_size: kernel size in each window for outlook attention
    return: outlooker layer
    """
    def __init__(self, dim, kernel_size, padding, stride=1,
                 num_heads=1,mlp_ratio=3., attn_drop=0.,
                 drop_path=0., act_layer=nn.GELU,
                 norm_layer=nn.LayerNorm, qkv_bias=False,
                 qk_scale=None):
        super().__init__()
        self.norm1 = norm_layer(dim)
        self.attn = OutlookAttention(dim, num_heads, kernel_size=kernel_size,
                                     padding=padding, stride=stride,
                                     qkv_bias=qkv_bias, qk_scale=qk_scale,
                                     attn_drop=attn_drop)

        self.drop_path = DropPath(
            drop_path) if drop_path > 0. else nn.Identity()

        self.norm2 = norm_layer(dim)
        mlp_hidden_dim = int(dim * mlp_ratio)
        self.mlp = Mlp(in_features=dim,
                       hidden_features=mlp_hidden_dim,
                       act_layer=act_layer)

    def forward(self, x):
        x = x + self.drop_path(self.attn(self.norm1(x)))
        x = x + self.drop_path(self.mlp(self.norm2(x)))
        return x

class Mlp(nn.Module):
    "Implementation of MLP"

    def __init__(self, in_features, hidden_features=None,
                 out_features=None, act_layer=nn.GELU,
                 drop=0.):
        super().__init__()
        out_features = out_features or in_features
        hidden_features = hidden_features or in_features
        self.fc1 = nn.Linear(in_features, hidden_features)
        self.act = act_layer()
        self.fc2 = nn.Linear(hidden_features, out_features)
        self.drop = nn.Dropout(drop)

    def forward(self, x):
        x = self.fc1(x)
        x = self.act(x)
        x = self.drop(x)
        x = self.fc2(x)
        x = self.drop(x)
        return x

class Attention(nn.Module):
    "Implementation of self-attention"

    def __init__(self, dim,  num_heads=8, qkv_bias=False,
                 qk_scale=None, attn_drop=0., proj_drop=0.):
        super().__init__()
        self.num_heads = num_heads
        head_dim = dim // num_heads
        self.scale = qk_scale or head_dim**-0.5

        self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)
        self.attn_drop = nn.Dropout(attn_drop)
        self.proj = nn.Linear(dim, dim)
        self.proj_drop = nn.Dropout(proj_drop)

    def forward(self, x):
        B, H, W, C = x.shape

        qkv = self.qkv(x).reshape(B, H * W, 3, self.num_heads,
                                  C // self.num_heads).permute(2, 0, 3, 1, 4)
        q, k, v = qkv[0], qkv[1], qkv[
            2]  # make torchscript happy (cannot use tensor as tuple)

        attn = (q @ k.transpose(-2, -1)) * self.scale
        attn = attn.softmax(dim=-1)
        attn = self.attn_drop(attn)

        x = (attn @ v).transpose(1, 2).reshape(B, H, W, C)
        x = self.proj(x)
        x = self.proj_drop(x)

        return x

class Transformer(nn.Module):
    """
    Implementation of Transformer,
    Transformer is the second stage in our VOLO
    """
    def __init__(self, dim, num_heads, mlp_ratio=4., qkv_bias=False,
                 qk_scale=None, attn_drop=0., drop_path=0.,
                 act_layer=nn.GELU, norm_layer=nn.LayerNorm):
        super().__init__()
        self.norm1 = norm_layer(dim)
        self.attn = Attention(dim, num_heads=num_heads, qkv_bias=qkv_bias,
                              qk_scale=qk_scale, attn_drop=attn_drop)

        # NOTE: drop path for stochastic depth, we shall see if this is better than dropout here
        self.drop_path = DropPath(
            drop_path) if drop_path > 0. else nn.Identity()

        self.norm2 = norm_layer(dim)
        mlp_hidden_dim = int(dim * mlp_ratio)
        self.mlp = Mlp(in_features=dim,
                       hidden_features=mlp_hidden_dim,
                       act_layer=act_layer)

    def forward(self, x):
        x = x + self.drop_path(self.attn(self.norm1(x)))
        x = x + self.drop_path(self.mlp(self.norm2(x)))
        return x

class ClassAttention(nn.Module):
    """
    Class attention layer from CaiT, see details in CaiT
    Class attention is the post stage in our VOLO, which is optional.
    """
    def __init__(self, dim, num_heads=8, head_dim=None, qkv_bias=False,
                 qk_scale=None, attn_drop=0., proj_drop=0.):
        super().__init__()
        self.num_heads = num_heads
        if head_dim is not None:
            self.head_dim = head_dim
        else:
            head_dim = dim // num_heads
            self.head_dim = head_dim
        self.scale = qk_scale or head_dim**-0.5

        self.kv = nn.Linear(dim,
                            self.head_dim * self.num_heads * 2,
                            bias=qkv_bias)
        self.q = nn.Linear(dim, self.head_dim * self.num_heads, bias=qkv_bias)
        self.attn_drop = nn.Dropout(attn_drop)
        self.proj = nn.Linear(self.head_dim * self.num_heads, dim)
        self.proj_drop = nn.Dropout(proj_drop)

    def forward(self, x):
        B, N, C = x.shape

        kv = self.kv(x).reshape(B, N, 2, self.num_heads,
                                self.head_dim).permute(2, 0, 3, 1, 4)
        k, v = kv[0], kv[
            1]  # make torchscript happy (cannot use tensor as tuple)
        q = self.q(x[:, :1, :]).reshape(B, self.num_heads, 1, self.head_dim)
        attn = ((q * self.scale) @ k.transpose(-2, -1))
        attn = attn.softmax(dim=-1)
        attn = self.attn_drop(attn)

        cls_embed = (attn @ v).transpose(1, 2).reshape(
            B, 1, self.head_dim * self.num_heads)
        cls_embed = self.proj(cls_embed)
        cls_embed = self.proj_drop(cls_embed)
        return cls_embed

class ClassBlock(nn.Module):
    """
    Class attention block from CaiT, see details in CaiT
    We use two-layers class attention in our VOLO, which is optional.
    """

    def __init__(self, dim, num_heads, head_dim=None, mlp_ratio=4.,
                 qkv_bias=False, qk_scale=None, drop=0., attn_drop=0.,
                 drop_path=0., act_layer=nn.GELU, norm_layer=nn.LayerNorm):
        super().__init__()
        self.norm1 = norm_layer(dim)
        self.attn = ClassAttention(
            dim, num_heads=num_heads, head_dim=head_dim, qkv_bias=qkv_bias,
            qk_scale=qk_scale, attn_drop=attn_drop, proj_drop=drop)
        # NOTE: drop path for stochastic depth
        self.drop_path = DropPath(
            drop_path) if drop_path > 0. else nn.Identity()
        self.norm2 = norm_layer(dim)
        mlp_hidden_dim = int(dim * mlp_ratio)
        self.mlp = Mlp(in_features=dim,
                       hidden_features=mlp_hidden_dim,
                       act_layer=act_layer,
                       drop=drop)

    def forward(self, x):
        cls_embed = x[:, :1]
        cls_embed = cls_embed + self.drop_path(self.attn(self.norm1(x)))
        cls_embed = cls_embed + self.drop_path(self.mlp(self.norm2(cls_embed)))
        return torch.cat([cls_embed, x[:, 1:]], dim=1)

def get_block(block_type, **kargs):
    """
    get block by name, specifically for class attention block in here
    """
    if block_type == 'ca':
        return ClassBlock(**kargs)

def rand_bbox(size, lam, scale=1):
    """
    get bounding box as token labeling (https://github.com/zihangJiang/TokenLabeling)
    return: bounding box
    """
    W = size[1] // scale
    H = size[2] // scale
    cut_rat = np.sqrt(1. - lam)
    cut_w = np.int(W * cut_rat)
    cut_h = np.int(H * cut_rat)

    # uniform
    cx = np.random.randint(W)
    cy = np.random.randint(H)

    bbx1 = np.clip(cx - cut_w // 2, 0, W)
    bby1 = np.clip(cy - cut_h // 2, 0, H)
    bbx2 = np.clip(cx + cut_w // 2, 0, W)
    bby2 = np.clip(cy + cut_h // 2, 0, H)

    return bbx1, bby1, bbx2, bby2

class PatchEmbed(nn.Module):
    """
    Image to Patch Embedding.
    Different with ViT use 1 conv layer, we use 4 conv layers to do patch embedding
    """

    def __init__(self, img_size=224, stem_conv=False, stem_stride=1,
                 patch_size=8, in_chans=3, hidden_dim=64, embed_dim=384):
        super().__init__()
        assert patch_size in [4, 8, 16]

        self.stem_conv = stem_conv
        if stem_conv:
            self.conv = nn.Sequential(
                nn.Conv2d(in_chans, hidden_dim, kernel_size=7, stride=stem_stride,
                          padding=3, bias=False),  # 112x112
                nn.BatchNorm2d(hidden_dim),
                nn.ReLU(inplace=True),
                nn.Conv2d(hidden_dim, hidden_dim, kernel_size=3, stride=1,
                          padding=1, bias=False),  # 112x112
                nn.BatchNorm2d(hidden_dim),
                nn.ReLU(inplace=True),
                nn.Conv2d(hidden_dim, hidden_dim, kernel_size=3, stride=1,
                          padding=1, bias=False),  # 112x112
                nn.BatchNorm2d(hidden_dim),
                nn.ReLU(inplace=True),
            )

        self.proj = nn.Conv2d(hidden_dim,
                              embed_dim,
                              kernel_size=patch_size // stem_stride,
                              stride=patch_size // stem_stride)
        self.num_patches = (img_size // patch_size) * (img_size // patch_size)

    def forward(self, x):
        if self.stem_conv:
            x = self.conv(x)
        x = self.proj(x)  # B, C, H, W
        return x

class Downsample(nn.Module):
    """
    Image to Patch Embedding, downsampling between stage1 and stage2
    """
    def __init__(self, in_embed_dim, out_embed_dim, patch_size):
        super().__init__()
        self.proj = nn.Conv2d(in_embed_dim, out_embed_dim,
                              kernel_size=patch_size, stride=patch_size)

    def forward(self, x):
        x = x.permute(0, 3, 1, 2)
        x = self.proj(x)  # B, C, H, W
        x = x.permute(0, 2, 3, 1)
        return x

def outlooker_blocks(block_fn, index, dim, layers, num_heads=1, kernel_size=3,
                     padding=1,stride=1, mlp_ratio=3., qkv_bias=False, qk_scale=None,
                     attn_drop=0, drop_path_rate=0., **kwargs):
    """
    generate outlooker layer in stage1
    return: outlooker layers
    """
    blocks = []
    for block_idx in range(layers[index]):
        block_dpr = drop_path_rate * (block_idx +
                                      sum(layers[:index])) / (sum(layers) - 1)
        blocks.append(block_fn(dim, kernel_size=kernel_size, padding=padding,
                               stride=stride, num_heads=num_heads, mlp_ratio=mlp_ratio,
                               qkv_bias=qkv_bias, qk_scale=qk_scale, attn_drop=attn_drop,
                               drop_path=block_dpr))

    blocks = nn.Sequential(*blocks)

    return blocks

def transformer_blocks(block_fn, index, dim, layers, num_heads, mlp_ratio=3.,
                       qkv_bias=False, qk_scale=None, attn_drop=0,
                       drop_path_rate=0., **kwargs):
    """
    generate transformer layers in stage2
    return: transformer layers
    """
    blocks = []
    for block_idx in range(layers[index]):
        block_dpr = drop_path_rate * (block_idx +
                                      sum(layers[:index])) / (sum(layers) - 1)
        blocks.append(
            block_fn(dim, num_heads,
                     mlp_ratio=mlp_ratio,
                     qkv_bias=qkv_bias,
                     qk_scale=qk_scale,
                     attn_drop=attn_drop,
                     drop_path=block_dpr))

    blocks = nn.Sequential(*blocks)

    return blocks

class VOLO_backbone(nn.Module):
    """
    Vision Outlooker, the main class of our model
    --layers: [x,x,x,x], four blocks in two stages, the first block is outlooker, the
              other three are transformer, we set four blocks, which are easily
              applied to downstream tasks
    --img_size, --in_chans, --num_classes: these three are very easy to understand
    --patch_size: patch_size in outlook attention
    --stem_hidden_dim: hidden dim of patch embedding, d1-d4 is 64, d5 is 128
    --embed_dims, --num_heads: embedding dim, number of heads in each block
    --downsamples: flags to apply downsampling or not
    --outlook_attention: flags to apply outlook attention or not
    --mlp_ratios, --qkv_bias, --qk_scale, --drop_rate: easy to undertand
    --attn_drop_rate, --drop_path_rate, --norm_layer: easy to undertand
    --post_layers: post layers like two class attention layers using [ca, ca],
                  if yes, return_mean=False
    --return_mean: use mean of all feature tokens for classification, if yes, no class token
    --return_dense: use token labeling, details are here:
                    https://github.com/zihangJiang/TokenLabeling
    --mix_token: mixing tokens as token labeling, details are here:
                    https://github.com/zihangJiang/TokenLabeling
    --pooling_scale: pooling_scale=2 means we downsample 2x
    --out_kernel, --out_stride, --out_padding: kerner size,
                                               stride, and padding for outlook attention
    """
    def __init__(self, layers, img_size=512, in_chans=3, patch_size=8,
                 stem_hidden_dim=64, embed_dims=None, num_heads=None, downsamples=None,
                 outlook_attention=None, mlp_ratios=None, qkv_bias=False, qk_scale=None,
                 drop_rate=0., attn_drop_rate=0., drop_path_rate=0., norm_layer=nn.LayerNorm,
                 pooling_scale=2, out_kernel=3, out_stride=2, out_padding=1):

        super().__init__()
        self.patch_embed = PatchEmbed(stem_conv=True, stem_stride=2, patch_size=patch_size,
                                      in_chans=in_chans, hidden_dim=stem_hidden_dim,
                                      embed_dim=embed_dims[0])

        # inital positional encoding, we add positional encoding after outlooker blocks
        self.pos_embed = nn.Parameter(
            torch.zeros(1, img_size // patch_size // pooling_scale,
                        img_size // patch_size // pooling_scale,
                        embed_dims[-1]))

        self.pos_drop = nn.Dropout(p=drop_rate)

        # set the main block in network
        network = []
        for i in range(len(layers)):
            if outlook_attention[i]:
                # stage 1
                stage = outlooker_blocks(Outlooker, i, embed_dims[i], layers,
                                         downsample=downsamples[i], num_heads=num_heads[i],
                                         kernel_size=out_kernel, stride=out_stride,
                                         padding=out_padding, mlp_ratio=mlp_ratios[i],
                                         qkv_bias=qkv_bias, qk_scale=qk_scale,
                                         attn_drop=attn_drop_rate, norm_layer=norm_layer)
                network.append(stage)
            else:
                # stage 2
                stage = transformer_blocks(Transformer, i, embed_dims[i], layers,
                                           num_heads[i], mlp_ratio=mlp_ratios[i],
                                           qkv_bias=qkv_bias, qk_scale=qk_scale,
                                           drop_path_rate=drop_path_rate,
                                           attn_drop=attn_drop_rate,
                                           norm_layer=norm_layer)
                network.append(stage)

            if downsamples[i]:
                # downsampling between two stages
                network.append(Downsample(embed_dims[i], embed_dims[i + 1], 2))

        self.network = nn.ModuleList(network)
        trunc_normal_(self.pos_embed, std=.02)

    def init_weights(self, pretrained=None, strict=False):
        """Initialize the weights in backbone.
        Args:
            pretrained (str, optional): Path to pre-trained weights.
                Defaults to None.
        """
        def _init_weights(m):
            if isinstance(m, nn.Linear):
                trunc_normal_(m.weight, std=.02)
                if isinstance(m, nn.Linear) and m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.LayerNorm):
                nn.init.constant_(m.bias, 0)
                nn.init.constant_(m.weight, 1.0)
        if isinstance(pretrained, str):
            self.apply(_init_weights)
            load_checkpoint(self, pretrained, strict=strict)
            print('load pretained weight strict={}'.format(strict))
        elif pretrained is None:
            self.apply(_init_weights)
        else:
            raise TypeError('pretrained must be a str or None')

    def forward_embeddings(self, x):
        # patch embedding
        x = self.patch_embed(x)
        # B,C,H,W-> B,H,W,C
        x = x.permute(0, 2, 3, 1)
        return x

    def forward_tokens(self, x):
        out = []
        for idx, block in enumerate(self.network):
            if idx == 2:  # add positional encoding after outlooker blocks
                x = x + self.pos_embed
                x = self.pos_drop(x)
            x = block(x)
            out.append(x.permute(0, 3, 1, 2).contiguous())
        return out

    def forward(self, x):
        # step1: patch embedding
        x = self.forward_embeddings(x)

        # step2: tokens learning in the two stages
        out = self.forward_tokens(x)

        return tuple(out)

class VOLO(nn.Module):

    def __init__(self, nclass, embed_dim, layers, num_heads, mlp_ratios, downsamples, outlook_attention,
                 aux=False, pretrained_root=None, head='seghead', edge_aux=False, stem_hidden_dim=None):
        super(VOLO, self).__init__()
        self.aux = aux
        self.edge_aux = edge_aux
        self.head_name = head
        self.backbone = VOLO_backbone(layers=layers,
                                      embed_dims=embed_dim,
                                      num_heads=num_heads,
                                      mlp_ratios=mlp_ratios,
                                      downsamples=downsamples,
                                      outlook_attention=outlook_attention,
                                      stem_hidden_dim=stem_hidden_dim or 64,
                                      img_size=512
                                      )

        if self.head_name == 'apchead':
            self.decode_head = APCHead(in_channels=embed_dim[3], num_classes=nclass, in_index=3, channels=512)

        if self.head_name == 'aspphead':
            self.decode_head = ASPPHead(in_channels=embed_dim[3], num_classes=nclass, in_index=3)

        if self.head_name == 'asppplushead':
            self.decode_head = ASPPPlusHead(in_channels=embed_dim[3], num_classes=nclass, in_index=[0, 3])

        if self.head_name == 'dahead':
            self.decode_head = DAHead(in_channels=embed_dim[3], num_classes=nclass, in_index=3)

        if self.head_name == 'dnlhead':
            self.decode_head = DNLHead(in_channels=embed_dim[3], num_classes=nclass, in_index=3, channels=512)

        if self.head_name == 'fcfpnhead':
            self.decode_head = FCFPNHead(in_channels=embed_dim, num_classes=nclass, in_index=[0, 1, 2, 3], channels=256)

        if self.head_name == 'cefpnhead':
            self.decode_head = CEFPNHead(in_channels=embed_dim, num_classes=nclass, in_index=[0, 1, 2, 3], channels=256)

        if self.head_name == 'fcnhead':
            self.decode_head = FCNHead(in_channels=embed_dim[3], num_classes=nclass, in_index=3, channels=512)

        if self.head_name == 'gchead':
            self.decode_head = GCHead(in_channels=embed_dim[3], num_classes=nclass, in_index=3, channels=512)

        if self.head_name == 'psahead':
            self.decode_head = PSAHead(in_channels=embed_dim[3], num_classes=nclass, in_index=3)

        if self.head_name == 'psphead':
            self.decode_head = PSPHead(in_channels=embed_dim[3], num_classes=nclass, in_index=3)

        if self.head_name == 'seghead':
            self.decode_head = SegHead(in_channels=embed_dim, num_classes=nclass, in_index=[0, 1, 2, 3])

        if self.head_name == 'unethead':
            self.decode_head = UNetHead(in_channels=embed_dim, num_classes=nclass, in_index=[0, 1, 2, 3])

        if self.head_name == 'uperhead':
            self.decode_head = UPerHead(in_channels=embed_dim, num_classes=nclass)

        if self.head_name == 'annhead':
            self.decode_head = ANNHead(in_channels=embed_dim[2:], num_classes=nclass, in_index=[2, 3], channels=512)

        if self.head_name == 'mlphead':
            self.decode_head = MLPHead(in_channels=embed_dim, num_classes=nclass, in_index=[0, 1, 2, 3], channels=256)

        if self.aux:
            self.auxiliary_head = FCNHead(num_convs=1, in_channels=embed_dim[2], num_classes=nclass, in_index=2, channels=256)

        if self.edge_aux:
            self.edge_head = EdgeHead(in_channels=embed_dim[0:2], in_index=[0, 1], channels=embed_dim[0])

        if pretrained_root is None:
            self.backbone.init_weights()
        else:
            if 'upernet' in pretrained_root:
                load_checkpoint(self, filename=pretrained_root, strict=False)
            else:
                self.backbone.init_weights(pretrained=pretrained_root, strict=False)

    def forward(self, x):
        size = x.size()[2:]
        outputs = []

        out_backbone = self.backbone(x)
        x0 = self.decode_head(out_backbone)
        if isinstance(x0, (list, tuple)):
            for out in x0:
                out = F.interpolate(out, size, **up_kwargs)
                outputs.append(out)
        else:
            x0 = F.interpolate(x0, size, **up_kwargs)
            outputs.append(x0)

        if self.aux:
            x1 = self.auxiliary_head(out_backbone)
            x1 = F.interpolate(x1, size, **up_kwargs)
            outputs.append(x1)

        if self.edge_aux:
            edge = self.edge_head(out_backbone)
            edge = F.interpolate(edge, size, **up_kwargs)
            outputs.append(edge)

        return outputs

def volo_d1(nclass, pretrained=False, aux=False, head='uperhead', edge_aux=False):
    if pretrained:
        pretrained_root = './pretrained_weights/d1_224_84.2.pth.tar'
    else:
        pretrained_root = None

    layers = [4, 4, 8, 2]  # num of layers in the four blocks
    embed_dims = [192, 384, 384, 384]
    num_heads = [6, 12, 12, 12]
    mlp_ratios = [3, 3, 3, 3]
    downsamples = [True, False, False, False] # do downsampling after first block
    outlook_attention = [True, False, False, False ]
    # first block is outlooker (stage1), the other three are transformer (stage2)
    model = VOLO(layers=layers,
                 embed_dim=embed_dims,
                 num_heads=num_heads,
                 mlp_ratios=mlp_ratios,
                 downsamples=downsamples,
                 outlook_attention=outlook_attention,
                 nclass=nclass, aux=aux, head=head, edge_aux=edge_aux, pretrained_root=pretrained_root
                 )
    return model

def volo_d2(nclass, pretrained=False, aux=False, head='uperhead', edge_aux=False):
    if pretrained:
        pretrained_root = './pretrained_weights/rest_lite.pth'
    else:
        pretrained_root = None

    layers = [6, 4, 10, 4]
    embed_dims = [256, 512, 512, 512]
    num_heads = [8, 16, 16, 16]
    mlp_ratios = [3, 3, 3, 3]
    downsamples = [True, False, False, False]
    outlook_attention = [True, False, False, False]
    # first block is outlooker (stage1), the other three are transformer (stage2)
    model = VOLO(layers=layers,
                 embed_dim=embed_dims,
                 num_heads=num_heads,
                 mlp_ratios=mlp_ratios,
                 downsamples=downsamples,
                 outlook_attention=outlook_attention,
                 nclass=nclass, aux=aux, head=head, edge_aux=edge_aux, pretrained_root=pretrained_root
                 )
    return model

def volo_d3(nclass, pretrained=False, aux=False, head='uperhead', edge_aux=False):
    if pretrained:
        pretrained_root = './pretrained_weights/rest_lite.pth'
    else:
        pretrained_root = None

    layers = [8, 8, 16, 4]
    embed_dims = [256, 512, 512, 512]
    num_heads = [8, 16, 16, 16]
    mlp_ratios = [3, 3, 3, 3]
    downsamples = [True, False, False, False]
    outlook_attention = [True, False, False, False]
    # first block is outlooker (stage1), the other three are transformer (stage2)
    model = VOLO(layers=layers,
                 embed_dim=embed_dims,
                 num_heads=num_heads,
                 mlp_ratios=mlp_ratios,
                 downsamples=downsamples,
                 outlook_attention=outlook_attention,
                 nclass=nclass, aux=aux, head=head, edge_aux=edge_aux, pretrained_root=pretrained_root
                 )
    return model

def volo_d4(nclass, pretrained=False, aux=False, head='uperhead', edge_aux=False):
    if pretrained:
        pretrained_root = './pretrained_weights/rest_lite.pth'
    else:
        pretrained_root = None

    layers = [8, 8, 16, 4]
    embed_dims = [384, 768, 768, 768]
    num_heads = [12, 16, 16, 16]
    mlp_ratios = [3, 3, 3, 3]
    downsamples = [True, False, False, False]
    outlook_attention = [True, False, False, False]
    # first block is outlooker (stage1), the other three are transformer (stage2)
    model = VOLO(layers=layers,
                 embed_dim=embed_dims,
                 num_heads=num_heads,
                 mlp_ratios=mlp_ratios,
                 downsamples=downsamples,
                 outlook_attention=outlook_attention,
                 nclass=nclass, aux=aux, head=head, edge_aux=edge_aux, pretrained_root=pretrained_root
                 )
    return model

def volo_d5(nclass, pretrained=False, aux=False, head='uperhead', edge_aux=False):
    if pretrained:
        pretrained_root = './pretrained_weights/d5_512_87.07.pth.tar'
    else:
        pretrained_root = None

    layers = [12, 12, 20, 4]
    embed_dims = [384, 768, 768, 768]
    num_heads = [12, 16, 16, 16]
    mlp_ratios = [4, 4, 4, 4]
    downsamples = [True, False, False, False]
    outlook_attention = [True, False, False, False]
    # first block is outlooker (stage1), the other three are transformer (stage2)
    model = VOLO(layers=layers,
                 embed_dim=embed_dims,
                 num_heads=num_heads,
                 mlp_ratios=mlp_ratios,
                 downsamples=downsamples,
                 outlook_attention=outlook_attention,
                 stem_hidden_dim=128,
                 nclass=nclass, aux=aux, head=head, edge_aux=edge_aux, pretrained_root=pretrained_root
                 )
    return model

if __name__ == '__main__':
    """Notice if torch1.6, try to replace a / b with torch.true_divide(a, b)"""
    from tools.flops_params_fps_count import flops_params_fps

    model_base = volo_d1(nclass=6, aux=True, edge_aux=True, head='mlphead', pretrained=True)

    flops_params_fps(model_base)


from flask import Blueprint, request, send_from_directory, Response
from config import IS_LOCALHOST
import yaml
import dotenv
from os import getenv
dotenv.load_dotenv()

bp = Blueprint("plugin", __name__)
print('localhost started? ', IS_LOCALHOST)

AUTHO_CLIENT_URL = getenv('AUTHO_CLIENT_URL')
AUTHO_AUTHORIZATION_URL = getenv('AUTHO_AUTHORIZATION_URL')
OPENAI_VERIFICATION_TOKEN = getenv('OPENAI_VERIFICATION_TOKEN')

if not (AUTHO_CLIENT_URL and AUTHO_AUTHORIZATION_URL and OPENAI_VERIFICATION_TOKEN):
    print('WARNING: THIS WILL NOT WORK ON PRODUCTION WITHOUT AUTHO_CLIENT_URL, AUTHO_AUTHORIZATION_URL, and OPENAI_VERIFICATION_TOKEN ENV VARIABLES SET')

@bp.route("/.well-known/ai-plugin.json", methods=["GET"])
def get_ai_plugin():
    host = request.headers['Host']
    print('host: ', host)
    if IS_LOCALHOST:
        print('GIVING LOCALHOST YAMAL')
        with open('./plugin/manifest_local.json', 'r') as f:
            text = f.read()
            text = text.replace("PLUGIN_HOSTNAME", "http://localhost:5000")
    else:
        print('rendering prod manifest')
        with open('./plugin/manifest.json', 'r') as f:
            text = f.read()
            text = text.replace("PLUGIN_HOSTNAME", f"https://{host}")
            text = text.replace("AUTHO_CLIENT_URL", AUTHO_CLIENT_URL)
            text = text.replace("AUTH0_AUTHORIZATION_URL", AUTHO_AUTHORIZATION_URL)
            text = text.replace("OPENAI_VERIFICATION_TOKEN", OPENAI_VERIFICATION_TOKEN)

    return Response(text, mimetype="text/json")

@bp.route("/.well-known/ai-plugin2.json", methods=["GET"])
def get_ai_plugin2():
    host = request.headers['Host']
    with open('./plugin/manifest.json', 'r') as f:
        text = f.read()
        text = text.replace("PLUGIN_HOSTNAME", f"https://{host}")
        text = text.replace("AUTHO_CLIENT_URL", AUTHO_CLIENT_URL)
        text = text.replace("AUTH0_AUTHORIZATION_URL", AUTHO_AUTHORIZATION_URL)
        text = text.replace("OPENAI_VERIFICATION_TOKEN", OPENAI_VERIFICATION_TOKEN)

    return Response(text, mimetype="text/json")

@bp.route("/openapi.yaml", methods=["GET"])
def get_openapi():
    with open("./plugin/openapi.yaml") as f:
        host = request.headers['Host']
        text = f.read()
    if IS_LOCALHOST:
        text = text.replace("PLUGIN_HOSTNAME", "http://localhost:5000")
    else:
        text = text.replace("PLUGIN_HOSTNAME", f"https://{host}")

    # load the yaml
    yaml_dict = yaml.load(text, Loader=yaml.FullLoader)
    print('yaml good')
    return Response(text, mimetype="text/yaml")

@bp.route("/logo.jpeg", methods=["GET"])
def get_logo():
    print('getting logo')
    return send_from_directory('./static', 'logo.png')


import cv2
import os
import pydicom
import argparse

from tqdm import tqdm

parser = argparse.ArgumentParser()
parser.add_argument('-t', '--type', type=str, required=True,
                    choices=['train', 'test'], help='whether to convert train images or test images')
args = vars(parser.parse_args())

if args['type'] == 'train':
    print('Converting train images from .dcm to .jpg...')
    inputdir = 'input/stage_2_train_images/'
    outdir = 'input/images'
elif args['type'] == 'test':
    print('Converting test images from .dcm to .jpg...')
    inputdir = 'input/stage_2_test_images/'
    outdir = 'input/samples'
os.makedirs(outdir, exist_ok=True)

train_list = [f for f in  os.listdir(inputdir)]

for i, f in tqdm(enumerate(train_list[:]), total=len(train_list)):
    ds = pydicom.read_file(inputdir + f) # read dicom image
    img = ds.pixel_array # get image array
    # img = cv2.resize(img, (416, 416))
    cv2.imwrite(os.path.join(outdir, f.replace('.dcm','.jpg')), img) # write jpg image


from pyramid.config import Configurator
from sqlalchemy import engine_from_config
from os import environ
from api.models.sql_automap import DBSession, Base

def main(global_config, **settings):
    sqlalchemy_url_value = environ.get('STR_CONNECTION', 'mysql://root:pass@172.17.0.3:3306/matomo')
    settings.update({'sqlalchemy.url': sqlalchemy_url_value})

    application_url_value = environ.get('APPLICATION_URL', 'http://127.0.0.1:6543')
    settings.update({'application.url': application_url_value})

    config = Configurator(settings=settings)

    engine = engine_from_config(settings, 'sqlalchemy.', pool_recycle=1800)
    DBSession.configure(bind=engine)
    Base.metadata.bind = engine

    config.add_renderer('tsv', 'api.renderers.TSVRenderer')

    config.add_route('home', '/')
    config.add_route('status', '/status')
    config.add_route('members', '/members')
    config.add_route('reports', '/reports')
    config.add_route('reports_report_id', '/reports/{report_id}')
    config.scan('.views')

    return config.make_wsgi_app()


import sys
import treeswift as ts
from sys import platform as _platform
import tempfile
from subprocess import Popen, PIPE
import pkg_resources
import time
import logging

def print_ident(tree):
    for i in tree.root.children:
        print(len(list(i.traverse_postorder())),)
    print("")

def reestimate_backbone(options):
    assert options.ref_fp
    start = time.time()
    orig_branch_tree = ts.read_tree(options.tree_fp, schema='newick')
    if len(orig_branch_tree.root.children) > 2:  # 3
        rooted = False
    else:
        rooted = True
    orig_branch_tree.suppress_unifurcations()
    if len(orig_branch_tree.root.children) > 3:
        # polytomy at the root
        orig_branch_tree.resolve_polytomies()
    else:
        # root node is ok, resolve the other nodes
        for i in orig_branch_tree.root.children:
            i.resolve_polytomies()
    all_branches_have_length = True
    for n in orig_branch_tree.traverse_postorder(internal=True, leaves=True):
        if not n.is_root() and n.edge_length is None:
            all_branches_have_length = False
            break

    if rooted and all_branches_have_length:
        left, right = orig_branch_tree.root.children
        if left.children:
            thetwo = [next(c.traverse_postorder(internal=False)) for c in left.children]
            theone = [next(right.traverse_postorder(internal=False))]
            lengthtwoside = left.edge_length
            lengthoneside = right.edge_length
        else:
            thetwo = [next(c.traverse_postorder(internal=False)) for c in right.children]
            theone = [next(left.traverse_postorder(internal=False))]
            lengthtwoside = right.edge_length
            lengthoneside = left.edge_length

    orig_branch_resolved_fp = tempfile.NamedTemporaryFile(delete=True, mode='w+t').name
    orig_branch_tree.write_tree_newick(orig_branch_resolved_fp)

    if _platform == "darwin":
        fasttree_exec = pkg_resources.resource_filename('apples', "tools/FastTree-darwin")
    elif _platform == "linux" or _platform == "linux2":
        fasttree_exec = pkg_resources.resource_filename('apples', "tools/FastTree-linux")
    elif _platform == "win32" or _platform == "win64" or _platform == "msys":
        fasttree_exec = pkg_resources.resource_filename('apples', "tools/FastTree.exe")
    else:
        # Unrecognised system
        raise ValueError('Your system {} is not supported yet.' % _platform)

    bb_fp = tempfile.NamedTemporaryFile(delete=False, mode='w+t')
    fasttree_log = tempfile.NamedTemporaryFile(delete=False, mode='w+t').name
    logging.info("FastTree log file is located here: %s" % fasttree_log)

    s = [fasttree_exec, "-nosupport", "-nome", "-noml", "-log", fasttree_log,
         "-intree", orig_branch_resolved_fp]
    if not options.protein_seqs:
        s.append("-nt")
    with open(options.ref_fp, "r") as rf:
        with Popen(s, stdout=PIPE, stdin=rf, stderr=sys.stderr) as p:
            #options.tree_fp = bb_fp.name
            tree_string = p.stdout.read().decode('utf-8')
            if rooted and all_branches_have_length:
                ft = ts.read_tree_newick(tree_string)
                for n in ft.traverse_postorder(internal=False):
                    if n.label == theone[0].label:
                        theone_inft = n
                        break
                ft.reroot(theone_inft)
                mrca = ft.mrca([n.label for n in thetwo])
                mrca_edge_length = mrca.edge_length
                ft.reroot(mrca, length=mrca_edge_length/2)
                if lengthtwoside+lengthoneside > 0:
                    for i in range(2):
                        if ft.root.children[i] == mrca:
                            ft.root.children[i].edge_length = mrca_edge_length*lengthtwoside/(lengthtwoside+lengthoneside)
                            ft.root.children[1-i].edge_length = mrca_edge_length*lengthoneside/(lengthtwoside+lengthoneside)
                ft.is_rooted = False
                tree_string = str(ft)
            with open(bb_fp.name, "w") as ntree:
                ntree.write(tree_string.strip())
                ntree.write("\n")
                options.tree_fp = bb_fp.name
    logging.info(
        "[%s] Reestimated branch lengths in %.3f seconds." % (time.strftime("%H:%M:%S"), (time.time() - start)))


#!/bin/python3

def max_common_child(s1: str, s2: str) -> int:
    """A string is said to be a child of a another string if it can be formed by deleting
    0 or more characters from the other string. Letters cannot be rearranged.
    Given two strings of equal length, what's the longest string that can be constructed
    such that it is a child of both?
    Example:
    s1 = "ABCD", s2 = "ABDC"
    common_child(s1, s2) == 3   (both "ABC" and "ABD" are valid)
    """

    memo = {}

    def memoize(func):
        def decorator(s1, s2, length=0):
            key = s1 + "-" + s2
            if key not in memo:
                memo[key] = func(s1, s2, length)
            return memo[key]

        return decorator

    # O(N^2) with memoization
    # note that for large strings it can get N^2 recursions and throw a RecursionError
    @memoize
    def max_child_length_recursive(s1: str, s2: str, length: int = 0) -> int:
        if len(s1) == 0 or len(s2) == 0:
            return length

        # take the character if present in both strings and continue
        if s1[0] == s2[0]:
            return max_child_length_recursive(s1[1:], s2[1:], length + 1)

        return max(
            max_child_length_recursive(s1[1:], s2, length),  # skip one from s1
            max_child_length_recursive(s1, s2[1:], length)   # skip one from s2
        )

    # O(^2)
    def max_child_length_with_matrix(s1: str, s2: str) -> int:
        n1 = len(s1)
        n2 = len(s2)
        if n1 == 0 or n2 == 0:
            return 0

        solutions_matrix = [[0 for _ in range(n1 + 1)] for _ in range(n2 + 1)]

        for i in range(1, n1 + 1):
            for j in range(1, n2 + 1):
                if s1[i-1] == s2[j-1]:
                    solutions_matrix[i][j] = solutions_matrix[i-1][j-1] + 1
                else:
                    solutions_matrix[i][j] = max(solutions_matrix[i][j-1], solutions_matrix[i-1][j])
        return solutions_matrix[n1][n2]

    # s1 = remove_all_from_string(s1, set(s1) - set(s2))
    # s2 = remove_all_from_string(s2, set(s2) - set(s1))
    # return max_child_length_recursive(s1, s2)

    return max_child_length_with_matrix(s1, s2)

def remove_all_from_string(s: str, characters: list) -> str:
    for c in characters:
        s = s.replace(c, "")
    return s


import pandas as pd
from konlpy.tag import Komoran
import tensorflow as tf
import numpy as np
from keras.layers import Dense, Conv1D, GlobalMaxPooling1D, Embedding, Dropout
from keras.models import Sequential
from keras.layers import LSTM, Bidirectional
from keras.models import Sequential, load_model
from keras.metrics import metrics
from keras.callbacks import EarlyStopping, ModelCheckpoint
from sklearn.model_selection import train_test_split
from keras import backend as K
import pickle

# def recall_m(y_true, y_pred):
#     true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
#     possible_positives = K.sum(K.round(K.clip(y_true, 0, 1)))
#     recall = true_positives / (possible_positives + K.epsilon())
#     return recall

# def precision_m(y_true, y_pred):
#     true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
#     predicted_positives = K.sum(K.round(K.clip(y_pred, 0, 1)))
#     precision = true_positives / (predicted_positives + K.epsilon())
#     return precision

# def f1_m(y_true, y_pred):
#     precision = precision_m(y_true, y_pred)
#     recall = recall_m(y_true, y_pred)
#     return 2*((precision*recall)/(precision+recall+K.epsilon()))

data = pd.read_csv('data_spacing.csv')
X_data = data['text']
y_data = data['isAbuse']
y_data = pd.to_numeric(y_data)
tokened = []

stop_words = []
ft = open('stopword.txt', 'r')
lines = ft.readlines()
for i in lines:
  i = i.rstrip()
  i=i.split(",")

  for j in i:
    stop_words.append(j)
ft.close()

komoran = Komoran()

for i in X_data:

  word_tokens = komoran.morphs(i)
  word_tokens = [word for word in word_tokens if not word in stop_words]
  tokened.append(word_tokens)

tokenizer = tf.keras.preprocessing.text.Tokenizer()
tokenizer.fit_on_texts(tokened)
threshold = 2
total_cnt = len(tokenizer.word_index) # 단어의 수
rare_cnt = 0 # 등장 빈도수가 threshold보다 작은 단어의 개수를 카운트
total_freq = 0 # 훈련 데이터의 전체 단어 빈도수 총 합
rare_freq = 0 # 등장 빈도수가 threshold보다 작은 단어의 등장 빈도수의 총 합
for key, value in tokenizer.word_counts.items():
    total_freq = total_freq + value

    # 단어의 등장 빈도수가 threshold보다 작으면
    if(value < threshold):
        rare_cnt = rare_cnt + 1
        rare_freq = rare_freq + value

print('단어 집합(vocabulary)의 크기 :',total_cnt)
print('등장 빈도가 %s번 이하인 희귀 단어의 수: %s'%(threshold - 1, rare_cnt))
print("단어 집합에서 희귀 단어의 비율:", (rare_cnt / total_cnt)*100)
print("전체 등장 빈도에서 희귀 단어 등장 빈도 비율:", (rare_freq / total_freq)*100)

vocab_size = total_cnt - rare_cnt + 1
print('단어 집합의 크기 :',vocab_size)

X_data = tokenizer.texts_to_sequences(tokened)

drop_data = [index for index, sentence in enumerate(X_data) if len(sentence) < 1]

paddedX = tf.keras.preprocessing.sequence.pad_sequences(X_data, padding='post', maxlen=100)
X_data = np.array(paddedX)
X_data = np.delete(X_data, drop_data, axis=0)

y_data = np.array(y_data)
y_data = np.delete(y_data, drop_data, axis=0)

with open('tokenizer.pickle', 'wb') as handle:
     pickle.dump(tokenizer, handle)

print(X_data[0].shape)
print(X_data[1].shape)
X_train, X_test, y_train, y_test = train_test_split(X_data, y_data, test_size=0.2, random_state=1, stratify=y_data, shuffle=True)

embedding_dim = 128 # 임베딩 벡터의 차원
dropout_ratio = 0.5 # 드롭아웃 비율
num_filters = 128 # 커널의 수
kernel_size = [15,10] # 커널의 크기
hidden_units = 128 # 뉴런의 수

model = Sequential()
model.add(Embedding(vocab_size, embedding_dim))
model.add(Dropout(dropout_ratio))

model.add(Conv1D(num_filters, kernel_size[0], padding='valid', activation='swish', strides = 1))
model.add(Conv1D(num_filters, kernel_size[1], padding='valid', activation='swish', strides = 1))
model.add(GlobalMaxPooling1D(keepdims=True))
model.add(Bidirectional(LSTM(hidden_units))) # Bidirectional LSTM을 사용
model.add(Dense(1, activation='sigmoid'))

es = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=5)
mc = ModelCheckpoint('best_model.h5', monitor='val_acc', mode='max', verbose=1, save_best_only=True)
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['acc'])
# model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['acc' ,precision_m, recall_m, f1_m])
history = model.fit(X_train, y_train, epochs=30, callbacks=[es, mc], batch_size=64, validation_split=0.2)

print("\n 테스트 정확도: %.4f" % (model.evaluate(X_test, y_test)[1]))


## This module aims to compute the number and energy flux of DM particles from a supernova
import numpy as np
import matplotlib.pyplot as plt
from ..Step1_Kinematics import Kinematics
import scipy.integrate as integrate

#Particle Property
#Neutrino
M_nu = 0.32 # Unit:eV/c2
E_total_nu = 3.6e53*6.24150913e11 #Total energy of neutrinos #Transfer 2e51erg to unit of eV
E_per_nu = 10e6 #Mean energy of each neutrino #estimated value

#DM
M_DM = 1e03

#NFW Parameter
rho_s = 0.184e9
rs=24.42*3.08567758e21

#cross section (Neutrino and DM)
cs = 1e-30

def DM_flux(m_dm,e_per_nu,start,end,n_total):
    gamma = Kinematics.energy_kicked_by_neutrino(E_per_nu, M_nu,m_dm)/m_dm
    beta = (1-gamma**(-2))**0.5
    R = (np.sum((start-end)**2))**0.5
    pos = -start+end
    time_delay = R*(1/beta-1)/3e10
    print("time delay(s):"+str(time_delay))

    T = R/3e10
    t = np.linspace(0,time_delay,100)

    def n_ori(l):

        def get_mod(x):
            x2 = x**2
            return (x2[:,0] + x2[:,1] +x2[:,2])**0.5

        r= get_mod(np.tensordot(l,pos,axes=0)/R+np.tensordot(np.ones(l.shape),start,axes=0))

        x= r/rs
        return 1/(x*(1+x)*(1+x))

    def n(t):
        l = 3e10*(T - beta*t/(1-beta))
        l[-1]= 0.
        return n_ori(l)
    c = 3e10 #in unit of cm/s
    phi = cs *n_total*rho_s/m_dm/(4*np.pi*(R**2))*n(t) *beta/(1-beta)*c

    plt.plot(t, phi, color ='blue', label = 'DM Flux')
    plt.xlabel('Time (s)')
    plt.ylabel('Flux (#/cm^2*s)')
    plt.legend(loc='upper right')

    plt.show()

def DM_number(m_dm,e_per_nu,start,end,n_total):
    gamma = Kinematics.energy_kicked_by_neutrino(E_per_nu, M_nu,m_dm)/m_dm
    beta = (1-gamma**(-2))**0.5
    time_delay = np.sum((start-end)**2)**0.5*(1/beta-1)/(3e10)
    print("time delay:"+str(time_delay))

    R = (np.sum((start-end)**2))**0.5
    l = end -start
    def f(t):
        r=(np.sum((start+l*t)**2))**0.5
        x= r/rs
        return 1/(x*(1+x)*(1+x))
    k = n_total*rho_s*cs/m_dm/(4*np.pi) /R

    L_dm = integrate.nquad(f, [[0,1.]])[0]*k
    L_nu = n_total/(4*np.pi*R*R)

    print("DM Number(1/cm^2):"+str(L_dm))
    print("Neutrino Number(1/cm^2):"+str(L_nu))

if __name__== '__main__':

    print("Total number of neutrino:"+str(E_total_nu/E_per_nu))
    start=np.array([0.87*3.08567758e21,0,2.4*3.08567758e18])
    end =np.array([8.7*3.08567758e21,0,24*3.08567758e18])

    DM_number(M_DM,E_per_nu ,start,end,E_total_nu/E_per_nu)
    DM_flux(M_DM,E_per_nu ,start,end,E_total_nu/E_per_nu)


import numpy as np

# Sigmoid function used for activation function
def sigmoid(x):
    return 1.0 / (1 + np.exp(-x))

# Derivative of the sigmoid function, used during backpropagation
def sigmoid_derivative(x):
    return x * (1.0 - x)

# MLP Class
class MLP:
    # Constructor of MLP class
    def __init__(self, x, y, hidden_size):
        self.input = x  # Input data
        self.weights1 = np.random.rand(self.input.shape[1], hidden_size)  # Weights for input layer
        self.weights2 = np.random.rand(hidden_size, 1)  # Weights for hidden layer
        self.y = y  # Output data
        self.output = np.zeros(y.shape)  # Current output of the network

    # Forward propagation through the network
    def feedforward(self):
        self.layer1 = sigmoid(np.dot(self.input, self.weights1))  # First layer after activation
        self.output = sigmoid(np.dot(self.layer1, self.weights2))  # Output layer after activation

    # Backpropagation through the network (used for learning)
    def backprop(self, learning_rate):
        # Application of the chain rule to compute derivative of the loss function with respect to weights2 and weights1
        d_weights2 = np.dot(self.layer1.T, 2 * (self.y - self.output) * sigmoid_derivative(self.output))
        d_weights1 = np.dot(self.input.T, np.dot(2 * (self.y - self.output) * sigmoid_derivative(self.output),
                                                 self.weights2.T) * sigmoid_derivative(self.layer1))

        # Update weights with the derivative of the loss function
        self.weights1 += learning_rate * d_weights1
        self.weights2 += learning_rate * d_weights2

    # Function to predict the output for a given input
    def predict(self, x):
        self.input = x
        self.feedforward()  # Generate output
        return self.output  # Return generated output

    # Cost function of the network
    def costFunction(self):
        self.feedforward()  # Generate output
        # Return the sum of square difference between the network output and actual output
        return np.sum((self.y - self.output) ** 2)

    # Training function of the network
    # Method for building (training) the model
    def buildModel(self, learning_rate=0.01, epochs=1000):
        # The for loop will repeat 'epochs' number of times.
        # Each iteration represents one full pass (forward and backward) over the entire dataset which is one epoch.
        for epoch in range(epochs):
            # First, the forward pass - the model applies weights to the inputs and runs through the network layers
            self.feedforward()

            # Second, the backward pass (Backpropagation) - the model adjusts its weights based on the error from the forward pass
            self.backprop(learning_rate)

            # For every 100th epoch (i.e., every time the remainder of epoch/100 is zero)
            if epoch % 100 == 0:
                # Print the current epoch number and the cost function value
                # This provides a visibility into how the training is going - the cost should be going down over time
                print("Epoch: {} - Cost: {}".format(epoch, self.costFunction()))

def test():
    # Dados de entrada
    X = np.array([[0, 0, 1],
                  [0, 1, 1],
                  [1, 0, 1],
                  [1, 1, 1]])
    # Dados de saída
    y = np.array([[0], [1], [1], [0]])

    mlp = MLP(X, y, hidden_size=4)

    # Construir modelo
    mlp.buildModel(learning_rate=0.5, epochs=1500)

    # Prever saída para um exemplo
    print(mlp.predict(np.array([1, 0, 1])))

if __name__ == '__main__':
    test()


from sympy import isprime
import random
import re

class ElGammal:
    def __init__(self):
        self.p=0
        self.alpha=0
        self.private_a=0
        self.beta=0

        # self.p=11
        # self.alpha=2
        # self.private_a=3
        # self.beta=8

    def genKey(self):
        primes_list=[i for i in range(676,10000) if isprime(i)] #El rango es para codificar el bloque sin colisión
        self.p=random.choice(primes_list)
        self.alpha=random.randint(1,self.p-1)
        self.private_a=random.randint(1,self.p-2)
        self.beta=pow(self.alpha,self.private_a,self.p)
        self.m=random.randint(1,self.p-2)

    def setKey(self,p,alpha,private_a):
        self.p=p
        self.alpha=alpha%p
        self.private_a=private_a%p
        self.beta=pow(self.alpha,self.private_a,self.p)
        self.m=random.randint(1,self.p-2)

    def preprocess_stringv3(self,s):
        s=re.sub('[^a-zA-Z]',"",s) #Elimina todo lo que no sean letras(espacios,números y otros)
        s=s.lower()
        # s=s[::-1]
        while len(s)%2!=0:
            s+='a'
        return s

    def block_convertv2(self,s,n,b=2): #Cada bloque es de 2 letras
        s=self.preprocess_stringv3(s)
        # s=s[::-1]
        b=[s[i:i+b] for i in range(0,len(s),b)]
        num_arr=[]
        for bi in b:
            l=len(bi)
            num=0
            for i in range(l):
                num+=((ord(bi[i])-96))*26**i
            num_arr.append(num%n)
        return num_arr

    def num_to_text(self,arr):
        decimal_text=[]
        final_text=[]
        for block in arr:
            dec_num=[]
            cond=True
            while cond:
                dec_num.append(block%26)
                if block//26==0:
                    cond=False
                block//=26
            decimal_text.append(dec_num)
        final_string=[]
        for char in decimal_text:
            s=''
            for n in char:
                s+=(chr(n+96))
            final_string.append(s)
        final_text.append(final_string)
        message=''
        for s in final_text[0]:
            message+=s
        return message

    def encrypt(self,x): #x es el mensaje a cifrar
        x_=self.block_convertv2(x,self.p)
        encrypt_m=[]
        for x in x_:
            y1=pow(self.alpha,self.m,self.p)
            y2=(pow(self.beta,self.m,self.p)*x)%self.p
            encrypt_m.append((y1,y2))
        return encrypt_m

    def extended_gcd(self,a, b):
        if a == 0:
            return b, 0, 1
        else:
            gcd, x, y = self.extended_gcd(b % a, a)
            return gcd, y - (b // a) * x, x

    def inverse_mod(self,a,n):
        return self.extended_gcd(a,n)[1]

    def decrypt(self,ys):
        sol=[]
        for ys_ in ys:
            y1,y2=ys_
            sol.append(y2*(self.inverse_mod(pow(y1,self.private_a,self.p),self.p))%self.p)
        return sol

# gammal=ElGammal()
# gammal.genKey()
# m='This is a long proof'
# print(gammal.preprocess_stringv3(m))
# print(gammal.block_convertv2(m,gammal.p))
# e=gammal.encrypt(m)
# print(e)
# d=gammal.decrypt(e)
# print(d)
# print(gammal.num_to_text(d))


import os
os.system('cls')

from math import sqrt

from functools import reduce

class chofer():
    def __init__(self, nombreCompleto):

        self.__nombreCompleto = nombreCompleto

    def getNombreCompleto(self):
        return self.__nombreCompleto

class camion():
    def __init__(self, patente, litrosDisponibles, ciudadActual, kmLitro, velMaxima):

        self.__patente= patente
        self.__litrosDiposnibles= litrosDisponibles
        self.__ciudadActual = ciudadActual
        self.__kmLitro =kmLitro
        self.__velMaxima = velMaxima

    def getPatente(self):
        return self.__patente

    def getVelMaxima(self):
        return self.__velMaxima

    def getCiudadActual(self):
        return self.__ciudadActual
    def setCiudadActual(self, AC):
        self.__ciudadActual = AC
    CiudadActual = property (getCiudadActual, setCiudadActual)

    def getLitrosDisponibles(self):
        return self.__litrosDiposnibles
    def setLitrosDisponibles(self, LD):
        self.__litrosDiposnibles = LD
    LitrosDisponibles = property (getLitrosDisponibles, setLitrosDisponibles)

    def getKmLitros(self):
        return self.__kmLitro

Juan = chofer("Juan Mari")
Martin = chofer("Martin Torres")
Agustin = chofer ("Agustin De Luca")

camion1=camion ("ARG123",60,"Lomas",3, 60)
camion2=camion ("BRZ456",60,"Lanus", 5, 80)
camion3=camion ("URG678",60,"Escalada", 4,60)

def decorador(funcion):
    def nuevaFuncion(*args):
        print("ARCHIVO ABIERTO")
        funcion(*args)
        print("ARCHIVO CERRADO")
    return nuevaFuncion

def cargarRecorrido():
    destino=[]
    while True:
        ciudades=["Lanus", "Lomas", "Escalada", "Banfield"]
        print("seleccionar ciudades")
        print(f"1= {ciudades[0]}")
        print(f"2= {ciudades[1]}")
        print(f"3= {ciudades[2]}")
        print(f"4= {ciudades[3]}")
        print("0= terminar carga ")
        x=int(input())
        if(x!=0):
            x=x-1
            destino.append(ciudades[x])
            print(destino)
        else:
            break
    return destino

def sumar(x,y):
    return x+y

@decorador
def guardar(estimacion):
    archivo = "ArchivoParcial2.txt"
    while True:
        try:
            with open (archivo, "a") as a:
                a.write(f"\n{estimacion}")
                print("VIAJE GUARDADO")
                break
        except:
            print("Error al intentar abrir")
            print (f"No se encuentra el archivo {archivo}, especifique su nombre correctamente:")
            archivo = (input("Nombre de archivo:"))

@decorador
def leer():
    archivo = "ArchivoParcial2.txt"
    while True:
        try:
            with open (archivo, "r") as a:
                contenido = a.read()
                print(contenido)
                break
        except:
            print("Error al intentar abrir")
            print (f"No se encuentra el archivo {archivo}, especifique su nombre correctamente:")
            archivo = (input("Nombre de archivo:"))

def estimarViaje(camion, recorrido, chofer):

    estimado=["","","","","",""]
    estimado[0]=camion.getPatente()
    estimado[5]=chofer.getNombreCompleto()

    d = {
    "Lanus": (40,30),
    "Lomas": (20,10),
    "Banfield": (12,30),
    "Escalada": (10, 34) }

    km=[]

    for x in recorrido:

        x1=d[camion.CiudadActual][0]
        x2=d[x][0]
        y1=d[camion.CiudadActual][1]
        y2=d[x][1]

        resultado= sqrt((x2-x1)**2 + (y2-y1)**2)
        km.append(resultado)
        camion.CiudadActual=x

    kmTotal= reduce(sumar, km)
    print(f"total de kilometros: {kmTotal}")
    estimado[1]= f"{kmTotal} kmTotal"

    tiempoEstimado= int((kmTotal/camion.getVelMaxima()))
    print(f"tiempo estimado: {tiempoEstimado} hora/s")

    paradas=len(recorrido)
    tiempoTotal=tiempoEstimado+paradas
    print(f"tiempo estimado mas paradas: {tiempoTotal} hora/s")
    estimado[2]=f"{tiempoTotal} hora/s"

    consumo=int((kmTotal*camion.getKmLitros())/1)
    print("consumo: "+str(consumo))

    if(consumo >= camion.LitrosDisponibles):
        print("se requieren mas litros")
        camion.LitrosDisponibles+=1000
        print("se cargaron 1000 litros al camion")
        camion.LitrosDisponibles= camion.LitrosDisponibles-consumo
        estimado[3]="Si Se cargo el tanque"
    else:
        print("no se necesito cargar el tanque")
        camion.LitrosDisponibles= camion.LitrosDisponibles-consumo
        estimado[3]="NO se cargo el tanque"

    if(kmTotal>=1000):
        estimado[4]="Si supero los 1000km"
    else:
        estimado[4]="NO supero los 1000km"

    guardar(estimado)

while True:
    print("seleccione camion")
    print(f"1= {camion1.getPatente()}")
    print(f"2= {camion2.getPatente()}")
    print(f"3= {camion3.getPatente()}")
    print("4= leer resumen")
    print("0= EXIT")
    x = int(input())
    if(x==1):
        print(f"seleccionaste a {camion1.getPatente()}")
        recorrido = cargarRecorrido()
        print (recorrido)
        estimarViaje(camion1, recorrido, Juan)
    elif(x==2):
        print(f"seleccionaste a {camion2.getPatente()}")
        recorrido = cargarRecorrido()
        estimarViaje(camion2,recorrido, Martin)
    elif(x==3):
        print(f"seleccionaste a {camion3.getPatente()}")
        recorrido = cargarRecorrido()
        estimarViaje(camion3, recorrido, Agustin)
    elif(x==4):
        leer()
    elif(x==0):
        break
    else:
        print("valor incorrecto")