import os

from django.utils.translation import ugettext_lazy as _

BUILT_IN_TAGS = [_("主机"), _("消息中间件"), _("HTTP服务"), _("数据库"), _("办公应⽤"), _("其他")]

HEARTBEAT_MESSAGE_ID = 101178

BEAT_RUN_ERR = 4001  # 脚本运行报错
BEAT_PARAMS_ERR = 4002  # 脚本命令不合法
BEAT_FORMAT_OUTPUT_ERR = 4003  # 解析脚本标准输出报错

BEAT_ERR = {
    BEAT_RUN_ERR: _("脚本运行报错,原因是{}"),
    BEAT_PARAMS_ERR: _("脚本命令不合法,原因是{}"),
    BEAT_FORMAT_OUTPUT_ERR: _("解析脚本标准输出报错,原因是{}"),
}

OS_TYPE_TO_DIRNAME = {
    "windows": "external_plugins_windows_x86_64",
    "linux": "external_plugins_linux_x86_64",
    "linux_aarch64": "external_plugins_linux_aarch64",
    "aix": "external_plugins_aix_powerpc",
}
OS_TYPE_TO_SCRIPT_SUFFIX = {"shell": ".sh", "bat": ".bat", "custom": ""}

INNER_DIMENSIONS = [
    "bk_biz_id",
    "bk_target_ip",
    "bk_target_cloud_id",
    "bk_target_topo_level",
    "bk_target_topo_id",
    "bk_target_service_category_id",
    "bk_target_service_instance_id",
    "bk_collect_config_id",
]

PLUGIN_TEMPLATES_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "plugin_templates")

# config_json 字段类型选项
class ParamMode(object):
    # 采集器参数
    COLLECTOR = "collector"
    # 命令行选项参数
    OPT_CMD = "opt_cmd"
    # 命令行位置参数
    POS_CMD = "pos_cmd"
    # 环境变量
    ENV = "env"
    # 维度注入
    DMS_INSERT = "dms_insert"

PARAM_MODE_CHOICES = [
    ParamMode.COLLECTOR,
    ParamMode.OPT_CMD,
    ParamMode.POS_CMD,
    ParamMode.ENV,
    ParamMode.DMS_INSERT,
]

SCRIPT_TYPE_CHOICES = (
    "shell",
    "bat",
    "python",
    "perl",
    "powershell",
    "vbs",
    "custom",
)

PROCESS_MATCH_TYPE_CHOICES = (
    "pid",
    "command",
)

class NodemanRegisterStatus(object):
    FAILED = "FAILED"
    RUNNING = "RUNNING"
    SUCCESS = "SUCCESS"

class DebugStatus(object):
    INSTALL = "INSTALL"
    FETCH_DATA = "FETCH_DATA"
    FAILED = "FAILED"
    SUCCESS = "SUCCESS"

class PluginType(object):
    EXPORTER = "Exporter"
    SCRIPT = "Script"
    JMX = "JMX"
    DATADOG = "DataDog"
    PUSHGATEWAY = "Pushgateway"
    BUILT_IN = "Built-In"
    LOG = "Log"
    PROCESS = "Process"
    SNMP_TRAP = "SNMP_Trap"
    SNMP = "SNMP"

class ConflictMap(object):
    """用于插件导入的冲突提示，此处国际化在调用方进行"""

    class VersionBelow(object):
        id = 1
        info = _("导入版本不大于当前版本")

    class PluginType(object):
        id = 2
        info = _("插件类型冲突")

    class RemoteCollectorConfig(object):
        id = 3
        info = _("远程采集配置项冲突")

    class RelatedCollectorConfig(object):
        id = 4
        info = _("插件已关联%s个采集配置")

    class DuplicatedPlugin(object):
        id = 5
        info = _("导入插件与当前插件内容完全一致")

SUPPORT_REMOTE_LIST = [PluginType.PUSHGATEWAY, PluginType.JMX, PluginType.SNMP]

# SNMP Trap插件默认运行参数
DEFAULT_TRAP_CONFIG = {
    "server_port": "162",
    "listen_ip": "0.0.0.0",
    "yaml": {"filename": "", "value": ""},
    "community": "",
    "aggregate": True,
}
DEFAULT_TRAP_V3_CONFIG = {
    "version": "v3",
    "auth_info": [
        {
            "security_level": "noAuthNoPriv",
            "security_name": "",
            "context_name": "",
            "authentication_protocol": "MD5",
            "authentication_passphrase": "",
            "privacy_protocol": "DES",
            "privacy_passphrase": "",
            "authoritative_engineID": "",
        }
    ],
}

SNMP_MAX_METRIC_NUM = 500
MAX_METRIC_NUM = 2000

PLUGIN_REVERSED_DIMENSION = [
    ("bk_target_ip", _("目标IP")),
    ("bk_target_cloud_id", _("云区域ID")),
    ("bk_target_topo_level", _("拓扑层级")),
    ("bk_target_topo_id", _("拓扑ID")),
    ("bk_target_service_category_id", _("服务类别ID")),
    ("bk_target_service_instance_id", _("服务实例")),
    ("bk_collect_config_id", _("采集配置ID")),
    ("bk_target_host_id", _("目标主机ID")),
    ("bk_host_id", _("采集主机ID")),
    ("time", _("数据上报时间")),
    ("bk_biz_id", _("业务ID")),
    ("bk_supplier_id", _("开发商ID")),
    ("bk_cloud_id", _("采集器云区域ID")),
    ("ip", _("采集器IP")),
    ("bk_cmdb_level", _("CMDB层级信息")),
    ("bk_agent_id", _("Agent ID")),
]

ORIGIN_PLUGIN_EXCLUDE_DIMENSION = [
    "bk_host_id",
    "bk_target_host_id",
    "bk_biz_id",
    "ip",
    "time",
    "bk_supplier_id",
    "bk_cloud_id",
    "bk_cmdb_level",
    "bk_agent_id",
]


"""       nft approval and rejection panel for staff      """

from django.db.models import F
from rest_framework.decorators import api_view
from rest_framework import status
from rest_framework.response import Response
from django.db.models import Q
from rest_framework.pagination import PageNumberPagination

from ethereum.models import EthBalance
from nft.models import Nft
from nft.serializers import NftSerializer

# Views

@api_view(['GET'])
def nft_unapproved(request):
    try:
        nft = Nft.objects.filter(approved=False)
        paginator = PageNumberPagination()
        paginator.page_size = 10
        result_page = paginator.paginate_queryset(nft, request)
        serializer = NftSerializer(result_page, many=True)
        return paginator.get_paginated_response(serializer.data)
    except Exception as e:
        return Response({'error': f'{e}'})

@api_view(['PATCH'])
def nft_approve(request, pk):
    try:
        nft = Nft.objects.get(id=pk)
        serializer = NftSerializer(nft, data=request.data, partial=True)
        if serializer.is_valid():
            serializer.save()

            # update the eth balance of an user
            """
                User get rewarded on every NFT they uplode.
                User only get eth after we approved/verified the NFT uploded by the user
            """
            if nft.approved == True:
                try:
                    eth_user = EthBalance.objects.get(user=nft.user)
                    if eth_user:
                        eth_user.eth_balance = F('eth_balance')+0.00000017
                        eth_user.save()
                        print("SUCCESSFULL UPDATED")

                #  If user uplode's their first NFT
                except Exception as e:
                    print("CREATING ETH WALLET")
                    eth_user = EthBalance.objects.create(
                            user = nft.user
                        )
                    eth_user.save()
                    print("SUCCESS")

            return Response(serializer.data, status=status.HTTP_200_OK)
        else:
            return Response(status=status.HTTP_204_NO_CONTENT)
    except Exception as e:
        return Response({'error': f'{e}'}, status=status.HTTP_204_NO_CONTENT)

@api_view(['DELETE'])
def nft_reject(request, pk):
    try:
        nft = Nft.objects.get(id=pk)
        if request.user.is_staff == True:
            nft.delete()
            return Response(status=status.HTTP_200_OK)
        else:
            return Response(status=status.HTTP_204_NO_CONTENT)
    except Exception as e:
        return Response({'error': f'{e}'})


# -*- coding: utf-8 -*-
import matplotlib.pyplot as plt
import numpy as np

from .signal_period import signal_period

def signal_rate(
    peaks,
    sampling_rate=1000,
    desired_length=None,
    interpolation_method="monotone_cubic",
    show=False,
):
    """**Compute Signal Rate**

    Calculate signal rate (per minute) from a series of peaks. It is a general function that works
    for any series of peaks (i.e., not specific to a particular type of signal). It is computed as
    ``60 / period``, where the period is the time between the peaks (see func:`.signal_period`).

    .. note:: This function is implemented under :func:`.signal_rate`, but it also re-exported under
       different names, such as :func:`.ecg_rate`, or :func:`.rsp_rate`. The
       aliases are provided for consistency.

    Parameters
    ----------
    peaks : Union[list, np.array, pd.DataFrame, pd.Series, dict]
        The samples at which the peaks occur. If an array is passed in, it is assumed that it was
        obtained with :func:`.signal_findpeaks`. If a DataFrame is passed in, it is assumed it is
        of the same length as the input signal in which occurrences of R-peaks are marked as "1",
        with such containers obtained with e.g., :func:.`ecg_findpeaks` or :func:`.rsp_findpeaks`.
    sampling_rate : int
        The sampling frequency of the signal that contains peaks (in Hz, i.e., samples/second).
        Defaults to 1000.
    desired_length : int
        If left at the default None, the returned rated will have the same number of elements as
        ``peaks``. If set to a value larger than the sample at which the last peak occurs in the
        signal (i.e., ``peaks[-1]``), the returned rate will be interpolated between peaks over
        ``desired_length`` samples. To interpolate the rate over the entire duration of the signal,
        set ``desired_length`` to the number of samples in the signal. Cannot be smaller than or
        equal to the sample at which the last peak occurs in the signal. Defaults to ``None``.
    interpolation_method : str
        Method used to interpolate the rate between peaks. See :func:`.signal_interpolate`.
        ``"monotone_cubic"`` is chosen as the default interpolation method since it ensures monotone
        interpolation between data points (i.e., it prevents physiologically implausible
        "overshoots" or "undershoots" in the y-direction). In contrast, the widely used cubic
        spline interpolation does not ensure monotonicity.
     show : bool
        If ``True``, shows a plot. Defaults to ``False``.

    Returns
    -------
    array
        A vector containing the rate (peaks per minute).

    See Also
    --------
    signal_period, signal_findpeaks, signal_fixpeaks, signal_plot

    Examples
    --------
    .. ipython:: python

      import neurokit2 as nk

      # Create signal of varying frequency
      freq = nk.signal_simulate(1, frequency = 1)
      signal = np.sin((freq).cumsum() * 0.5)

      # Find peaks
      info = nk.signal_findpeaks(signal)

      # Compute rate using 2 methods
      rate1 = nk.signal_rate(peaks=info["Peaks"],
                             desired_length=len(signal),
                             interpolation_method="nearest")

      rate2 = nk.signal_rate(peaks=info["Peaks"],
                             desired_length=len(signal),
                             interpolation_method="monotone_cubic")

      # Visualize signal and rate on the same scale
      @savefig p_signal_rate1.png scale=100%
      nk.signal_plot([signal, rate1, rate2],
                     labels = ["Original signal", "Rate (nearest)", "Rate (monotone cubic)"],
                     standardize = True)
      @suppress
      plt.close()

    """
    period = signal_period(peaks, sampling_rate, desired_length, interpolation_method)
    rate = 60 / period

    if show is True:
        _signal_rate_plot(rate, peaks, sampling_rate, interpolation_method)

    return rate

# =============================================================================
# Internals
# =============================================================================
def _signal_rate_plot(
    rate,
    peaks,
    sampling_rate=None,
    interpolation_method=None,
    title="Rate",
    ytitle="Cycle per minute",
    color="black",
    color_mean="orange",
    color_points="red",
    ax=None,
):
    # Prepare plot
    if ax is None:
        fig, ax = plt.subplots()

    if sampling_rate is None:
        x_axis = np.arange(0, len(rate))
        ax.set_xlabel("Time (samples)")
    else:
        x_axis = np.linspace(0, len(rate) / sampling_rate, len(rate))
        ax.set_xlabel("Time (seconds)")

    if interpolation_method is not None:
        title += " (interpolation method: " + str(interpolation_method) + ")"
    ax.set_title(title)
    ax.set_ylabel(ytitle)

    # Plot continuous rate
    ax.plot(
        x_axis,
        rate,
        color=color,
        label="Rate",
        linewidth=1.5,
    )

    # Plot points
    if peaks is not None:
        ax.scatter(
            x_axis[peaks],
            rate[peaks],
            color=color_points,
        )

    # Show average rate
    rate_mean = rate.mean()
    ax.axhline(y=rate_mean, label="Mean", linestyle="--", color=color_mean)

    ax.legend(loc="upper right")

    return ax


import pygame
import sys
import time
import random
from pygame.locals import *

pygame.init()
pygame.font.init()
myfont = pygame.font.SysFont('Comic Sans MS', 30)
# timeDead is for auto die and timeGap is for respawn time
timeDead = 8
timeGap, display_width, display_height = 10, 720, 720

black = (0, 0, 0)
white = (255, 255, 255)
global_count = [0]

yy = 90
xx = 90

gameDisplay = pygame.display.set_mode((display_width, display_height))
pygame.display.set_caption('Space Invaders')
clock = pygame.time.Clock()
pygame.mouse.set_visible(0)
#######################################################

# store an array of images
img_array = [1, 2, 3, 4, 5]
img_array[0] = pygame.image.load("ship.png")
img_array[0] = pygame.transform.scale(img_array[0], (int(xx), int(yy)))
img_array[1] = pygame.image.load("a1.png")
img_array[1] = pygame.transform.scale(img_array[1], (int(xx), int(yy)))
img_array[2] = pygame.image.load("a2.png")
img_array[2] = pygame.transform.scale(img_array[2], (int(xx), int(yy)))
img_array[3] = pygame.image.load("m1.png")
img_array[3] = pygame.transform.scale(img_array[3], (int(xx), int(yy)))
img_array[4] = pygame.image.load("m2.png")
img_array[4] = pygame.transform.scale(img_array[4], (int(xx), int(yy)))

alien_list = []
missile1_list = []
missile2_list = []
time_start = []
prev_time = []


import numpy as np
import tensorflow as tf
from PIL import Image
from os import listdir
from os.path import isfile, join

#settings
np.set_printoptions(threshold=np.nan)
unique_characters = 10
number_of_images = 1
number_of_test_images = 1944
training_steps = 100
directory = "/home/ollie/work/fyp"
scaled_image_size = (32,32)

if number_of_images + number_of_test_images > 1945: #1945 0s in dataset
    print("number_of_images + number_of_test_images > 1945")
    quit()

def extract_all(total):
    l = []
    for num in range(total):
        numbers = []
        names = [f for f in listdir(directory + "/digits/" + str(num)) if isfile(join(directory + "/digits/" + str(num), f))]
        total = 0
        for loc in names:
            if total < number_of_images + number_of_test_images:
                with Image.open(directory + "/digits/" + str(num) + "/" + loc) as imageFile:
                    image = imageFile
                    image = image.resize(scaled_image_size,resample = Image.BILINEAR)
                    image = np.array(image)
                    image = image[:, :, 3]
                    image = np.divide(image,255)
                    image = image.flatten()
                    numbers.append(image)
                total += 1
            else:
                break
        print("    "+str(num)+" complete")
        l.append(numbers)
    return l

#import images data
print("Loading raw images...")

all_images = extract_all(unique_characters)

print("Import complete.")

#generate images
print("Generating training images...")

images = []

for i in range(unique_characters):
    images.extend(all_images[i][:number_of_images])

#generate labels
print("Generating training labels...")

def label_generator(num):
    label = [0] * unique_characters
    label[num] = 1
    return label

labels = []
for i in range(unique_characters):
    for j in range(number_of_images):
        labels.append(label_generator(i))

#generate test images
print("Generating test images...")

test_images = []
for i in range(unique_characters):
    test_images.append(all_images[i][number_of_images:number_of_images+number_of_test_images])

#generate test lables

test_labels = []
for i in range(unique_characters):
    for j in range(number_of_test_images):
        test_labels.append(label_generator(i))

#training
def weight_variable(shape):
  initial = tf.truncated_normal(shape, stddev=0.1)
  return tf.Variable(initial)

def bias_variable(shape):
  initial = tf.constant(0.1, shape=shape)
  return tf.Variable(initial)

def conv2d(x, W):
  return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')

def max_pool_2x2(x):
  return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],
                        strides=[1, 2, 2, 1], padding='SAME')

# Input layer
x  = tf.placeholder(tf.float32, [None, scaled_image_size[0] * scaled_image_size[1]], name='x')
y_ = tf.placeholder(tf.float32, [None, unique_characters],  name='y_')
x_image = tf.reshape(x, [-1, scaled_image_size[0], scaled_image_size[1], 1])

# Convolutional layer 1
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])

h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)

# Convolutional layer 2
W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])

h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)

# Fully connected layer 1
h_pool2_flat = tf.reshape(h_pool2, [-1, int(scaled_image_size[0]/4)*int(scaled_image_size[1]/4)*64])

W_fc1 = weight_variable([int(scaled_image_size[0]/4) * int(scaled_image_size[1]/4) * 64, 1024])
b_fc1 = bias_variable([1024])

h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)

# Dropout
keep_prob  = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

# Fully connected layer 2 (Output layer)
W_fc2 = weight_variable([1024, unique_characters])
b_fc2 = bias_variable([unique_characters])

y = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2, name='y')

# Evaluation functions
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1]))

# Training algorithm
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)

# Training steps
sess = tf.InteractiveSession()
sess.run(tf.global_variables_initializer())

print("Beginning training...")

for step in range(training_steps):
  sess.run(train_step, feed_dict={x: images, y_: labels, keep_prob: 0.5})
  print("    "+str(step)+"/"+str(training_steps), end="\r")

print("Training complete.")
print()

#test

def analyse_result(result):
    results = []
    for i in range(unique_characters):
        results.append(result.count(i))
    return results

def analyse(number):
    numberImages = test_images[number]

    results = []

    for i in range(len(numberImages)):
        results.append(np.argmax(sess.run(y,feed_dict={x: [numberImages[i]], keep_prob: 1.0})))

    analysedResults = analyse_result(results)

    def percentageOfTotal(list,loc):
        return list[loc]/sum(list)*100

    print(str(number) + " - " +str(percentageOfTotal(analysedResults,number))+"% accurate.")
    print("--------------------")
    print("Number | Occurrences")
    current = 0
    for result in analysedResults:
        print(str(current)+"      | "+str(result))
        current += 1
    print("--------------------")
    print()

    return analysedResults[number]

totalCorrect = 0

for i in range(unique_characters):
    totalCorrect += analyse(i)

print("Overall accuracy: "+str(totalCorrect*100/(number_of_test_images*unique_characters))+"%")


import pymunk

def get_shape(x, y, space):
    # See if we clicked on anything
    shape_list = space.point_query((x, y), 1, pymunk.ShapeFilter())

    # If we did, remember what we clicked on
    if len(shape_list) > 0:
        shape = shape_list[0]
    else:
        shape = None
    return shape

def make_pin_joint_connection(x, y, space, joints, shape_1, shape_2):
    if get_shape(x, y, space) is None:
        return
    if shape_1 is None:
        print("Shape 1 Found")
        shape_1 = get_shape(x, y, space)
    elif shape_2 is None:
        print("Shape 2 Found")
        shape_2 = get_shape(x, y, space)
        joint = pymunk.PinJoint(shape_1.shape.body, shape_2.shape.body)
        space.add(joint)
        joints.append(joint)
        shape_1 = None
        shape_2 = None
        print("Joint Connection Made")
    return shape_1, shape_2

def make_damped_spring_connection(x, y, space, joints, shape_1, shape_2):
    if get_shape(x, y, space) is None:
        return
    if shape_1 is None:
        print("Shape 1 Found")
        shape_1 = get_shape(x, y, space)
    elif shape_2 is None:
        print("Shape 2 Found")
        shape_2 = get_shape(x, y, space)
        joint = pymunk.DampedSpring(shape_1.shape.body, shape_2.shape.body, (0, 0), (0, 0), 45, 300, 30)
        space.add(joint)
        joints.append(joint)
        shape_1 = None
        shape_2 = None
        print("Spring Connection Made")
    return shape_1, shape_2


'''

-Medium-
*DP*

There is a 3 lane road of length n that consists of n + 1 points labeled from 0 to n. A frog starts at point 0 in the second lane and wants to jump to point n. However, there could be obstacles along the way.

You are given an array obstacles of length n + 1 where each obstacles[i] (ranging from 0 to 3) describes an obstacle on the lane obstacles[i] at point i. If obstacles[i] == 0, there are no obstacles at point i. There will be at most one obstacle in the 3 lanes at each point.

For example, if obstacles[2] == 1, then there is an obstacle on lane 1 at point 2.
The frog can only travel from point i to point i + 1 on the same lane if there is not an obstacle on the lane at point i + 1. To avoid obstacles, the frog can also perform a side jump to jump to another lane (even if they are not adjacent) at the same point if there is no obstacle on the new lane.

For example, the frog can jump from lane 3 at point 3 to lane 1 at point 3.
Return the minimum number of side jumps the frog needs to reach any lane at point n starting from lane 2 at point 0.

Note: There will be no obstacles on points 0 and n.

Example 1:

Input: obstacles = [0,1,2,3,0]
Output: 2
Explanation: The optimal solution is shown by the arrows above. There are 2 side jumps (red arrows).
Note that the frog can jump over obstacles only when making side jumps (as shown at point 2).
Example 2:

Input: obstacles = [0,1,1,3,3,0]
Output: 0
Explanation: There are no obstacles on lane 2. No side jumps are required.
Example 3:

Input: obstacles = [0,2,1,0,3,0]
Output: 2
Explanation: The optimal solution is shown by the arrows above. There are 2 side jumps.

Constraints:

obstacles.length == n + 1
1 <= n <= 5 * 105
0 <= obstacles[i] <= 3
obstacles[0] == obstacles[n] == 0

'''

from typing import List
from random import randint

class Solution:
    def minSideJumps(self, obstacles: List[int]) -> int:
        O, n = obstacles, len(obstacles)-1
        dp = [[float('inf')]*3 for _ in range(n+1)]
        dp[n][0] = dp[n][1] = dp[n][2] = 0
        for i in range(n-1, -1, -1):
            o = O[i] - 1
            if o != 0:
               dp[i][0] = min(dp[i+1][0], 1+dp[i+1][1] if o != 1 else float('inf'), 1+dp[i+1][2] if o != 2 else float('inf'))
            if o != 1:
               dp[i][1] = min(dp[i+1][1], 1+dp[i+1][0] if o != 0 else float('inf'), 1+dp[i+1][2] if o != 2 else float('inf'))
            if o != 2:
               dp[i][2] = min(dp[i+1][2], 1+dp[i+1][1] if o != 1 else float('inf'), 1+dp[i+1][0] if o != 0 else float('inf'))
        # for i in range(3):
        #     for j in range(n+1):
        #         print(dp[j][i],end=' ')
        #     print("\n")
        return dp[0][1]

    def minSideJumps2(self, obstacles: List[int]) -> int:
        O, n = obstacles, len(obstacles)-1
        dp = [[float('inf')]*3 for _ in range(n+1)]
        dp[n][0] = dp[n][1] = dp[n][2] = 0
        for i in range(n-1, -1, -1):
            o = O[i] - 1
            if o == -1:
                dp[i][0] = min(dp[i+1][0], 1+dp[i+1][1], 1+dp[i+1][2])
                dp[i][1] = min(dp[i+1][1], 1+dp[i+1][0], 1+dp[i+1][2])
                dp[i][2] = min(dp[i+1][2], 1+dp[i+1][1], 1+dp[i+1][0])
            elif o == 0:
                dp[i][1] = min(dp[i+1][1], 1+dp[i+1][2])
                dp[i][2] = min(dp[i+1][2], 1+dp[i+1][1])
            elif o == 1:
                dp[i][0] = min(dp[i+1][0], 1+dp[i+1][2])
                dp[i][2] = min(dp[i+1][2], 1+dp[i+1][0])
            else:
                dp[i][0] = min(dp[i+1][0], 1+dp[i+1][1])
                dp[i][1] = min(dp[i+1][1], 1+dp[i+1][0])
        return dp[0][1]

    def minSideJumps3(self, obstacles: List[int]) -> int:
        O, n = obstacles, len(obstacles)-1
        dp_0 = [0]*3
        dp_1 = [0]*3
        for i in range(n-1, -1, -1):
            o = O[i] - 1
            if o == -1:
                dp_1[0] = min(dp_0[0], 1+dp_0[1], 1+dp_0[2])
                dp_1[1] = min(dp_0[1], 1+dp_0[0], 1+dp_0[2])
                dp_1[2] = min(dp_0[2], 1+dp_0[1], 1+dp_0[0])
            elif o == 0:
                dp_1[0] = float('inf')
                dp_1[1] = min(dp_0[1], 1+dp_0[2])
                dp_1[2] = min(dp_0[2], 1+dp_0[1])
            elif o == 1:
                dp_1[1] = float('inf')
                dp_1[0] = min(dp_0[0], 1+dp_0[2])
                dp_1[2] = min(dp_0[2], 1+dp_0[0])
            else:
                dp_1[2] = float('inf')
                dp_1[0] = min(dp_0[0], 1+dp_0[1])
                dp_1[1] = min(dp_0[1], 1+dp_0[0])
            dp_0, dp_1 = dp_1, dp_0
            # print(i, dp_0)
        return dp_0[1]

    def minSideJumps4(self, obstacles: List[int]) -> int:
        O, n = obstacles, len(obstacles)-1
        dp = [0, 0, 0]
        for i in range(n-1, -1, -1):
            if O[i]:
                dp[O[i]-1] = float('inf')
            for k in range(3):
                if O[i] != k+1:
                    dp[k] = min(dp[k], 1+dp[(k+1)%3], 1+dp[(k+2)%3])
        return dp[1]

if __name__ == "__main__":
    print(Solution().minSideJumps(obstacles = [0,1,2,3,0]))
    print(Solution().minSideJumps3(obstacles = [0,1,2,3,0]))
    print(Solution().minSideJumps4(obstacles = [0,1,2,3,0]))
    print(Solution().minSideJumps(obstacles = [0,1,1,3,3,0]))
    print(Solution().minSideJumps4(obstacles = [0,1,1,3,3,0]))
    print(Solution().minSideJumps(obstacles = [0,2,1,0,3,0]))
    print(Solution().minSideJumps4(obstacles = [0,2,1,0,3,0]))
    N = 5*10**5
    obstacles = []
    for i in range(N):
        obstacles.append(randint(0, 3))
    obstacles[0] = 0
    obstacles[N-1] = 0
    # print(Solution().minSideJumps(obstacles = obstacles))
    # print(Solution().minSideJumps2(obstacles = obstacles))
    print(Solution().minSideJumps3(obstacles = obstacles))
    print(Solution().minSideJumps4(obstacles = obstacles))


"""
Stochastic layer for GPi-H BNN prior parameterisation
"""

import math
import torch
import torch.distributions as dist
import torch.nn as nn
import torch.nn.functional as F
import torch.nn.init as init

class HierarchicalLayer(nn.Module):
    def __init__(self, input_dim, output_dim, rbf_dim=None, prior_per='layer', fit_means=True):
        """
        Implementation of each BNN layer, when using fixed Gaussian prior over parameters.

        :param input_dim: int, number of dimensions of previous layer's output (this layer's input)
        :param output_dim: int, number of dimensions of this layer's output
        :param rbf_dim: int, width of embedding layer (number of spatial basis functions)
        :param prior_per: str, indicates either one prior per `layer`, `parameter`, or `input`
        :param fit_means: bool, specify if means are fitted as parameters (set to zero otherwise)
        """
        super().__init__()

        self.input_dim = input_dim
        self.output_dim = output_dim
        self.nonstationary = False
        self.fit_means = fit_means

        # Set dimensions of optimised parameters
        if prior_per == 'layer':
            W_shape, b_shape = 1, 1
        elif prior_per == 'parameter':
            W_shape, b_shape = (input_dim, output_dim), output_dim
        elif prior_per == 'input':
            if rbf_dim is None:
                raise Exception('For nonstationary prior (one per input), require rbf_dim (embedding layer width)')
            W_shape, b_shape = (rbf_dim, 1), (rbf_dim, 1)
            self.nonstationary = True
        else:
            raise ValueError("Accepted values: `parameter`, `layer`, or `input`")

        # Define optimised hyperparameters and require gradient (make autograd record operations)
        if self.nonstationary:
            if fit_means:
                self.W_mu_coeffs = nn.Parameter(torch.zeros(W_shape), requires_grad=True)
                self.b_mu_coeffs = nn.Parameter(torch.zeros(b_shape), requires_grad=True)
            self.W_shape_coeffs = nn.Parameter(torch.randn(W_shape), requires_grad=True)
            self.W_rate_coeffs = nn.Parameter(torch.randn(W_shape), requires_grad=True)
            self.b_shape_coeffs = nn.Parameter(torch.randn(b_shape), requires_grad=True)
            self.b_rate_coeffs = nn.Parameter(torch.randn(b_shape), requires_grad=True)
        else:
            if fit_means:
                self.W_mu = nn.Parameter(torch.zeros(W_shape), requires_grad=True)
                self.b_mu = nn.Parameter(torch.zeros(b_shape), requires_grad=True)
            self.W_shape = nn.Parameter(torch.randn(W_shape), requires_grad=True)
            self.W_rate = nn.Parameter(torch.randn(W_shape), requires_grad=True)
            self.b_shape = nn.Parameter(torch.randn(b_shape), requires_grad=True)
            self.b_rate = nn.Parameter(torch.randn(b_shape), requires_grad=True)

    def reset_parameters(self):
        """
        Reset parameters to values sampled from std normal distribution.
        """
        if self.nonstationary:
            init.normal_(self.W_shape_coeffs)
            init.normal_(self.W_rate_coeffs)
            init.normal_(self.b_shape_coeffs)
            init.normal_(self.b_rate_coeffs)
            if self.fit_means:
                init.zeros_(self.W_mu_coeffs)
                init.zeros_(self.b_mu_coeffs)
        else:
            init.normal_(self.W_shape)
            init.normal_(self.W_rate)
            init.normal_(self.b_shape)
            init.normal_(self.b_rate)
            if self.fit_means:
                init.zeros_(self.W_mu)
                init.zeros_(self.b_mu)

    def _resample_std(self, X_RBF):
        """
        Obtain std deviations from resampled inverse-gamma variances.

        :param X_RBF: torch.Tensor, embedding layer output (for nonstationary case)
        :return: tuple (torch.Tensor, torch.Tensor), weight std dev, bias std dev
        """
        # Positivity constraints
        if self.nonstationary:
            W_shape = F.softplus(X_RBF @ self.W_shape_coeffs).squeeze()
            W_rate = F.softplus(X_RBF @ self.W_rate_coeffs).squeeze()
            b_shape = F.softplus(X_RBF @ self.b_shape_coeffs).squeeze()
            b_rate = F.softplus(X_RBF @ self.b_rate_coeffs).squeeze()
        else:
            W_shape = F.softplus(self.W_shape)
            W_rate = F.softplus(self.W_rate)
            b_shape = F.softplus(self.b_shape)
            b_rate = F.softplus(self.b_rate)

        # Resample variances (sample from Gamma then invert)
        W_gamma_dist = dist.Gamma(W_shape, W_rate)
        b_gamma_dist = dist.Gamma(b_shape, b_rate)

        # Note: rsample() is reparametrised sample, which stores gradients
        inv_W_var = W_gamma_dist.rsample()
        inv_b_var = b_gamma_dist.rsample()

        # Note: self.eps added in denominator to avoid division by zero
        W_std = 1. / (torch.sqrt(inv_W_var) + 1e-10)
        b_std = 1. / (torch.sqrt(inv_b_var) + 1e-10)

        return W_std, b_std

    def forward(self, X, X_RBF=None):
        """
        Performs forward pass through layer given input data.

        :param X: torch.Tensor, size (batch_size, input_dim), input data
        :return: torch.Tensor, size (batch_size, output_dim), output data
        """
        if self.nonstationary:
            X = X.to(self.W_rho_coeffs.device)
            if X_RBF is None:
                X_RBF = X.detach().clone()
            else:
                X_RBF = X_RBF.to(self.W_rho_coeffs.device)
            W_std, b_std = self._resample_std(X_RBF)  # b_std has shape [batch_size, 1]
            W_std = W_std.unsqueeze(2)  # need shape [batch_size, 1, 1]
            if self.fit_means:
                W_mu = (X_RBF @ self.W_mu_coeffs).unsqueeze(2)  # [batch_size, 1, 1]
                b_mu = (X_RBF @ self.b_mu_coeffs)  # [batch_size, 1]
            else:
                W_mu = 0.
                b_mu = 0.
            Zw = torch.randn((1, self.input_dim, self.output_dim), device=W_std.device)
            Zb = torch.randn((1, self.output_dim), device=b_std.device)
            W = W_mu + W_std * Zw
            W = W / math.sqrt(self.input_dim)  # NTK
            b = b_mu + b_std * Zb

            # Rearrange tensors for broadcasting to work properly
            if len(X.shape) < len(W.shape):
                X = X.unsqueeze(1)
            fwd = (X @ W).squeeze() + b.squeeze()
            # X @ W is [batch_size, 1, input_dim] * [batch_size, input_dim, output_dim] = [batch_size, 1, output_dim]
            return fwd
        else:
            X = X.to(self.W_rho.device)
            if self.fit_means:
                W_mu = self.W_mu
                b_mu = self.b_mu
            else:
                W_mu = 0.
                b_mu = 0.
            W = W_mu + F.softplus(self.W_rho) * torch.randn((self.input_dim, self.output_dim), device=self.W_rho.device)
            b = b_mu + F.softplus(self.b_rho) * torch.randn(self.output_dim, device=self.b_rho.device)
            W = W / math.sqrt(self.input_dim)  # NTK
            return X @ W + b

    def sample_predict(self, X, n_samples, X_RBF=None):
        """
        Perform predictions using n_samples different sampled network parameters.

        :param X: torch.Tensor, size (batch_size, input_dim) or (n_samples, batch_size, input_dim), input data
        :param n_samples: int, number of network samples
        :param X_RBF: torch.Tensor, contains embedding layer output (RBF values in each neuron)
        :return: torch.Tensor, size (n_samples, batch_size, output_dim), output data
        """
        if self.nonstationary:
            X = X.to(self.W_rho_coeffs.device)

            # Resize input X appropriately
            if len(X.shape) == 2:
                X_RBF = X.detach().clone()
                X = X[None, :, None, :].repeat(n_samples, 1, 1, 1)
            else:
                X_RBF = X_RBF.to(self.W_rho_coeffs.device)
                X = X.unsqueeze(2)

            W_std, b_std = self._resample_std(X_RBF)
            W_std = W_std.squeeze()[None, :, None, None]
            b_std = b_std.squeeze()[None, :, None]
            if self.fit_means:
                W_mu = (X_RBF @ self.W_mu_coeffs).squeeze()[None, :, None, None]
                b_mu = (X_RBF @ self.b_mu_coeffs).squeeze()[None, :, None]
            else:
                W_mu = 0.
                b_mu = 0.
            Ws = W_mu + W_std * torch.randn((n_samples, 1, self.input_dim, self.output_dim), device=W_std.device)
            bs = b_mu + b_std * torch.randn((n_samples, 1, self.output_dim), device=b_std.device)
            Ws = Ws / math.sqrt(self.input_dim)  # NTK
            return (X @ Ws).squeeze() + bs.squeeze()
        else:
            X = X.to(self.W_rho.device)
            if self.fit_means:
                W_mu = self.W_mu
                b_mu = self.b_mu
            else:
                W_mu = 0.
                b_mu = 0.
            Ws = W_mu + F.softplus(self.W_rho) * torch.randn((n_samples, self.input_dim, self.output_dim),
                                                             device=self.W_rho.device)
            bs = b_mu + F.softplus(self.b_rho) * torch.randn((n_samples, 1, self.output_dim),
                                                             device=self.b_rho.device)
            Ws = Ws / math.sqrt(self.input_dim)  # NTK
            return X @ Ws + bs


# 1. [무료배송] 에어팟프로 애플로고 실리콘 케이스
# 2. 도낫도낫 와일드 에어팟 프로 실리콘 케이스
# 3. 누아트 페블 에어팟프로 실리콘케이스 + 메탈 철가루 방지스티커 랜덤 발송

import random
import argparse
import sys
# from time import time
# import matplotlib.pyplot as plt
import torch
import torch.nn as nn
import torch.optim as optim

import numpy as np
import torch.nn.functional as F
from utils import *

class FocalLoss(nn.Module):
    def __init__(self, alpha=0.25, gamma=2, logits=False, reduce=True):
        super(FocalLoss, self).__init__()
        self.alpha = alpha
        self.gamma = gamma
        self.logits = logits
        self.reduce = reduce

    def forward(self, inputs, targets):
        # ce_loss = nn.BCELoss()(inputs, targets)
        BCE = nn.BCEWithLogitsLoss()
        ce_loss = BCE(inputs, targets)

        pt = torch.exp(-ce_loss)
        F_loss = self.alpha * (1 - pt) ** self.gamma * ce_loss

        return torch.mean(F_loss)

class Dice_loss(nn.Module):
    def __init__(self, weight=None, size_average=True):
        super(Dice_loss, self).__init__()

    def forward(self, inputs, targets, smooth=1):
        # comment out if your model contains a sigmoid or equivalent activation layer
        bce_ = nn.BCEWithLogitsLoss(reduction='mean')
        bce = bce_(inputs, targets)

        inputs = F.sigmoid(inputs)

        # flatten label and prediction tensors
        inputs = inputs.view(-1)
        targets = targets.view(-1)

        intersection = (inputs * targets).sum()
        dice = (2. * intersection + smooth) / (inputs.sum() + targets.sum() + smooth)
        loss = (1 - dice) + bce

        return loss, bce, 1 - dice

def get_init():
    parser = argparse.ArgumentParser()

    # ETC
    # parser.add_argument('--data_path', default='/home/sam/Desktop/RM/datasets')
    parser.add_argument('--data_path', default='../../datasets')
    parser.add_argument('--split_ratio', type=float, default=0.2)
    parser.add_argument('--parent_dir', type=str, default='./saved_models/', help='for saving trained models')
    parser.add_argument('--num_workers', type=int, default=8)
    parser.add_argument('--save_root', default='SE_epoch100_coverage', help='res18, res101rfamdec, SE_mul_erosion')
    parser.add_argument('--train', action='store_true', default=True) # python3 main.py --train => 학습하는 거고 | python3 main.py => 테스트 하는 거임 ㅇㅋ 입력
    parser.add_argument('--saved_pt', type=str, default='last_100.pth')

    # Train Parser Args
    parser.add_argument('--model_name', default='thorn', help='res18, thorn, res18_base, res18worms, res50, res101, resnext101, vgg16, vgg16_seg')
    parser.add_argument('--num_labels', type=int, default=1)
    parser.add_argument('--optimizer', type=str, default='Adam', help='Adam, SGD, adabound')
    parser.add_argument('--momentum', type=float, default=0.9)
    parser.add_argument('--weight_decay', type=float, default=0.001)
    parser.add_argument('--criterion', type=str, default='DICE', help='BCE, CE(2) FL, DICE')
    parser.add_argument('--trainer', default='fuse', help='fuse, base, MFCN, split, autp, trans, SE, worms')
    parser.add_argument('--dataloader', default='casia1', help='dso1, coverage, casia2, casia1')

    parser.add_argument('--aug_option', default='n', help='')
    parser.add_argument('--erase_prob', type=float, default=0.0)
    parser.add_argument('--patch_size', type=int, default=5)
    parser.add_argument('--diagonal', type=int, default=100)#, default=int(np.random.randint(256, size=1)), help=' int(np.random.randint(256, size=1)) , 100')
    parser.add_argument('--load', type=str, default='f')

    parser.add_argument('--img_size', default=256, type=int, help='image width and height size')
    parser.add_argument('--batch_size', type=int, default=32)
    parser.add_argument('--epochs', type=int, default=100)
    parser.add_argument('--learning_rate', type=float, default=0.001)
    # parser.add_argument('--prune', type=float, default=0.5)

    args = parser.parse_args()
    return args

def print_configurations(args) :
    print("+++++++++++++++++++++++++++++++++++")
    print("data_type : {}".format(args.dataloader))
    print("save_root : {}".format(args.save_root))
    print("model_name : {}".format(args.model_name))
    print("optimizer : {}".format(args.optimizer))
    print("learning_rate : {}".format(args.learning_rate))
    print("momentum : {}".format(args.momentum))
    print("weight_decay : {}".format(args.weight_decay))
    print("criterion : {}".format(args.criterion))
    print("batch_size : {}".format(args.batch_size))
    print("epochs : {}".format(args.epochs))
    print("img_size : {}".format(args.img_size))
    print("+++++++++++++++++++++++++++++++++++")

def fix_seed(device) :
    random.seed(4321)
    np.random.seed(4321)
    torch.manual_seed(4321)
    if device == 'cuda':
        torch.cuda.manual_seed_all(4321)
        torch.backends.cudnn.deterministic = True
        torch.backends.cudnn.benchmark = False

    print(f"Your experiment is fixed to {4321}")

def get_trainer(trainer):
    if trainer == 'fuse':
        from Trainer_fusion import fit, test_epoch
    elif trainer == 'autp':
        from Trainer_autp import fit, test_epoch
    elif trainer == 'tune':
        from Trainer_tune import fit, test_epoch
    elif trainer == 'worms':
        from Trainer_worms import fit, test_epoch
    elif trainer == 'SE':
        from Trainer_SE import fit, test_epoch
    elif trainer == 'basic':
        from Trainer_Basic import fit, test_epoch
    elif trainer == 'MFCN':
        from Trainer_MFCN import fit, test_epoch
    return fit, test_epoch

def get_dataloader(args):
    # if args.dataloader == 'autp':
    #     from utils.load_functions import load_autp
    #     train_loader, test_loader = load_autp(data_path=args.data_path,
    #                                             split_ratio=args.split_ratio,
    #                                             batch_size=args.batch_size,
    #                                             img_size=args.img_size,
    #                                             num_workers=args.num_workers)
    if args.dataloader == 'casia2':
        from utils.load_functions import load_casia2
        train_loader, test_loader = load_casia2(data_path=args.data_path,
                                                split_ratio=args.split_ratio,
                                                batch_size=args.batch_size,
                                                img_size=args.img_size,
                                                num_workers=args.num_workers)
    elif args.dataloader == 'casia1':
        from utils.load_functions import load_casia1
        train_loader, test_loader = load_casia1(data_path=args.data_path,
                                                split_ratio=args.split_ratio,
                                                batch_size=args.batch_size,
                                                img_size=args.img_size,
                                                num_workers=args.num_workers)
    elif args.dataloader == 'dso1':
        from utils.load_functions import load_dso1
        train_loader, test_loader = load_dso1(data_path=args.data_path,
                                                split_ratio=args.split_ratio,
                                                batch_size=args.batch_size,
                                                img_size=args.img_size,
                                                num_workers=args.num_workers)
    elif args.dataloader == 'coverage':
        from utils.load_functions import load_coverage
        train_loader, test_loader = load_coverage(data_path=args.data_path,
                                                split_ratio=args.split_ratio,
                                                batch_size=args.batch_size,
                                                img_size=args.img_size,
                                                num_workers=args.num_workers)

    return train_loader, test_loader

def get_device() :
    device = 'cuda' if torch.cuda.is_available() else 'cpu'
    print(f"your device is {device}")
    return device

def get_current_lr(optimizer) :
    for param_group in optimizer.param_groups:
        return param_group['lr']

def get_lr(step, total_step, lr_max, lr_min) :
    """Compute learning rate according to cosine annealing schedule."""
    return lr_min + (lr_max - lr_min) * 0.5 * ( 1 + np.cos(step/total_step * np.pi))

def get_criterion(args) :
    if args.criterion == 'BCE' :
        criterion = nn.BCEWithLogitsLoss()
    elif args.criterion == 'CE':
        criterion = nn.CrossEntropyLoss()
    elif args.criterion == 'FL' :
        criterion = FocalLoss()
    elif args.criterion == 'DICE':
        criterion = Dice_loss()
    elif args.criterion == 'Region':
        from models.model_core.region_loss import Region_loss
        criterion = Region_loss()
    else :
        print("You choose wrong criterion : [BCE, FL, DICE]")
        sys.exit()

    print("Your criterion is : ", criterion)

    return criterion

def get_optimizer(args, model) :
    if args.optimizer == 'Adam' :
        optimizer = optim.Adam(model.parameters(), lr=args.learning_rate)
    elif args.optimizer == 'SGD' :
        optimizer = optim.SGD(model.parameters(),
                              lr=args.learning_rate,
                              momentum=args.momentum,
                              weight_decay=args.weight_decay,
                              nesterov=True)
    elif args.optimizer == 'adabound':
        import adabound
        optimizer = adabound.AdaBound(model.parameters(), lr=args.learning_rate, final_lr=0.1)

    else :
        print("You choose wrong optimizer : [Adam, SGD]")
        sys.exit()

    print("Your optimizer is : ", optimizer)

    return optimizer

def get_scheduler(args, train_loader, optimizer) :
    scheduler = torch.optim.lr_scheduler.LambdaLR(
        optimizer,
        lr_lambda=lambda step: get_lr(  # pylint: disable=g-long-lambda
            step,
            args.epochs * len(train_loader),
            1,  # lr_lambda computes multiplicative factor
            1e-6 / args.learning_rate))

    return scheduler

def get_history() :
    history = dict()
    history['train_loss'] = list()
    history['test_loss'] = list()

    return history

def get_patches(image, channel, patch_size):
    unfold = torch.nn.Unfold(kernel_size=patch_size, stride=patch_size)
    patches = unfold(image)
    patches = patches.permute(0, 2, 1)
    patches = patches.reshape(-1, channel, patch_size, patch_size) # [P*B, C, W, H]
    return patches

import os
import cv2

def get_mask_dir(root_dir, prefix):
    files = os.listdir(root_dir)
    mask_list = []
    for file in files:
        path = os.path.join(root_dir, file)
        mask_list.append(path)
        if os.path.isdir(path):
            get_mask_dir(path, prefix + "    ")
    return mask_list

def get_coco_dir(root_dir, prefix):
    files = os.listdir(root_dir)
    img_list = []
    for file in files:
        path = os.path.join(root_dir, file)
        jpg = path.split('/')[-1].split('.')[0]
        img_path = os.path.join(coco_dir,jpg+'.jpg')
        img_list.append(img_path)
        if os.path.isdir(path):
            get_mask_dir(path, prefix + "    ")
    return img_list

coco_dir = '../datasets/test2017'
mask_dir = '../datasets/output_coco'

def data_split(split, ratio=0.2):
    # idx = np.arange(0, len(split))
    # np.random.shuffle(idx)
    length = int(len(split) * ratio)
    train_data = split[length:]
    test_data = split[:length]
    return train_data, test_data

class Augmentation(object) :
    def __init__(self):
        super(Augmentation, self).__init__()

        self.coco = get_coco_dir(mask_dir, "")
        self.mask = get_mask_dir(mask_dir, "")

    def __call__(self, img):
        if np.random.rand(1) >= 0.5:
            h, w = 256, 256
            index = np.random.randint(1,len(self.mask))
            mask_data = cv2.imread(self.mask[index], 0)
            coco_data = cv2.imread(self.coco[index])

            mask_data[mask_data < 254] = 1
            mask_data[mask_data == 255] = 0
            expand_mask = np.expand_dims(mask_data, axis=2)
            mask_data = np.repeat(expand_mask, 3, axis=2)

            # masked = cv2.resize(masked, dsize=(w, h), interpolation=cv2.INTER_AREA)
            coco_data = cv2.resize(coco_data, dsize=(w, h), interpolation=cv2.INTER_AREA)
            mask_data = cv2.resize(mask_data, dsize=(w, h), interpolation=cv2.INTER_AREA)

            masked = coco_data * mask_data

            rows, cols = mask_data.shape[:2]
            randint = np.random.randint(1, 360)
            # randf = float(np.round(0.5 * np.random.rand(1), 1)) # [0.5 ~ 2.0] # scale
            M = cv2.getRotationMatrix2D((cols / 2, rows / 2), randint, 1)

            mask_data = cv2.warpAffine(mask_data, M, (cols, rows))
            masked = cv2.warpAffine(masked, M, (cols, rows))

            x = np.random.randint(-150, 150)
            y = np.random.randint(-150, 150)

            T = np.float64([[1, 0, x], [0, 1, y]])
            mask_data = cv2.warpAffine(mask_data, T, (cols, rows))
            masked = cv2.warpAffine(masked, T, (cols, rows))

            if np.random.rand(1) > 0.5:
                masked = cv2.GaussianBlur(masked, (3, 3), 0)

            mask_data[mask_data == 1] = 3
            mask_data[mask_data == 0] = 1
            mask_data[mask_data == 3] = 0
            img = np.array(img)

            if len(img.shape) == 2:
                out_mask = mask_data + 1
                out_mask[out_mask == 1] = 1
                out_mask[out_mask == 2] = 0

                img[img > 0] = 1
                mask = np.bitwise_or(img, out_mask[:,:,1])
                return mask
            else :
                masked = cv2.cvtColor(masked, cv2.COLOR_BGR2RGB)
                blank = img*mask_data
                out_img = blank + masked
                return out_img
        else:
            return img


from flask import Flask, Response, request, render_template, make_response, redirect, url_for
import flask_excel as excel
from openpyxl import Workbook
import openpyxl
import pyexcel

from rq import Queue
from rq.job import Job

from worker import conn
from utils import count_words_at_url

import time

# fix dimensiones por la coma que hay se separan en dos valores distintos en el excel
app = Flask(__name__)

counter = 0

@app.route('/')
def homepage():
    return render_template('index.html', data=counter)

@app.route("/", methods=["POST"])
def getPlotCSV():
    text = request.form['text'] # url from webpage
    url = str(text)

    q = Queue(connection=conn) # create de queue
    task = q.enqueue(count_words_at_url, url)
    task_id = task.get_id()
    return redirect(url_for('waiting', task_id=task_id))

@app.route("/waiting/<task_id>")
def waiting(task_id):
    job = Job.fetch(task_id, connection=conn)
    start_time = time.time()
    while not job.is_finished:
        if time.time() - start_time > 25:
            print("la solicitud se ha demorado mas de 25 segundos, se redirege a la url waiting!!!!!!!!!!!!")
            return redirect(url_for('waiting', task_id=task_id))

    result = job.result
    my_workbook = Workbook()
    my_workbook_active = my_workbook.active

    # excel headers
    header=["Tipo", "Categoria", "Ubicacion", "Codigo", "Informacion",
    "Construido", "Terreno", "Valor(UF)", "UF/Construido", "UF/Terreno", "url"]
    my_workbook_active.append(header)

    for j in result:
        my_workbook_active.append(j)

    output = make_response(openpyxl.writer.excel.save_virtual_workbook(my_workbook))
    output.headers["Content-Disposition"] = "attachment; filename=Datos.xlsx"
    output.headers["Content-type"] = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    print("excel esta listo para ser retornado suerte")

    return output

if __name__ == '__main__':
    excel.init_excel(app)
    app.run(debug=True, use_reloader=True)
    # app.run()


#!/usr/bin/env python3

import argparse
import dataclasses
import torch
import torch.utils.data
import yaml
import logging
import pathlib
import torchinfo
import utils
import loaders
import architectures
import losses
import losses.loss_mha as loss_mha
import transforms
import train
import numpy as np
from time import perf_counter
import torchvision.transforms as tf

RESULTS = "runs/"
MODELS = "models/"
HYPER_EXT = "txt"

@dataclasses.dataclass
class ProgramArguments:
    setup_config_path : str
    model_config_path : str

def parse_program_arguments():
    """
    Parse program arguments and create a program configuration.

    :return: program configuration
    """
    parser = argparse.ArgumentParser(
        description="Parser for the configuration file to begin testing of the Autoencoder testing")

    parser.add_argument(
        "--setup_config_path",
        type=str,
        help="Configuration file to use for inferencing")

    parser.add_argument(
        "--model_config_path",
        type=str,
        help="Configuration file for the model setup")

    args = parser.parse_args()

    return ProgramArguments(
        setup_config_path=args.setup_config_path,
        model_config_path=args.model_config_path)

class Tester:
    """
    Testing and its helper functions
    """
    def __init__(self,
                 setup_cfg,
                 model_cfg):
        """
        Read the contents of the config file

        :param setup_cfg: config file of the testing parameters
        :param model_cfg: config file of the model parameters
        """
        # General setup
        logging.basicConfig(level=logging.INFO)
        self.mode = setup_cfg["setup"]["mode"]
        self.num_images = setup_cfg["setup"]["num_images"]
        self.print_interval = setup_cfg["setup"]["print_interval"]
        self.num_workers = setup_cfg["setup"]["num_workers"]
        self.master_rank = setup_cfg["setup"]["master_rank"]

        # Test specific setup
        self.test_split = setup_cfg["testing"]["test_split"]
        self.val_split = setup_cfg["testing"]["val_split"]
        self.batch_size = setup_cfg["testing"]["batch_size"]
        self.model_path = setup_cfg["testing"]["model_path"]
        self.ae_model_path = setup_cfg["testing"]["ae_path"]
        self.semseg_model_path = setup_cfg["testing"]["semseg_path"]

        # Dataset specific setup
        self.dataset_name = model_cfg["dataset"]["name"]
        self.dataset_dict = model_cfg[self.dataset_name]
        self.input_res = model_cfg["dataset"]["input_res"]
        self.val_data_loader, self.test_data_loader = self.dataset()

        # Model, loss and optimiser setup
        self.model_name = model_cfg["model"]
        self.model_dict = {**model_cfg[self.model_name], **model_cfg["dataset"]}
        self.model_ae_name = model_cfg["model_ae"]
        self.model_ae_dict = {**model_cfg[self.model_ae_name], **model_cfg["dataset"]}
        self.model_semseg_name = model_cfg["model_semseg"]
        self.model_semseg_dict = {**model_cfg[self.model_semseg_name], **model_cfg["dataset"]}
        self.ae_loss_dict = setup_cfg["setup"]["autoencoder_loss"]
        self.semseg_loss_dict = setup_cfg["setup"]["segmentation_loss"]
        self.loss_weights = setup_cfg["setup"]["loss_weights"]
        self.device = self.device_check()

        # Semseg head setup
        self.num_classes = model_cfg[self.model_name]["num_classes"]
        self.running_metrics = utils.SegmentationMetric(self.num_classes)

        # Visualisation setup
        self.mean = setup_cfg["setup"]["mean"]
        self.std = setup_cfg["setup"]["std"]
        self.visualise_val_ae = utils.Visualise(dataloader=self.val_data_loader,
                                                num_images=self.num_images,
                                                device=self.device)
        self.visualise_test_ae = utils.Visualise(dataloader=self.test_data_loader,
                                                 num_images=self.num_images,
                                                 device=self.device)
        self.visualise_val_ss = utils.VisualiseSemSeg(num_images=self.num_images)
        self.visualise_test_ss = utils.VisualiseSemSeg(num_images=self.num_images)

        # Pathfiles setup
        self.models_save_path = pathlib.Path(MODELS) / self.model_name / self.dataset_name
        utils.mkdir(self.models_save_path)
        self.results_save_path = pathlib.Path(RESULTS) / self.model_name / self.dataset_name / "test"
        self.seed = setup_cfg.get("seed", 10)

        # Begin inference
        self.infer()

    def dataset(self):
        """
        Load the test set of the chosen dataset

        :return test_data_loader: the dataloader of the test set
        """
        data_loader, data_loader_params = loaders.get_loader(dataset_name=self.dataset_name,
                                                             dataset_dict=self.dataset_dict,
                                                             mode=self.mode)
        test_data = data_loader(**data_loader_params,
                                augments=None,
                                split=self.test_split,
                                input_res=self.input_res)
        test_data_loader = test_data.load_data(num_workers=self.num_workers,
                                               shuffle=False)

        val_data = data_loader(**data_loader_params,
                               augments=None,
                               split=self.val_split,
                               input_res=self.input_res)
        val_data_loader = val_data.load_data(num_workers=self.num_workers,
                                             shuffle=False)

        logging.info(f"Using dataset: {self.dataset_name}")

        return val_data_loader, test_data_loader

    def model(self,
              model_name,
              model_dict):
        """
        Establish the model

        :return model: model to perform inferencing on
        """
        model_name, model_params = architectures.get_arch(model_name=model_name,
                                                          model_dict=model_dict,
                                                          seed=self.seed)
        model = model_name(**model_params)

        return model

    def loss(self):
        """
        Establish the loss. The loss of both heads are established here and grouped as a list. The MultiHeadLoss is
        then instantiated with this loss list

        :return loss: the multi-head loss function
        """
        # Initialise individual head losses
        ae_loss_name, ae_loss_params = losses.get_loss(self.ae_loss_dict,
                                                       self.master_rank)
        semseg_loss_name, semseg_loss_params = losses.get_loss(self.semseg_loss_dict,
                                                               self.master_rank)

        ae_loss = ae_loss_name(**ae_loss_params).to(self.master_rank)
        semseg_loss = semseg_loss_name(**semseg_loss_params).to(self.master_rank)

        # Compile losses to list and initialise multi-head loss
        loss_all = [ae_loss, semseg_loss]
        loss = losses.loss_mha.MultiHeadLoss(losses=loss_all,
                                             loss_weights=self.loss_weights)

        return loss, (ae_loss_params, semseg_loss_params)

    def device_check(self):
        """
        Get the best device to test the model on

        :return device: the best device
        """
        device = utils.best_device(utils.devices())
        logging.info(f"Using device: {torch.cuda.get_device_name(device)}")

        return device

    def load_checkpoint(self):
        """
        Load in the model and optimiser state. Also load in the single head models for fps comparison

        :return model: the model for which inferencing is performed on
        """
        if pathlib.Path(self.model_path).is_file():
            # mha model
            checkpoint = torch.load(f=self.model_path,
                                    map_location='cpu')
            model = self.model(model_name=self.model_name,
                               model_dict=self.model_dict)
            model.to(self.device)
            model = utils.parallelise_model(model,
                                            checkpoint["model_state"])
            model.eval()

            # single head ae model
            checkpoint_ae = torch.load(f=self.ae_model_path,
                                       map_location='cpu')
            model_ae = self.model(model_name=self.model_ae_name,
                                  model_dict=self.model_ae_dict)
            model_ae.to(self.device)
            model_ae = utils.parallelise_model(model_ae,
                                               checkpoint_ae["model_state"])
            model_ae.eval()

            # single head semseg model
            checkpoint_semseg = torch.load(f=self.semseg_model_path,
                                           map_location='cpu')
            model_semseg = self.model(model_name=self.model_semseg_name,
                                      model_dict=self.model_semseg_dict)
            model_semseg.to(self.device)
            model_semseg = utils.parallelise_model(model_semseg,
                                                   checkpoint_semseg["model_state"])
            model_semseg.eval()

            return model, model_ae, model_semseg

        else:
            raise NotImplementedError("Re-check checkpoint path if it exists")

    def create_summary(self,
                       model):
        """
        Utilise torchinfo to display the architecture of the model in tabular form

        :param model: the model of interest
        :param model_summary: a summary of the model
        """
        C, H, W = self.test_data_loader.dataset[0][0].shape
        model_summary = torchinfo.summary(model, input_size=(self.batch_size, C, H, W))

        return model_summary

    def validation_set(self,
                       model,
                       model_ae,
                       model_semseg,
                       loss_function,
                       best_mha_fps):
        """
        Run inferencing on the model, gather inference results and visualisations
        Also gather latent space and feature space visualisations
        On the validation set -> semantic segmentation and autoencoder results and metrics

        :param model: mha model
        :param model_ae: single head autoencoder model
        :param model_semseg: single head semantic segmentation model
        :param loss_function: the multi-loss function
        :param best_mha_fps: current best mha fps
        :param best_sha_fps: current best sha fps
        """
        val_size = len(self.val_data_loader)
        val_loss_meter = utils.AverageTracker()
        ae_val_loss_meter = utils.AverageTracker()
        ss_val_loss_meter = utils.AverageTracker()

        vis_samples = int(len(self.val_data_loader) * 0.25)
        latent_space = utils.VisualiseLatentSpace(model=model,
                                                  mean=torch.tensor(self.mean),
                                                  std=torch.tensor(self.std),
                                                  num_samples=vis_samples)
        model.eval()

        with torch.no_grad():
            for i, (xs, ys, gt) in enumerate(self.val_data_loader):
                xs = xs.to(self.device)
                ys = ys.to(self.device)

                # mha prediction
                mha_start_t = perf_counter()
                preds = model(xs)
                mha_end_t = perf_counter()

                targets = [xs, ys]
                total_loss, head_losses = loss_function(preds,
                                                        targets)

                val_loss_meter.update(total_loss.item())
                ae_val_loss_meter.update(head_losses[0])
                ss_val_loss_meter.update(head_losses[1])
                mha_fps = utils.calc_fps(self.batch_size,
                                         mha_end_t,
                                         mha_start_t)

                if mha_fps > best_mha_fps:
                    best_mha_fps = mha_fps

                    # sha prediction (for fps evaluation only)
                    sha_start_t = perf_counter()
                    ae_preds = model_ae(xs)
                    semseg_preds = model_semseg(xs)
                    sha_end_t = perf_counter()

                    best_sha_fps = utils.calc_fps(self.batch_size,
                                                  sha_end_t,
                                                  sha_start_t)

                # get semseg metrics
                semseg_pred = torch.argmax(preds[1], 1)
                semseg_pred = semseg_pred.to("cpu").data.numpy()
                self.running_metrics.update(semseg_pred, ys.to("cpu").numpy())

                if i % self.print_interval == 0:
                    val_loss, current = total_loss.item(), i * len(xs)
                    print(f"val loss: {val_loss}, best fps: {best_mha_fps}, [{current} / {val_size}]")

                # visualise semseg
                pred_img = semseg_pred.squeeze(0)
                mask = utils.get_colour_pallete(pred_img)
                mask_inv, xs_inv = transforms.inver_norm_pair(mean=torch.tensor(tuple(self.mean)),
                                                              std=torch.tensor(tuple(self.std)),
                                                              input=xs,
                                                              output=mask,
                                                              mode="semseg")
                self.visualise_val_ss.update(xs_inv.to("cpu"),
                                             mask_inv,
                                             gt)

                # viualise segmentation feature maps
                if i == 0:
                    utils.visualise_segmentation_features(model,
                                                          xs,
                                                          self.models_save_path)

                # visualise autoencoder
                ps_inv, _ = transforms.inver_norm_pair(mean=torch.tensor(self.mean),
                                                       std=torch.tensor(self.std),
                                                       input=xs,
                                                       output=preds[0],
                                                       mode=self.mode)
                self.visualise_val_ae.update(xs_inv,
                                             ps_inv)

                # visualise autoencoder latent space
                latent_space.update(xs)
            latent_space.visualise(self.models_save_path)

        # semseg metrics
        score, class_iou = self.running_metrics.get()
        mIoU = score["Mean IoU"]
        pix_acc = score["Overall Acc"]

        # loss metrics
        val_loss = val_loss_meter.avg
        ae_val_loss = ae_val_loss_meter.avg
        ss_val_loss = ss_val_loss_meter.avg

        return val_loss, ae_val_loss, ss_val_loss, best_mha_fps, best_sha_fps, pix_acc, mIoU, class_iou

    def test_set(self,
                 model,
                 model_ae,
                 model_semseg,
                 best_mha_fps):
        """
        Run inferencing on the model, gather inference visualisations
        On the test set -> semantic segmentation and autoencoder results and metrics (no loss recorded as cityscapes
        do not provide the test labels)

        :param model: model
        :param best_fps: current best fps
        """
        test_size = len(self.test_data_loader)
        model.eval()

        with torch.no_grad():
            for i, (xs, _, _) in enumerate(self.test_data_loader):
                xs = xs.to(self.device)

                start_t = perf_counter()
                preds = model(xs)
                end_t = perf_counter()

                fps = utils.calc_fps(self.batch_size,
                                     end_t,
                                     start_t)

                if fps > best_mha_fps:
                    best_mha_fps = fps

                    # sha prediction (for fps evaluation only)
                    sha_start_t = perf_counter()
                    ae_preds = model_ae(xs)
                    semseg_preds = model_semseg(xs)
                    sha_end_t = perf_counter()

                    best_sha_fps = utils.calc_fps(self.batch_size,
                                                  sha_end_t,
                                                  sha_start_t)

                if i % self.print_interval == 0:
                    current = i * len(xs)
                    print(f"Test Set, best fps: {best_mha_fps}, [{current} / {test_size}]")

                # visualise autoencoder
                ps_inv, xs_inv = transforms.inver_norm_pair(mean=torch.tensor(self.mean),
                                                       std=torch.tensor(self.std),
                                                       input=xs,
                                                       output=preds[0],
                                                       mode=self.mode)
                self.visualise_test_ae.update(xs_inv,
                                              ps_inv)

        return best_mha_fps, best_sha_fps

    def infer(self):
        """
        The complete inference process
        - load in the model and loss conditions
        - conduct inferencing
        - save the metrics and model summary
        - output the visualisation results to tensorboard
        """
        best_mha_fps_start = -1
        writer = utils.Logger(self.results_save_path)

        # Load conditions
        model, model_ae, model_semseg = self.load_checkpoint()
        logging.info(f"Loaded model: {type(model).__name__}")
        loss_function, loss_params = self.loss()
        ae_loss_params, ss_loss_params = loss_params[0], loss_params[1]
        logging.info(f"Using loss: {type(loss_function).__name__}")

        # Begin testing
        val_results = self.validation_set(model=model,
                                          model_ae=model_ae,
                                          model_semseg=model_semseg,
                                          loss_function=loss_function,
                                          best_mha_fps=best_mha_fps_start)
        val_loss, ae_val_loss, ss_val_loss, best_mha_fps, best_sha_fps, pix_acc, mIoU, class_iou = val_results

        best_mha_fps_test, best_sha_fps_test = self.test_set(model=model,
                                                             model_ae=model_ae,
                                                             model_semseg=model_semseg,
                                                             best_mha_fps=best_mha_fps_start)

        test_set_metrics = {
            "total_val_loss": val_loss,
            "ae_val_loss": ae_val_loss,
            "ss_val_loss": ss_val_loss,
            "best_mha_fps_val": best_mha_fps,
            "best_sha_fps_val": best_sha_fps,
            "best_mha_fps_test": best_mha_fps_test,
            "best_sha_fps_test": best_sha_fps_test,
            "pixel acc": pix_acc,
            "mean iou": mIoU,
            "class_iou": class_iou
        }

        # Save metrics
        params_filename = self.models_save_path / f"best_model_metrics.{HYPER_EXT}"
        utils.save_contents(test_set_metrics,
                            params_filename,
                            self.mode)

        # Output model summary
        model_summary = self.create_summary(model)
        model_summary_filename = self.models_save_path / f"model_summary.{HYPER_EXT}"
        utils.save_contents(model_summary,
                            model_summary_filename,
                            self.mode)

        # Gather visualisation results
        self.visualise_val_ae.create_visuals(writer,
                                             "val")
        self.visualise_test_ae.create_visuals(writer,
                                              "test")

        self.visualise_val_ss.create_visuals(writer,
                                             self.models_save_path)
        writer.close()

def main():
    args = parse_program_arguments()

    with open(args.setup_config_path) as fp:
        setup_cfg = yaml.safe_load(fp)

    with open(args.model_config_path) as fp:
        model_cfg = yaml.safe_load(fp)

    Tester(setup_cfg,
           model_cfg)

if __name__ == '__main__':
    main()