import torch

from .activation_base import BoundActivation
from .nonlinear import BoundOptimizableNonLinear

class BoundSin(BoundOptimizableNonLinear):
    # Lookup tables shared by all BoundSin classes.
    xl_lower_tb = None
    xl_upper_tb = None
    xu_lower_tb = None
    xu_upper_tb = None
    func, d_func = torch.sin, torch.cos
    n_table_entries = 1001

    def __init__(self, attr=None, inputs=None, output_index=0, options=None):
        super().__init__(attr, inputs, output_index, options)
        self.ibp_intermediate = True
        self.use_precompute = True
        self.act_func = torch.sin
        self.d_act_func = torch.cos

        # Bound limits used by IBP.
        self.ibp_max_point = torch.pi / 2
        self.ibp_min_point = torch.pi * 3 / 2

        self.all_table_x = torch.linspace(
            0, 2 * torch.pi, BoundSin.n_table_entries, device=self.device)
        if self.use_precompute:
            self.precompute_relaxation(self.act_func, self.d_act_func, x_limit = torch.pi / 2)
        if BoundSin.xl_lower_tb is None:
            # Generate look-up tables.
            BoundSin.xl_lower_tb = BoundSin.get_lower_left_bound(self.all_table_x)
            BoundSin.xl_upper_tb = BoundSin.get_upper_left_bound(self.all_table_x)
            BoundSin.xu_lower_tb = BoundSin.get_lower_right_bound(self.all_table_x)
            BoundSin.xu_upper_tb = BoundSin.get_upper_right_bound(self.all_table_x)

    def d2_act_func(self, x):
        return -torch.sin(x)

    def _init_opt_parameters_impl(self, size_spec, name_start):
        """Implementation of init_opt_parameters for each start_node."""
        l, u = self.inputs[0].lower, self.inputs[0].upper
        shape = [size_spec] + list(l.shape)
        alpha = torch.empty(12, *shape, device=l.device)
        alpha.data[:4] = ((l + u) / 2).unsqueeze(0).expand(4, *shape)
        alpha.data[4:6] = self.tp_both_lower_init[name_start].expand(2, *shape)
        alpha.data[6:8] = self.tp_both_upper_init[name_start].expand(2, *shape)
        alpha.data[8:10] = self.tp_left_lower_init[name_start].expand(2, *shape)
        alpha.data[10:12] = self.tp_left_upper_init[name_start].expand(2, *shape)
        return alpha

    def opt_init(self):
        super().opt_init()
        self.tp_both_lower_init = {}
        self.tp_both_upper_init = {}
        self.tp_left_lower_init = {}
        self.tp_left_upper_init = {}
        self.tp_right_lower_init = {}
        self.tp_right_upper_init = {}

    def generate_inflections(self, lb, ub):
        return

    def branch_input_domain(self, lb, ub):
        lb_cycles = torch.floor((lb + 0.5 * torch.pi) / (2 * torch.pi)) * (2 * torch.pi)
        lb_clamped = lb - lb_cycles
        ub_cycles = torch.floor((ub + 0.5 * torch.pi) / (2 * torch.pi)) * (2 * torch.pi)
        ub_clamped = ub - ub_cycles

        self.sigmoid_like_mask = (ub - lb <= torch.pi)
        self.sigmoid_like_mask = torch.logical_and(self.sigmoid_like_mask, torch.logical_or(
            torch.logical_and(lb_clamped <= 0.5 * torch.pi, ub_clamped <= 0.5 * torch.pi),
            torch.logical_and(lb_clamped >= 0.5 * torch.pi, ub_clamped >= 0.5 * torch.pi)))
        self.branch_mask = torch.logical_not(self.sigmoid_like_mask)

        self.mask_neg = torch.logical_and((self.d2_act_func(lb) >= 0),
            torch.logical_and((self.d2_act_func(ub) >= 0),
            self.sigmoid_like_mask))
        self.mask_pos = torch.logical_and((self.d2_act_func(lb) < 0),
            torch.logical_and((self.d2_act_func(ub) < 0),
            self.sigmoid_like_mask))
        self.mask_both = torch.logical_xor(self.sigmoid_like_mask,
            torch.logical_or(self.mask_neg, self.mask_pos))

        self.convex_concave = torch.logical_and(self.mask_both,
            (self.d2_act_func(lb) >= 0))
        self.concave_convex = torch.logical_xor(self.mask_both, self.convex_concave)

    def generate_d_lower_upper(self, lower, upper):
        # Indices of neurons with input upper bound >=0, whose optimal slope to lower bound the function was pre-computed.
        # Note that for neurons with also input lower bound >=0, they will be masked later.
        k_tensor = torch.floor(upper / (2 * torch.pi))
        upper_clamped = upper - k_tensor * (2 * torch.pi)
        case1_mask = torch.logical_and(upper_clamped >= 0, upper_clamped <= torch.pi / 2)
        upper_clamped_new = upper_clamped.clamp(min=0, max=(torch.pi / 2))
        index = torch.max(
            torch.zeros(upper.numel(), dtype=torch.long, device=upper.device),
            (upper_clamped_new / self.step_pre).to(torch.long).reshape(-1)
        ) + 1
        # Lookup the lower bound slope from the pre-computed table.
        d_lower = (torch.index_select(self.d_lower, 0, index).view(lower.shape) + k_tensor * 2 * torch.pi) * case1_mask

        case2_mask = torch.logical_and(upper_clamped >= torch.pi, upper_clamped <= 3 * torch.pi / 2)
        upper_clamped_new = upper_clamped.clamp(min=torch.pi, max=(3 * torch.pi / 2))
        index = torch.max(
            torch.zeros(upper.numel(), dtype=torch.long, device=upper.device),
            ((torch.pi - upper_clamped_new) / -self.step_pre).to(torch.long).reshape(-1)
        ) + 1
        # Lookup the lower bound slope from the pre-computed table.
        d_upper = (torch.pi - torch.index_select(self.d_upper, 0, index).view(lower.shape) + k_tensor * 2 * torch.pi) * case2_mask

        # Indices of neurons with lower bound <=0, whose optimal slope to upper bound the function was pre-computed.
        k_tensor = torch.floor(lower / (2 * torch.pi))
        lower_clamped = lower - k_tensor * (2 * torch.pi)
        case3_mask = torch.logical_and(lower_clamped >= 3 * torch.pi / 2, lower_clamped <= 2 * torch.pi)
        lower_clamped_new = lower_clamped.clamp(min=(3 * torch.pi / 2), max=2 * torch.pi)
        index = torch.max(
            torch.zeros(lower.numel(), dtype=torch.long, device=lower.device),
            ((lower_clamped_new - 2 * torch.pi) / -self.step_pre).to(torch.long).reshape(-1)
        ) + 1
        d_upper += (torch.index_select(self.d_upper, 0, index).view(upper.shape) + (k_tensor + 1) * 2 * torch.pi) * case3_mask

        case4_mask = torch.logical_and(lower_clamped >= torch.pi / 2, lower_clamped <= torch.pi)
        lower_clamped_new = lower_clamped.clamp(min=(torch.pi / 2), max=3 * torch.pi)
        index = torch.max(
            torch.zeros(lower.numel(), dtype=torch.long, device=lower.device),
            ((torch.pi - lower_clamped_new) / self.step_pre).to(torch.long).reshape(-1)
        ) + 1
        d_lower += (torch.pi - torch.index_select(self.d_lower, 0, index).view(upper.shape) + k_tensor * 2 * torch.pi) * case4_mask
        return d_lower, d_upper

    @staticmethod
    def n_crossing(start, end, s):
        """Check how many times we will encounter value s + k*2*pi within start and end for any integer k."""
        cycles = torch.floor((end - start) / (2 * torch.pi))  # Number of 2pi cycles.
        # Move s and end to the same 2 * pi cycle as start.
        dist = torch.floor((s - start) / (2 * torch.pi))
        real_s = s - dist * 2 * torch.pi
        real_end = end - cycles * 2 * torch.pi
        return (real_s >= start).to(s) * (real_s <= real_end).to(s) + cycles

    @staticmethod
    def arcsin(c):
        """Arcsin with gradient fixes.

        arcsin(-1) and arcsin(1) have pathological gradients and should be avoided.
        """
        if c.min() > -1 and c.max() < 1:
            return torch.arcsin(c)
        c_ = c.clone()
        mask_neg = c == -1
        mask_pos = c == 1
        c_[mask_neg] = 0
        c_[mask_pos] = 0
        ret = torch.arcsin(c_)
        ret[mask_neg] = -torch.pi / 2
        ret[mask_pos] = torch.pi / 2
        return ret

    @staticmethod
    def get_intersection(start, end, c, theta=0.):
        """Get the number of intersections between y = sin(x + theta) and y = c between start and end."""
        # Use arcsine to find the first 2 intersections.
        crossing1 = BoundSin.arcsin(c) - theta
        crossing2 = torch.pi - crossing1 - 2 * theta  # Problematic at exact 1/2 pi, but ok in our case (happens only when lb=ub).
        return BoundSin.n_crossing(start, end, crossing1) + BoundSin.n_crossing(start, end, crossing2)

    @staticmethod
    def check_bound(tangent_point, x):
        """Check whether the tangent line at tangent_point is a valid lower/upper bound for x."""
        # evaluate the value of the tangent line at x and see it is >= 0 or <=0.
        d = BoundSin.d_func(tangent_point)
        val = d * (x - tangent_point) + BoundSin.func(tangent_point)
        # We want a positive margin when finding a lower line, but as close to 0 as possible.
        # We want a negative margin when finding a upper line, but as close to 0 as possible.
        margin = BoundSin.func(x) - val
        return margin

    @staticmethod
    @torch.no_grad()
    def get_lower_left_bound(xl, steps=20):
        """Get a global lower bound given lower bound on x. Return slope and intercept."""
        dtype = xl.dtype
        # Constrain xl into the -0.5 pi to 1.5 pi region.
        cycles = torch.floor((xl + 0.5 * torch.pi) / (2 * torch.pi)) * (2 * torch.pi)
        xl = xl - cycles
        use_tangent_line = (xl >= torch.pi).to(dtype)
        # Case 1: xl > pi, Lower tangent line is the only possible lower bound.
        # Case 2: Binary search needed. Testing from another tangent endpoint in [pi, 1.5*pi]. It must be in this region.
        left = torch.pi * torch.ones_like(xl)
        # The right end guarantees the margin > 0 because it is basically a IBP lower bound (-1).
        right = (1.5 * torch.pi) * torch.ones_like(xl)
        last_right = right.clone()
        for _ in range(steps):
            mid = (left + right) / 2.
            margin = BoundSin.check_bound(mid, xl)
            pos_mask = (margin > 0).to(dtype)  # We want to margin > 0 but at small as possible.
            neg_mask = 1.0 - pos_mask
            right = mid * pos_mask + right * neg_mask  # We have positive margin, reduce right hand side.
            last_right = mid * pos_mask + last_right * neg_mask  # Always sound, since the margin is positive.
            left = mid * neg_mask + left * pos_mask
        d = xl * use_tangent_line + last_right * (1. - use_tangent_line)
        # Return slope and bias.
        return [d, cycles]

    @staticmethod
    @torch.no_grad()
    def get_upper_left_bound(xl, steps=20):
        """Get a global upper bound given lower bound on x. Return slope and intercept."""
        dtype = xl.dtype
        # Constrain xl into the -0.5 pi to 1.5 pi region.
        cycles = torch.floor((xl - 0.5 * torch.pi) / (2 * torch.pi)) * (2 * torch.pi)
        xl = xl - cycles
        use_tangent_line = (xl >= 2.0 * torch.pi).to(dtype)
        # Case 1: xl > pi, Lower tangent line is the only possible lower bound.
        # Case 2: Binary search needed. Testing from another tangent endpoint in [pi, 1.5*pi]. It must be in this region.
        left = (2.0 * torch.pi) * torch.ones_like(xl)
        # The right end guarantees the margin > 0 because it is basically a IBP lower bound (-1).
        right = (2.5 * torch.pi) * torch.ones_like(xl)
        last_right = right.clone()
        for _ in range(steps):
            mid = (left + right) / 2.
            margin = BoundSin.check_bound(mid, xl)
            pos_mask = (margin > 0).to(dtype)  # We want to margin < 0 but at small as possible.
            neg_mask = 1.0 - pos_mask
            right = mid * neg_mask + right * pos_mask  # We have positive margin, reduce right hand side.
            last_right = mid * neg_mask + last_right * pos_mask  # Always sound, since the margin is positive.
            left = mid * pos_mask + left * neg_mask
        d = xl * use_tangent_line + last_right * (1. - use_tangent_line)
        # Return slope and bias.
        return [d, cycles]

    @staticmethod
    @torch.no_grad()
    def get_lower_right_bound(xu, steps=20):
        """Get a global lower bound given upper bound on x. Return slope and intercept."""
        # Constrain xu into the -0.5 pi to 1.5 pi region.
        cycles = torch.floor((xu + 0.5 * torch.pi) / (2 * torch.pi)) * (2 * torch.pi)
        xu = xu - cycles
        d, _ = BoundSin.get_lower_left_bound(torch.pi - xu, steps)
        return [3 * torch.pi - d, cycles - 2 * torch.pi]

    @staticmethod
    @torch.no_grad()
    def get_upper_right_bound(xu, steps=20):
        """Get a global upper bound given upper bound on x. Return slope and intercept."""
        # Constrain xu into the 0.5 pi to 2.5 pi region.
        cycles = torch.floor((xu - 0.5 * torch.pi) / (2 * torch.pi)) * (2 * torch.pi)
        xu = xu - cycles
        d, _ = BoundSin.get_upper_left_bound(3 * torch.pi - xu, steps)
        return [5 * torch.pi - d, cycles - 2 * torch.pi]

    def get_bound_tb(self, lb, ub):
        """Find lower or upper bounds from lookup table."""
        step = 2 * torch.pi / (BoundSin.n_table_entries - 1)
        # Move to 0 to 2 pi region.
        lb_cycles = torch.floor(lb / (2 * torch.pi)) * (2 * torch.pi)
        lb = torch.clamp(lb - lb_cycles, min=0, max=2 * torch.pi)
        ub_cycles = torch.floor(ub / (2 * torch.pi)) * (2 * torch.pi)
        ub = torch.clamp(ub - ub_cycles, min=0, max=2 * torch.pi)
        # Find the indice within the lookup table from 0 - 2pi.
        indices_lb = lb.div(step).long()
        indices_ub = ub.div(step).long()
        tangent_left_lower = BoundSin.xl_lower_tb[0][indices_lb]
        tangent_left_upper = BoundSin.xl_upper_tb[0][indices_lb]
        tangent_right_lower = BoundSin.xu_lower_tb[0][indices_ub]
        tangent_right_upper = BoundSin.xu_upper_tb[0][indices_ub]
        if self.opt_stage in ['opt', 'reuse']:
            if not hasattr(self, 'alpha'):
                # Raise an error if alpha is not created.
                self._no_bound_parameters()
            ns = self._start

            self.alpha[ns].data[8:10, :] = torch.min(
                torch.max(self.alpha[ns][8:10, :], tangent_left_lower), tangent_right_lower)
            self.alpha[ns].data[10:12, :] = torch.min(
                torch.max(self.alpha[ns][10:12, :], tangent_left_upper), tangent_right_upper)
            tangent_lower = self.alpha[ns][8:10, :]
            tangent_upper = self.alpha[ns][10:12, :]
        else:
            tangent_lower = (tangent_left_lower + tangent_right_lower) / 2
            tangent_upper = (tangent_left_upper + tangent_right_upper) / 2
            if self.opt_stage == 'init':
                ns = self._start
                self.tp_left_lower_init[ns] = tangent_left_lower.detach()
                self.tp_left_upper_init[ns] = tangent_left_upper.detach()
                self.tp_right_lower_init[ns] = tangent_right_lower.detach()
                self.tp_right_upper_init[ns] = tangent_right_upper.detach()

        d_lower = BoundSin.d_func(tangent_lower)
        b_lower = BoundSin.func(tangent_lower) - d_lower * (tangent_lower +
                    torch.where(tangent_lower <= 1.5*torch.pi,
                        BoundSin.xl_lower_tb[1][indices_lb] + lb_cycles,
                        BoundSin.xu_lower_tb[1][indices_ub] + ub_cycles))
        d_upper = BoundSin.d_func(tangent_upper)
        b_upper = BoundSin.func(tangent_upper) - d_upper * (tangent_upper +
                    torch.where(tangent_upper <= 2.5*torch.pi,
                        BoundSin.xl_upper_tb[1][indices_lb] + lb_cycles,
                        BoundSin.xu_upper_tb[1][indices_ub] + ub_cycles))
        return d_lower, b_lower, d_upper, b_upper

    def forward(self, x):
        return torch.sin(x)

    def interval_propagate(self, *v):
        # Check if a point is in [l, u], considering the 2pi period
        def check_crossing(ll, uu, point):
            return ((((uu - point) / (2 * torch.pi)).floor()
                     - ((ll - point) / (2 * torch.pi)).floor()) > 0).to(h_Ls.dtype)
        h_L, h_U = v[0][0], v[0][1]
        h_Ls, h_Us = self.forward(h_L), self.forward(h_U)
        # If crossing pi/2, then max is fixed 1.0
        max_mask = check_crossing(h_L, h_U, self.ibp_max_point)
        # If crossing pi*3/2, then min is fixed -1.0
        min_mask = check_crossing(h_L, h_U, self.ibp_min_point)
        ub = torch.max(h_Ls, h_Us)
        ub = max_mask + (1 - max_mask) * ub
        lb = torch.min(h_Ls, h_Us)
        lb = - min_mask + (1 - min_mask) * lb
        return lb, ub

    def bound_relax_impl(self, lb, ub):
        dtype = lb.dtype

        ub = torch.max(ub, lb + 1e-8)

        # Case 1: Connect the two points as a line
        sub = self.func(ub)
        slb = self.func(lb)
        mid = (sub + slb) / 2.
        smid = self.func((ub + lb) / 2)
        gap = smid - mid

        min_preact = 1e-3
        mask_close = (ub - lb) < min_preact
        case1_line_slope = torch.where(mask_close, self.d_act_func(ub),
            (sub - slb) / (ub - lb).clamp(min=1e-10))
        case1_line_bias = slb - case1_line_slope * lb
        # Check if there are crossings between the line and the sin function.
        grad_crossings = self.get_intersection(lb, ub, case1_line_slope, theta=0.5 * torch.pi)
        # If there is no crossing, then we can connect the two points together as a lower/upper bound.
        use_line = grad_crossings == 1
        # Connected line is the upper bound.
        upper_use_line = torch.logical_and(gap <  0, use_line)
        # Connected line is the lower bound.
        lower_use_line = torch.logical_and(gap >= 0, use_line)

        # Case 2: we will try the global lower/upper bounds at lb and ub.
        # For the points and lb and ub, we can construct both lower and upper bounds.
        (case_2_lower_slope, case_2_lower_bias,
            case_2_upper_slope, case_2_upper_bias) = self.get_bound_tb(lb, ub)

        # Finally, choose between case 1 and case 2.
        lower_use_line = lower_use_line.to(dtype)
        not_lower_use_line = 1. - lower_use_line
        upper_use_line = upper_use_line.to(dtype)
        not_upper_use_line = 1. - upper_use_line
        lower_slope = lower_use_line * case1_line_slope + not_lower_use_line * case_2_lower_slope
        lower_bias = lower_use_line * case1_line_bias + not_lower_use_line * case_2_lower_bias
        upper_slope = upper_use_line * case1_line_slope + not_upper_use_line * case_2_upper_slope
        upper_bias = upper_use_line * case1_line_bias + not_upper_use_line * case_2_upper_bias
        return lower_slope, lower_bias, upper_slope, upper_bias

class BoundCos(BoundSin):
    def __init__(self, attr=None, inputs=None, output_index=0, options=None):
        super().__init__(attr, inputs, output_index, options)

        self.ibp_max_point = 0.0
        self.ibp_min_point = torch.pi

    def forward(self, x):
        return torch.cos(x)

    def bound_relax(self, x, init=False, dim_opt=None):
        if init:
            self.init_linear_relaxation(x, dim_opt)
        # Shift the input by half_pi, and shifting the linear bounds back.
        half_pi = 0.5 * torch.pi
        lb = x.lower + half_pi
        ub = x.upper + half_pi
        self.generate_inflections(lb, ub)
        self.branch_input_domain(lb, ub)
        self.bound_relax_impl_sigmoid(lb, ub, self.act_func, self.d_act_func)
        if self.opt_stage is None and self.sigmoid_like_mask.all():
            self.lb = self.lw * half_pi + self.lb
            self.ub = self.uw * half_pi + self.ub
            return
        lower_slope, lower_bias, upper_slope, upper_bias = self.bound_relax_impl(lb, ub)
        self.lw = self.lw * self.sigmoid_like_mask + self.branch_mask * lower_slope
        self.lb = (self.sigmoid_like_mask * (self.lw * half_pi + self.lb)
                   + self.branch_mask * (lower_slope * half_pi + lower_bias))
        self.uw = self.uw * self.sigmoid_like_mask + self.branch_mask * upper_slope
        self.ub = (self.sigmoid_like_mask * (self.uw * half_pi + self.ub)
                   + self.branch_mask * (upper_slope * half_pi + upper_bias))

class BoundSec(BoundActivation):
    def __init__(self, attr=None, inputs=None, output_index=0, options=None):
        super().__init__(attr, inputs, output_index, options)
        self.ibp_intermediate = True

    def forward(self, x):
        return 1. / torch.cos(x)

    def bound_relax(self, x, init=False):
        assert x.lower.min() > -torch.pi / 2
        assert x.upper.max() < torch.pi / 2

        x_L = x.lower
        x_U = x.upper
        y_L = self.forward(x_L)
        y_U = self.forward(x_U)
        mask_close = x_U - x_L < 1e-8
        upper_k = torch.where(
            mask_close,
            y_L * torch.tan(x_L),
            (y_U - y_L) / (x_U - x_L).clamp(min=1e-8)
        )
        self.uw = upper_k
        self.ub = -upper_k * x_L + y_L

        mid = (x_L + x_U) / 2
        y_mid = self.forward(mid)
        lower_k = y_mid * torch.tan(mid)
        self.lw = lower_k
        self.lb = -lower_k * mid + y_mid

    def interval_propagate(self, *v):
        h_L, h_U = v[0][0], v[0][1]
        assert h_L.min() > -torch.pi / 2
        assert h_U.max() < torch.pi / 2
        y_L = self.forward(h_L)
        y_U = self.forward(h_U)
        lower = (h_U < 0) * (y_U - 1) + (h_L > 0) * (y_L - 1) + 1
        upper = torch.max(y_L, y_U)
        return lower, upper


import unittest
from unittest.mock import Mock, PropertyMock, AsyncMock, AsyncMock, call
from beep import BeepBoop, Listener, NodeRunner, EventHandler, zemke_id, NodeRunnerError
from sseclient import Event
from discord import Guild, TextChannel
import json
import asyncio

message = {
  "id": 5900,
  "category": "SHOUTBOX",
  "author": {"username": "Zemke"},
  "body": "Here is a message",
}

class Env:

  def __init__(self, env):
    self.env = env

  def getenv(self, field):
    return self.env[field] if field in self.env else None

# todo append slash if it's not already there to SCRIPT env
#  and test this of course

class TestBeepBoop(unittest.TestCase):

  @classmethod
  def setUpClass(cls):
    cls.listener_mock = Mock()
    cls.listener_factory = lambda x: listener_mock()

  def test_init(self):
    client_mock = Mock()
    env = {"DISCORD_TOKEN": "fdasfdsa/"}
    beepBoop = BeepBoop(client_mock, Env(env).getenv, self.listener_mock)
    client_mock.run.assert_not_called()
    client_mock.listen.assert_not_called()

  def test_listen_not(self):
    client_mock = Mock()
    env = {}
    thread_factory_mock = Mock()
    beepBoop = BeepBoop(
        client_mock,
        Env(env).getenv,
        self.listener_mock,
        thread_factory_mock)
    thread_factory_mock.inst.assert_not_called()

  def test_listen_not(self):
    client_mock = Mock()
    endpoint = "http://example.com"
    env = {"LISTEN": "1", "CHANNEL": "1234", "CWT_MESSAGE_SSE_ENDPOINT": endpoint}
    thread_factory_mock = Mock()
    listener_mock = Mock()
    listener_factory = lambda x: listener_mock(*x, endpoint, None, None)
    beepBoop = BeepBoop(
        client_mock,
        Env(env).getenv, listener_factory,
        thread_factory_mock)
    thread_factory_mock.inst.assert_called_once()
    listener_mock.assert_called_once_with(
          client_mock, int(env["CHANNEL"]), endpoint, None, None)

class EventHandlerTest(unittest.TestCase):

  def create_channel_mock(self):
    channel_mock = Mock()
    guild_mock = Mock()
    type(guild_mock).id = PropertyMock(return_value=5)
    type(channel_mock).guild = guild_mock
    type(channel_mock).id = 1234
    type(channel_mock).send = AsyncMock()
    return channel_mock

  def create_message_mock(self, content, channel_mock):
    message_mock = Mock()
    author_mock = Mock()
    type(author_mock).display_name = PropertyMock(return_value="Zemke")
    type(message_mock).author = PropertyMock(return_value=author_mock)
    type(message_mock).content = PropertyMock(return_value=content)
    type(message_mock).channel = PropertyMock(return_value=channel_mock)
    return message_mock

  def test_on_message_beep_bop(self):
    client_mock = Mock()
    type(client_mock).user = PropertyMock(return_value=Mock())
    event_handler = EventHandler(client_mock, None, None)
    channel_mock = self.create_channel_mock()
    message_mock = self.create_message_mock("!cwt", channel_mock)
    actual = asyncio.get_event_loop().run_until_complete(
        event_handler.on_message(message_mock))
    message_mock.channel.send.assert_called_once_with(BeepBoop.say_beep_boop)

  def test_on_message_self(self):
    client_mock = Mock()
    type(client_mock).user = PropertyMock(return_value=Mock())
    node_runner_mock = Mock()
    expected = "whatevernodereturns"
    node_runner_mock.handle = Mock(return_value=expected)
    event_handler = EventHandler(client_mock, node_runner_mock, None)
    channel_mock = self.create_channel_mock()
    message_mock = self.create_message_mock("!cwtcommands", channel_mock)
    actual = asyncio.get_event_loop().run_until_complete(
        event_handler.on_message(message_mock))
    message_mock.channel.send.assert_called_once_with(expected)

  def test_on_direct_message_other_user(self):
    channel_mock = self.create_channel_mock()
    message_mock = self.create_message_mock("Hello, there", channel_mock)
    node_runner_mock = Mock()
    handle_return_value = "these are the commands"
    node_runner_mock.handle = Mock(return_value=handle_return_value)
    client_mock = Mock()
    eventHandler = EventHandler(client_mock, node_runner_mock, None)
    asyncio.get_event_loop().run_until_complete(
        eventHandler.on_direct_message(message_mock))
    calls = [call(EventHandler.other_user_dm_response), call(handle_return_value)]
    message_mock.channel.send.assert_has_calls(calls)
    self.assertEqual(len(message_mock.channel.send.mock_calls), 2)

  def test_on_direct_message_send_to_all_channels(self):
    channel_mock = self.create_channel_mock()
    content = "hello to all"
    message_mock = self.create_message_mock("!adminannounce - " + content, channel_mock)
    node_runner_mock = Mock()
    client_mock = Mock()
    user_n_author_mock = Mock()
    type(user_n_author_mock).id = PropertyMock(return_value=zemke_id)
    type(client_mock).user = PropertyMock(return_value=user_n_author_mock)
    type(message_mock).author = PropertyMock(return_value=user_n_author_mock)
    text_channel_mock = Mock(spec=TextChannel)
    text_channel_mock.send = AsyncMock()
    all_channels = [text_channel_mock]
    client_mock.get_all_channels = Mock(return_value=all_channels)
    eventHandler = EventHandler(client_mock, node_runner_mock, None)
    asyncio.get_event_loop().run_until_complete(
        eventHandler.on_direct_message(message_mock))
    # send to all channels
    text_channel_mock.send.assert_called_once_with(content)
    # send to dm
    message_mock.channel.send.assert_called_once_with(EventHandler.confirm_all_channels)

  def test_on_direct_message_send_to_env_channel(self):
    channel_mock = self.create_channel_mock()
    content = "hello to env channel"
    message_mock = self.create_message_mock("!adminannounce x " + content, channel_mock)
    node_runner_mock = Mock()
    client_mock = Mock()
    user_n_author_mock = Mock()
    type(user_n_author_mock).id = PropertyMock(return_value=zemke_id)
    type(client_mock).user = PropertyMock(return_value=user_n_author_mock)
    type(message_mock).author = PropertyMock(return_value=user_n_author_mock)
    target_channel = Mock()
    type(target_channel).id = PropertyMock(return_value=4321)
    target_channel.send = AsyncMock()
    client_mock.get_channel = Mock(return_value=target_channel)
    eventHandler = EventHandler(client_mock, node_runner_mock, target_channel.id)
    asyncio.get_event_loop().run_until_complete(
        eventHandler.on_direct_message(message_mock))
    client_mock.get_channel.assert_called_once_with(target_channel.id)
    # send to env channel
    target_channel.send.assert_called_once_with(content)
    # send to dm
    message_mock.channel.send.assert_called_once_with("Message has been sent.")

  def test_on_direct_message_send_to_specific_channel(self):
    channel_mock = self.create_channel_mock()
    target_channel = Mock()
    target_channel_id = 72608314
    type(target_channel).id = PropertyMock(return_value=target_channel_id)
    target_channel.send = AsyncMock()
    content = "hello to specific channel"
    message_mock = self.create_message_mock(
        "!adminannounce %s %s " % (str(target_channel_id), content), channel_mock)
    node_runner_mock = Mock()
    client_mock = Mock()
    user_n_author_mock = Mock()
    type(user_n_author_mock).id = PropertyMock(return_value=zemke_id)
    type(client_mock).user = PropertyMock(return_value=user_n_author_mock)
    type(message_mock).author = PropertyMock(return_value=user_n_author_mock)
    client_mock.get_channel = Mock(return_value=target_channel)
    eventHandler = EventHandler(client_mock, node_runner_mock, channel_mock.id)
    asyncio.get_event_loop().run_until_complete(
        eventHandler.on_direct_message(message_mock))
    client_mock.get_channel.assert_called_once_with(target_channel.id)
    # send to specific channel
    target_channel.send.assert_called_once_with(content)
    # send to dm
    message_mock.channel.send.assert_called_once_with(EventHandler.confirm_specific_channel)

class NodeRunnerTest(unittest.TestCase):

  def create_runner_mock(self, stdout):
    completed_process = Mock()
    runner = Mock(return_value=completed_process)
    completed_process.check_returncode = Mock()
    stdout_type = Mock()
    stdout_type.decode = Mock(return_value=stdout)
    type(completed_process).stdout = PropertyMock(return_value=stdout_type)
    return runner

  def test_format_shoutbox(self):
    script = "~/with/trailing/slash/"
    node = "Zemke via CWT: “Here is a messsage”"
    stdout = "some other ouput\nRES xx %s\n" % node
    node_runner = NodeRunner(script, self.create_runner_mock(stdout))
    actual = node_runner.format(message)
    self.assertEqual(actual, node);
    runner_args = ["node", script + "format.js", message["category"],
                   message["author"]["username"], message["body"]]
    node_runner.runner.assert_called_once_with(runner_args)

  def test_format_news(self):
    script = "../src/no/trailing/slash"
    node = "BoolC via CWT: “BoolC 3–1 Taner”"
    stdout = "some other ouput\nRES xx %s\n" % node
    node_runner = NodeRunner(script, self.create_runner_mock(stdout))
    message = {
      "category": "NEWS",
      "author": {"username": "BoolC"},
      "body": "BoolC,Taner,3,1",
      "newsType": "REPORT"
    }
    actual = node_runner.format(message)
    self.assertEqual(actual, node)
    runner_args = ["node", script + "/format.js", message["category"],
                   message["author"]["username"], message["body"],
                   message["newsType"]]
    node_runner.runner.assert_called_once_with(runner_args)

  def test_handle(self):
    script = "../src/no/trailing/slash"
    guildId = 1234
    channelId = 12345
    node = \
        "The CWT bot commands are !cwtchat, !cwtdice, !cwthell, !cwtterrain, " \
        "!cwtwinners, !cwtcommands, !cwtschedule, !cwtplayoffs, " \
        "!cwtwhatisthisthing, !cwtrafkagrass, !cwturl, !cwtgithub."
    stdout = "getting current tournament\n" \
           "cmd !cwtcommands\n" \
           "RES xx %s\n" % node
    node_runner = NodeRunner(script, self.create_runner_mock(stdout))
    actual = node_runner.handle("!cwtcommands", "Zemke", guildId, channelId)
    self.assertEqual(actual, node)
    runner_args = ["node", script + "/handle.js", "DISCORD",
                   'https://discord.com/channels/1234/12345',
                   "Zemke", "!cwtcommands"]
    node_runner.runner.assert_called_once_with(runner_args)

  def test_format_fail(self):
    runner = Mock()
    runner.check_returncode = Mock()
    stdout = "An error happened"
    node_runner = NodeRunner('.', self.create_runner_mock(stdout))
    self.assertRaises(NodeRunnerError, node_runner.format, message)

  def test_handle_fail(self):
    runner = Mock()
    runner.check_returncode = Mock()
    stdout = "An error happened"
    node_runner = NodeRunner('.', self.create_runner_mock(stdout))
    self.assertRaises(NodeRunnerError, node_runner.handle, "!cwtcommand", "Zemke", 1234, 12345)

class ListenerTest(unittest.TestCase):

  def test_listen(self):
    script = "./"
    node_runner = Mock()
    formatted = "Zemke via CWT: “Here is a message”"
    node_runner.format = Mock(return_value=formatted)
    channel = "1234"
    endpoint = "http://example.com"
    client_mock = Mock()
    guild_mock = Mock()
    channel_mock = Mock()
    type(channel_mock).id = PropertyMock(return_value=1234)
    type(guild_mock).text_channels = PropertyMock(return_value=[channel_mock])
    type(client_mock).guilds = PropertyMock(return_value=[guild_mock])
    message = {
      "id": 5000,
      "category": "SHOUTBOX",
      "author": {"username": "Zemke"},
      "body": "Here is a message",
    }
    events = [Event(event="EVENT", data=json.dumps(message))]
    open_stream = type("SSEClient", (object,), {"events": lambda x: events })
    listener = Listener(client_mock, channel, node_runner, open_stream)
    cb_mock = Mock()
    listener.listen(1, cb_mock)
    cb_mock.assert_called_once_with(int(channel), formatted)

  def test_process_message_empty(self):
    message = {}
    channel = "1234"
    listener = Listener(Mock(), channel, Mock(), Mock())
    cb = Mock()
    with self.assertLogs('discord', level='WARN') as logged:
      listener.process_message(message, channel, cb)
    cb.assert_not_called()
    self.assertIn('WARNING:discord:Data has no ID: {}', logged.output)

  def test_process_message_other_attr_missing(self):
    message = {"body": "hello there", "id": 4000}
    node_runner = Mock()
    node_runner.format = Mock(side_effect=Exception('any error'))
    listener = Listener(Mock(), None, node_runner, Mock())
    cb = Mock()
    with self.assertLogs('root', level='ERROR') as logged:
      listener.process_message(message, "1234", cb)
    cb.assert_not_called()
    self.assertTrue(len(logged.output) == 1)
    self.assertIn('ERROR:root:Error while sending', logged.output[0])

if __name__ == '__main__':
    unittest.main()


# Import the libraries
from google.cloud import pubsub_v1
from google.cloud import storage
from google.cloud import translate_v2 as translate
from google.cloud import vision
import base64
import json
import os

# Instantiates a clients
vision_client = vision.ImageAnnotatorClient()
translate_client = translate.Client()
publisher = pubsub_v1.PublisherClient()
storage_client = storage.Client()

def send_extracted(text, filename, src_lang):
    """Send extracted text to translating or result queue"""

    project_id = os.environ["PROJECT_ID"]
    topic_name = os.environ["TRANSLATE_TOPIC"]
    target_langs = os.environ["TARGET_LANG"].split(",")

    # If source language matches with target language or undefined
    # Skip translating and send to RESULT_TOPIC
    if (len(target_langs) == 1 and src_lang == target_langs) or src_lang == "und":
        topic_name = os.environ["RESULT_TOPIC"]

    # Message body
    message = {
        "text": text,
        "filename": filename,
        "lang": target_langs,
        "src_lang": src_lang,
    }

    # Topic to publish messages
    topic_path = publisher.topic_path(project_id, topic_name)
    # Create a bytestring representing the message body
    message_data = json.dumps(message).encode("utf-8")
    # Publishing messages to Pub/Sub
    future = publisher.publish(topic_path, data=message_data)
    future.result()

def send_translated(text, filename, target_lang):
    """Send extracted text to result queue"""

    project_id = os.environ["PROJECT_ID"]
    topic_name = os.environ["RESULT_TOPIC"]

    # Message body
    message = {
        "text": text,
        "filename": filename
    }

    # Topic to publish messages
    topic_path = publisher.topic_path(project_id, topic_name)
    # Create a bytestring representing the message body
    message_data = json.dumps(message).encode("utf-8")
    # Publishing messages to Pub/Sub
    future = publisher.publish(topic_path, data=message_data)
    future.result()

def validate_property(event, property):
    """Validating if property exist in request then return its value"""
    if property not in event:
        raise ValueError(
            "{} is not provided. Make sure you have property {} in the request"
            .format(property, property)
        )

    return event[property]

def extract_text(event, context):
    """Background Cloud Function to be triggered by Cloud Storage
       logs relevant data when a file is changed/uploaded
    """
    # Validate and get property value
    bucket = validate_property(event, "bucket")
    filename = validate_property(event, "name")

    # Loads the image from GCS
    image = vision.Image(
        source=vision.ImageSource(gcs_image_uri=f"gs://{bucket}/{filename}")
    )

    # Perform text detection
    print("Extracting text from {}".format(f"gs://{bucket}/{filename}"))
    response = vision_client.text_detection(image=image)
    result = response.text_annotations
    if len(result) > 0:
        text = result[0].description.lstrip()
        print("Detected text: {}".format(repr(text)))
    else:
        print("No text detected from {}".format(filename))
        return None # end

    # Perform language detection
    detect_lang = translate_client.detect_language(text)
    src_lang = detect_lang["language"]
    print("Detected source language: '{}'".format(src_lang))

    # Send to Pub/Sub
    print("Sending message to Pub/Sub")
    send_extracted(text, filename, src_lang)
    print("Finished extracting file {}".format(filename))

def translate_text(event, context):
    """Background Cloud Function to be triggered by Pub/Sub.
    Translating text to target languages"""

    # The Pub/Sub message's data is stored as a base64-encoded string in the data property
    if 'data' in event:
        # Decode to extract data
        data = base64.b64decode(event["data"]).decode("utf-8")
        message = json.loads(data)
    else:
        raise ValueError("Data sector is missing in the Pub/Sub message.")

    # Validate and get property value
    text = validate_property(message, "text")
    filename = validate_property(message, "filename")
    src_lang = validate_property(message, "src_lang")
    target_lang = validate_property(message, "lang")

    print("Received request to translating file {}".format(filename))

    # Perform Translation
    print("Translating text into: {}".format(target_lang))
    translated_text = []
    for lang in target_lang:
        # Skip translating if source language matches with target language translating
        if lang == src_lang:
            result = text.replace("\n", " ")
        else:
            response = translate_client.translate(
                text, target_language=lang, source_language=src_lang)
            result = response['translatedText']
        translated_text.append(f"{lang}: {result}")
    text = "\n".join(translated_text)+"\n"

    # Send to Pub/Sub
    print("Sending message to Pub/Sub")
    send_translated(text, filename, target_lang)
    print("Finished translating file {}".format(filename))

def save_result(event, context):
    """Background Cloud Function to be triggered by Pub/Sub.
    Send result text to result
    """
    # The Pub/Sub message's data is stored as a base64-encoded string in the data property
    if 'data' in event:
        # Decode to extract data
        message_data = base64.b64decode(event["data"]).decode("utf-8")
        message = json.loads(message_data)
    else:
        raise ValueError("Data sector is missing in the Pub/Sub message.")

    # Validate and get property value
    text = validate_property(message, "text")
    filename = validate_property(message, "filename")

    print("Received request to save file {}".format(filename))

    bucket_name = os.environ["RESULT_BUCKET"]
    result_filename = "{}_translated.txt".format(filename)
    bucket = storage_client.get_bucket(bucket_name)
    blob = bucket.blob(result_filename)

    # Upload blob's content
    blob.upload_from_string(text)
    print("File saved to {}".format(f"gs://{bucket}/{filename}"))


class Dog():
    age = 0
    name = ''
    weight = 0
toyterier = Dog()
toyterier.age = 2
toyterier.name = '3.14159265358979'
toyterier.weight = 2.7182818284590

class Person():
    name = ''
    cellphone = ''
    email = ''
a = Person()
b = Person()
a.name = 'xyz'
b.name = 'abc'
a.cellphone = '2.7182818284590'
a.email = 'fkgjhakgjhera@fkijdfgkoj.jr'
b.cellphone = '2.7182818284590'
b.email = 'fkgjhakgjhera@fkijdfgkoj.jr'

class Bird():
    color = ''
    name = ''
    breed = ''
a = Bird()

class Player():
    name = ''
    force = 0
    xcord = 0
    ycord = 0
a = Player()

##############################5 программист не обращается к атрибуту, а просто создаёт новые переменные
##############################6 да
##############################7 у боба не задано имя
class Cat():
    name = ''
    color = ''
    weight = 0
    def meow(self):
        print('мя', end = '')
        print('а',end = '')
        print('у')
a = Cat()
a.color = 'blue'
a.name = '3.1415door'
a.weight = 123**123
a.meow()

class Monster():
    name = ''
    health = 100
    def decrease_health(self,dmg):
        self.health -=dmg
        if self.health<=0:
            print('монстр умер')
a = Monster()
a.decrease_health(100)

class Aadress():
    attribute1 = ''
    attribute2 = ''
    def print_att(self):
        print(self.attribute1)
        print(self.attribute2)
a = Aadress()

import random
class Warrior():
    """класс воителя. Есть дамаг и хп"""
    hp = 100
    dmg = 20
    def damage(self):
        """метод, возвращающий кол-во дамага война"""
        return self.dmg
    def defence(self,a):
        """метод, берущий число, вычитающий его из хп. Если хп персонажа стало меньше или равно 0,
        тогда метод возвращает ложь, иначе правду"""
        self.hp-=a
        if self.hp<=0:
            return False
        else:
            return True
a = 10 # кол-во войнов
warriors = [Warrior() for i in range(0,a)]#генерация войнов
inGame = True # значения для отслеживания, идёт ли игра
# цикл игры
while inGame: # выход если значение inGame сменится на ложь
    # выбор атакующего и защищающегося
    damPl = random.randint(0,a-1)
    defPl = random.randint(0,a-1)
    # защита от суицида
    while damPl == defPl:
        defPl = random.randint(0,a-1)
    # сама битва, сменит значение inGame, если кто-то умрёт
    inGame = warriors[defPl].defence(warriors[damPl].damage())
    # вывод атакующего и защищающегося
    print(damPl,'юнит атаковал, у юнита ',defPl,'осталось',warriors[defPl].hp)
# после цикла, вывод последнего атакующего и защищающегося
print('Игрок',damPl,'Убил игрока',defPl)

"генерация случайных чисел"
import random
class Person():
    """родительский класс person. В конструктор берёт принадлежность команде, и создаёт id """
    def __init__(self,a):
        self.id = random.randint(0,100)
        self.comm = a
class Hero(Person):
    """класс герой имеет уровень и солдат, следующих за ним """
    lvl = 0
    solders = []
    def add_solder(self,solder):
        "добавляет солдата к массиву солдата"
        self.solders.append(solder)
    def solders_id(self):
        "выводит id следующих за героем солдат "
        for i in range(len(self.solders)):
            print(self.solders[i].id)
    def lvl_up(self):
        "увеличивает уровень героя на 1"
        self.lvl +=1
    def print_id(self):
        "выводит свой id, а потом id солдат"
        print(self.id)
        self.solders_id()
class Solder(Person):
    "класс солдат может идти за героем"
    def go_with_hero(self,herocom):
        "заставлет идти за героем из заданого массива"
        herocom[0].add_solder(self)
# генерация команд
num_comm = 6
comm = [[Hero(i)]for i in range(0,num_comm)]
# массив с будующими солдатами
solders = []
# заполнение массива с солдатами солдатами
for i in range(20):
    a = random.randint(0,num_comm-1)
    comm[a].append(Solder(a))
# вычисление команды с наибольшим кол-ом персонажей
t = 0
b = 0
for i in range(num_comm-1):
    if  t<len(comm[i]):
        t = len(comm[i])
        b = i
# апаем героя нужной команды
comm[b][0].lvl_up()
# выводим уровни героев всех команд, чтобы предыдущие действия не казались бессмысленными
for i in range(num_comm):
    print(comm[i][0].lvl)
# заставляем солдата 0 команды идти за героем
comm[0][1].go_with_hero(comm[0])
# выводим id 0 героя и всех солдат идущих за ним
comm[0][0].print_id()


"""A base class for expressions"""

import time
from unittest import TestCase

from dogpile.cache import register_backend

# from freezegun import freeze_time
from sqlalchemy import (
    JSON,
    Boolean,
    Column,
    Date,
    DateTime,
    Float,
    Integer,
    String,
    Table,
)

from sqlalchemy_recipe.dbinfo import get_dbinfo
from tests.utils import test_db

utc_offset = -1 * time.localtime().tm_gmtoff / 3600.0 + time.localtime().tm_isdst

register_backend("dictionary", "sqlalchemy_recipe.caching", "DictionaryBackend")

configs = [
    {
        "sqlalchemy.url": f"sqlite:///{test_db}",
        "sqlalchemy.pool_pre_ping": True,
        "cache.cache_queries": True,
        "cache.dogpile.backend": "dictionary",
        "cache.dogpile.expiration_time": 2,
    },
    {"sqlalchemy.url": f"sqlite:///{test_db}"},
]

class ExpressionTestCase(TestCase):
    """Grammar is constructed correctly."""

    def setup_tables(self):
        """Build a bunch of tables with the current dbinfo"""
        basic_table = Table(
            "foo",
            self.dbinfo.metadata,
            Column("first", String),
            Column("last", String),
            Column("age", Integer),
            Column("birth_date", Date),
            Column("dt", DateTime),
            extend_existing=True,
        )

        datetester_table = Table(
            "datetester",
            self.dbinfo.metadata,
            Column("dt", Date),
            Column("count", Integer),
            extend_existing=True,
        )

        scores_table = Table(
            "scores",
            self.dbinfo.metadata,
            Column("username", String),
            Column("department", String),
            Column("testid", String),
            Column("score", Float),
            Column("test_date", Date),
            extend_existing=True,
        )

        datatypes_table = Table(
            "datatypes",
            self.dbinfo.metadata,
            Column("username", String),
            Column("department", String),
            Column("testid", String),
            Column("score", Float),
            Column("test_date", Date),
            Column("test_datetime", DateTime),
            Column("valid_score", Boolean),
            extend_existing=True,
        )

        scores_with_nulls_table = Table(
            "scores_with_nulls",
            self.dbinfo.metadata,
            Column("username", String),
            Column("department", String),
            Column("testid", String),
            Column("score", Float),
            Column("test_date", Date),
            extend_existing=True,
        )

        tagscores_table = Table(
            "tagscores",
            self.dbinfo.metadata,
            Column("username", String),
            Column("tag", String),
            Column("department", String),
            Column("testid", String),
            Column("score", Float),
            extend_existing=True,
        )

        id_tests_table = Table(
            "id_tests",
            self.dbinfo.metadata,
            Column("student", String),
            Column("student_id", Integer),
            Column("age", Integer),
            Column("age_id", Integer),
            Column("score", Float),
            extend_existing=True,
        )

        census_table = Table(
            "census",
            self.dbinfo.metadata,
            Column("state", String),
            Column("sex", String),
            Column("age", Integer),
            Column("pop2000", Integer),
            Column("pop2008", Integer),
            extend_existing=True,
        )

        state_fact_table = Table(
            "state_fact",
            self.dbinfo.metadata,
            Column("id", String),
            Column("name", String),
            Column("abbreviation", String),
            Column("sort", String),
            Column("status", String),
            Column("occupied", String),
            Column("notes", String),
            Column("fips_state", String),
            Column("assoc_press", String),
            Column("standard_federal_region", String),
            Column("census_region", String),
            Column("census_region_name", String),
            Column("census_division", String),
            Column("census_division_name", String),
            # JSON is an unusable datatype for expressions
            Column("circuit_court", JSON),
            extend_existing=True,
        )

        self.tables = [
            basic_table,
            datetester_table,
            scores_table,
            datatypes_table,
            scores_with_nulls_table,
            tagscores_table,
            id_tests_table,
            census_table,
            state_fact_table,
        ]

    def setUp(self):
        config = {
            "sqlalchemy.url": f"sqlite:///{test_db}",
            "sqlalchemy.pool_pre_ping": True,
            "cache.cache_queries": True,
            "cache.dogpile.backend": "dictionary",
            "cache.dogpile.expiration_time": 2,
        }
        self.dbinfo = get_dbinfo(config)
        self.setup_tables()
        return super().setUp()


import re,os
import pandas as pd
import numpy as np
import sys

"""importing file to extract sentences and tokens for creating corpus unigrams and bigrams
frequency dictionaries"""

def format_file(file):
    read_file=open(file,'r')
    string_file=read_file.read()
    sentences=string_file.replace('\n',' ')
    sentences=re.split('\\s+\.\\s+',sentences)

    sentence_tokens = []
    corpus_words=[]
    for i in sentences:
        word_list=re.split('\\s+',i)
        #print(word_list)
        corpus_words+=word_list
        sentence_tokens.append(word_list)

    return(sentence_tokens,corpus_words)

def get_unigram_count(c_words):
        unigrams={}
        for i in c_words:
            if i in unigrams:
                unigrams[i]+=1
            else:
                unigrams[i]=1
        return unigrams

def get_bigram_count(s_tokens):
    bigrams={}
    for i in s_tokens:
        for j in range(1,len(i)):
            if(i[j-1],i[j]) in bigrams:
                bigrams[i[j-1],i[j]]+=1
            else:
                bigrams[i[j-1],i[j]]=1
    return bigrams

def no_smoothing(bigrams,unigrams,w_dir):
    f1=w_dir+"\\no_smoothing_count.txt"
    f2=w_dir+"\\no_smoothing_prob.txt"

    try:
        os.remove(f1)
        os.remove(f2)
    except FileNotFoundError:
        pass

    bigrams_prob={}
    for i in bigrams:
        c_i=bigrams[i]
        wi=i[1]
        wj=i[0]
        if wj in unigrams:
            wj_count=unigrams[wj]
            key_prob="P("+wi+"|"+wj+")"
            bigrams_prob[key_prob]=round((c_i/wj_count),4)

    #print(len(bigrams_prob))

    with open(f1,'a+') as countfile:
        for i in bigrams:
            countfile.write("%s : %d \n" %(i,bigrams[i]))

    with open(f2,'a+') as probfile:
        for i in bigrams_prob:
            probfile.write("%s : %f \n" %(i,bigrams_prob[i]))

    countfile.close()
    probfile.close()
    return f1,f2

def add1_smoothing(bigrams,unigrams,corpus_words,w_dir):
    f1=w_dir+"\\add1_smoothing_countStar.txt"
    f2=w_dir+"\\add1_smoothing_prob.txt"

    try:
        os.remove(f1)
        os.remove(f2)
    except FileNotFoundError:
        pass

    new_bigrams_countStar ={}
    new_bigrams_prob={}
    V = len(unigrams)
    N = len(corpus_words)
    for i in bigrams:
        prb = round((bigrams[i]+1)/(unigrams[i[0]]+V),4)
        countStar =round((bigrams[i]+1)*(N/(N+V)),4)
        if prb > 0:
            key="P("+i[1]+"|"+i[0]+")"
            new_bigrams_prob[key]=prb
        if countStar >0:
            new_bigrams_countStar[i]=countStar

    unseen_bigrams_prob={}
    unseen_bigrams_countStar={}
    for i in corpus_words:
        word0=i
        word1=r'.+'
        if (word0,word1) not in bigrams:
            prb = round(1/(unigrams[word0]+V),4)
            countStar =round(1*(N/(N+V)),4)
            if prb > 0:
                key="P("+word1+"|"+word0+")"
                unseen_bigrams_prob[key]=prb
                #print(word0,word1,prb,unigrams[word0],countStar)
            if countStar > 0:
                unseen_bigrams_countStar[(word0,word1)]=countStar

    add1_smoothing_countStar = {**unseen_bigrams_countStar,**new_bigrams_countStar}
    with open(f1,'a+') as countfile:
        for i in add1_smoothing_countStar:
            countfile.write("%s : %f \n" %(i,add1_smoothing_countStar[i]))

    add1_smoothing_prob = {**unseen_bigrams_prob,**new_bigrams_prob}
    with open(f2,'a+') as probfile:
        for i in add1_smoothing_prob:
            probfile.write("%s : %f \n" %(i,add1_smoothing_prob[i]))

    countfile.close()
    probfile.close()

    return f1,f2

def good_turing_discounting(unigrams,bigrams,corpus_words,w_dir):
    f1=w_dir+"\\GT_countStar.txt"
    f2=w_dir+"\\GT_prob.txt"

    try:
        os.remove(f1)
        os.remove(f2)
    except FileNotFoundError:
        pass

    N = len(corpus_words)
    bucket_NC= {}

    for key, value in sorted(bigrams.items()):
        bucket_NC.setdefault(value, []).append(key)

    GT_unseen_prob={}
    for i in corpus_words:
        word0=i
        word1=r'.+'
        if (word0,word1) not in bigrams:
            prb=len(bucket_NC[1])/N
            if prb > 0:
                key="P("+word1+"|"+word0+")"
                GT_unseen_prob[key]=prb

    GT_seen_prob={}
    GT_seen_count={}
    Nb= sum(bigrams.values())
    for i in bucket_NC:
        #print("bucket with freqency of frequency as ",i)
        for j in bucket_NC[i]:
            Nd = len(bucket_NC[i])
            if i+1 in bucket_NC:
                Nn= len(bucket_NC[i+1])
            else:
                Nn= 0
            c_star= round((bigrams[j]+1)*Nn/Nd,4)
            p_star = round(c_star/Nb,4)
            if p_star > 0:
                key="P("+j[1]+"|"+j[0]+")"
                GT_seen_prob[key]=p_star
            if c_star > 0:
                GT_seen_count[j]=c_star

    with open(f1,'a+') as countfile:
        for i in GT_seen_count:
            countfile.write("%s : %f \n" %(i,GT_seen_count[i]))

    GT_prob = {**GT_seen_prob,**GT_unseen_prob}
    with open(f2,'a+') as probfile:
        for i in GT_prob:
            probfile.write("%s : %f \n" %(i,GT_prob[i]))

    countfile.close()
    probfile.close()

    return f1,f2

def main():
    working_dir = os.path.dirname(os.path.realpath(sys.argv[0]))

    if(len(sys.argv)>1):
        file = sys.argv[1]
    else:
        file="C:\\Users\\taniy\\Desktop\\Spring19\\CS6320-NLP\\HWS\\HW2\\HW2_F18_NLP6320-NLPCorpusTreebank2Parts-CorpusA-Windows.txt"

    s_tokens,c_words=format_file(file)
    u_grams= get_unigram_count(c_words)
    b_grams=get_bigram_count(s_tokens)
    c_no_smoothing,p_no_smoothing = no_smoothing(b_grams,u_grams,working_dir)
    c_add1_smoothing,p_add1_smoothing = add1_smoothing(b_grams,u_grams,c_words,working_dir)
    c_GT,p_GT = good_turing_discounting(u_grams,b_grams,c_words,working_dir)

    print("############################################")
    print("PROCESSING INFORMATION")
    print("Total Sentences -> ",len(s_tokens))
    print("Total Words -> ",len(c_words))
    print("Total Unigrams -> ",len(u_grams))
    print("Total Bigrams -> ",len(b_grams))
    print("############################################")
    print("Working Directory -> ",working_dir)
    print("############################################")
    print("No Smoothing Output files Location")
    print("############################################")
    print("Count Output File -> ",c_no_smoothing)
    print("Probability Output File -> ",p_no_smoothing)
    print("############################################")
    print("Add 1 Smoothing Output files Location")
    print("############################################")
    print("Count Output File -> ",c_add1_smoothing)
    print("Probability Output File -> ",p_add1_smoothing)
    print("############################################")
    print("Good Turing Discounting Output files Location")
    print("############################################")
    print("Count Output File -> ",c_GT)
    print("Probability Output File -> ",p_GT)

if __name__=="__main__":
    main()


from functools import lru_cache

@lru_cache
def f(n):
    if n < 3:
        return n + 1
    if n >= 3 and (n % 2 == 0):
        return f(n - 2) + n - 2
    if n >= 3 and (n & 1):
        return f(n + 2) + n - 2

count = 0
for i in range(1, 10_000):
    try:
        if len(str(f(i))) == 5:
            count += 1
    except:
        ...
print(count)


import sys,os
from typing import List
from collections import defaultdict, Counter
from itertools import groupby, chain, product
import heapq
from pprint import pprint
import string

class Rake():
    def __init__(
            self,
            text:List[List[str]],
            stopwords=[],
            max_words=100,
            min_chars=3
        ):

        self.text = text
        self.stopwords = stopwords
        self.blacklist = set(chain(stopwords, string.punctuation))
        self._phrases = set()
        # Частота (freq(w)) определяется как количество фраз,
        # в которые входит рассматриваемое слово
        self.freq = Counter()
        # Степень (deg(w)) определяется как суммарное количество слов,
        # из которых состоят фразы, в которых оно содержится.
        self.degree = Counter()
        # Вес слова определим как отношение степени слова к его частоте:
        # s(w) = deg(w)/freq(w)
        self.token_weights = Counter()
        self.phrase_scores = Counter()
        self.min_chars = min_chars
        self.max_words = max_words

        self._generate_phrases()
        self._calc_frequencies()
        self._calc_weights()
        self._calc_scores()

    def _generate_phrases(self):
        '''Create contender phrases from sentences.'''

        for sent in self.text:
            self._phrases.update(self._get_phrase_list(sent))

    def _get_phrase_list(self,sent):
        '''Grouping the left words into phrases'''

        groups = groupby(sent, lambda x: x not in self.blacklist)
        phrases = [tuple(group[1]) for group in groups if group[0]]
        result = []

        for phrase in phrases:
            if (
                    phrase
                    and len(' '.join(phrase)) >= self.min_chars
                    and len(phrase) <= self.max_words
            ):
                result.append(phrase)
        #print('_get_phrase_list')
        #pprint(result)
        return result

    def _calc_frequencies(self):
        '''Calculation of frequencies of words'''
        for phrase in self._phrases:
            for token in phrase:
                self.freq[token] += 1
                self.degree[token] += len(phrase) - 1 # 1 вычитается не везде; смысл?

        # не во всех примерах Rake используется добавление частоты к degree ; смысл?
        for token in self.freq:
            self.degree[token] += self.freq[token]

    def _calc_frequencies2(self):

        self.freq = Counter(chain.from_iterable(self._phrases))
        def build_occurance_graph():

            graph = defaultdict(lambda: defaultdict(int))
            for phrase in self._phrases:
                # For each phrase in the phrase list, count co-occurances of the
                # word with other words in the phrase.
                #
                # Note: Keep the co-occurances graph as is, to help facilitate its
                # use in other creative ways if required later.
                for (word, coword) in product(phrase, phrase):
                    graph[word][coword] += 1
            return graph

        graph = build_occurance_graph()
        self.degree = defaultdict(int)
        for token in graph:
            self.degree[token] = sum(graph[token].values())

        pprint(graph )

    def _calc_weights(self):
        # веса слов s(w) = deg(w)/freq(w)
        for token in self.freq:
            score = self.degree[token] / (self.freq[token] * 1.0)
            self.token_weights[token] += score

    def _calc_scores(self):

        for phrase in self._phrases:
            #print(phrase,self._phrases.count(phrase))
            score = sum(self.token_weights.get(token,0) for token in phrase)
            self.phrase_scores[' '.join(phrase)] += score

    def topn(self,n=7,phrase=True):
        '''Get top phrases with ratings'''

        if phrase:
            scores = self.phrase_scores
        else:
            scores = self.token_weights
        if n < 0:
            n = len(scores)

        return heapq.nlargest(n,
            scores,
            key=scores.get
        )

    def phrases(self,scores=True):
        if scores:
            result = sorted(
                self.phrase_scores.items(),
                key=lambda t:t[1],
                reverse=True
            )

        else:
            result = sorted(
                self.phrase_scores,
                key=self.phrase_scores.get,
                reverse=True
            )
        return result

    def get_token_weights(self,scores=True):
        if scores:
            result = sorted(
                self.token_weights.items(),
                key=lambda t:t[1],
                reverse=True
            )

        else:
            result = sorted(
                self.token_weights,
                key=self.token_weights.get,
                reverse=True
            )
        return result


"""Take URL and return URL of archive.org snapshot.

Given a URL and (optionally) a timestamp, return the URL of the archive.org
snapshot closest to the provided timestamp. If no timestamp is provided or no
snapshot for the provided timestamp cannot be found, return the latest
snapshot. If no snapshot available, return None.
"""

import asyncio
import sys
from typing import Any, Optional

import httpx
import tenacity

@tenacity.retry(stop=tenacity.stop_after_attempt(5), wait=tenacity.wait_fixed(2))
async def call_api(client: httpx.AsyncClient, api_call: str) -> dict[str, Any]:
    """Call Wayback Machine API and return JSON response.

    If API is unresponsive, sleep task for two seconds for a maximum of five times.

    Expect the following API responses:

    - URL with correctly formatted timestamp (YYYYMMDDhhmmss):
        JSON with nearest snapshot, latest snapshot if timestamp was not found
    - URL without timestamp:
        JSON with latest snapshot available
    - Any URL with badly formatted timestamp (e.g. 'May2000'):
        Empty JSON
    - URL that is has not available in archive.org:
        Empty JSON

    Args:
        client (httpx.AsyncClient): HTTPX AsyncClient to make API calls
        api_call (str): Valid call to archive.org API

    Returns:
        dict[str, Any]: JSON API response
    """
    response: httpx.Response = await client.get(api_call)
    response.raise_for_status()
    return response.json()

def build_api_call(url: str, timestamp: Optional[str] = None) -> str:
    """Return valid achive.org API call.

    Args:
        url (str): URL to be searched in the Wayback Machine
        timestamp (Optional[str], optional): Timestamp for desired snapshot

    Returns:
        str: Valid archive.org API call
    """
    api_call: str = f"https://archive.org/wayback/available?url={url}"
    if timestamp:
        api_call += f"&timestamp={timestamp}"
    return api_call

def get_closest(api_response: dict[str, Any]) -> Optional[str]:
    """Get URL of closest snapshot from API response if available.

    Returns None if API response is empty.

    Args:
        api_response: dict[str, Any]: API response as JSON

    Returns:
        Optional[str]: URL of Wayback Machine snapshot, if any was found
    """
    if not api_response["archived_snapshots"]:
        return None
    return api_response["archived_snapshots"]["closest"]["url"]

async def gather_snapshots(
    urls: list[str], timestamp: str = None
) -> dict[str, Optional[str]]:
    """Create HTTPX session for API calls and return gathered snapshots.

    To make asynchronous calls, create a task list for calling the call_api function
    and get results with asyncio.gather(). The completed tasks will then be used to
    build a dict of url-snapshot pairs.

    Args:
        urls (list[str]): Urls to send to the Wayback Machine API
        timestamp (Optional[str]): Timestamp to send to the Wayback Machine API

    Returns:
        dict[str, Optional[str]]: API call results with original URL as keys and Wayback
                                  snapshot URLs as values
    """
    async with httpx.AsyncClient(timeout=None) as client:
        # Create task list (with each task being an API call)
        tasks: list[asyncio.Task[Any]] = []
        for url in urls:
            tasks.append(
                asyncio.create_task(call_api(client, build_api_call(url, timestamp)))
            )
        # Execute tasks and gather results. If a task failed five times, exit program
        try:
            api_responses: list[dict[str, Any]] = await asyncio.gather(*tasks)
        except tenacity.RetryError:
            sys.exit("API appears unresponsive, please try again later.")
        # Build url-snapshot pairs from results
        wayback_urls: dict[str, Optional[str]] = {}
        for api_response in api_responses:
            wayback_urls[api_response["url"]] = get_closest(api_response)
    return wayback_urls


import torch
import torch.nn as nn
import numpy as np
import torch.nn.functional as F
import time
import copy
from torch.distributions import Normal, MultivariateNormal, Beta
from ReplayBuffer import ReplayBuffer
from utils import action_adapter
import math

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

class BetaActor(nn.Module):
    def __init__(self, obs_shape, action_shape, hidden_num=128):
        super(BetaActor, self).__init__()

        self.l1 = nn.Linear(obs_shape, hidden_num)
        self.l2 = nn.Linear(hidden_num, hidden_num)
        self.alpha_head = nn.Linear(hidden_num, action_shape)
        self.beta_head = nn.Linear(hidden_num, action_shape)

    def forward(self, input):
        a = torch.tanh(self.l1(input))
        a = torch.tanh(self.l2(a))

        alpha = F.softplus(self.alpha_head(a)) + 1.0
        beta = F.softplus(self.beta_head(a)) + 1.0

        return alpha, beta

    def get_dist(self, state):
        alpha, beta = self.forward(state)
        dist = Beta(alpha, beta)
        return dist

    def dist_mode(self, state):
        alpha, beta = self.forward(state)
        mode = (alpha) / (alpha + beta)
        return mode

class actor(nn.Module):
    def __init__(self, obs_shape, action_shape, hidden_num=128):
        super(actor, self).__init__()
        # parameters
        self.obs_shape = obs_shape
        self.action_shape = action_shape

        # network
        self.net = nn.Sequential(
            nn.Linear(obs_shape, hidden_num),
            nn.Tanh(),
            nn.Linear(hidden_num, hidden_num),
            nn.Tanh()
        )

        self.mu_head = nn.Linear(hidden_num, action_shape)
        self.sigma_head = nn.Linear(hidden_num, action_shape) + 0.01

    def forward(self, input):
        x = self.net(input)
        mu = torch.sigmoid(self.mu_head(x))
        sigma = F.softplus(self.sigma_head(x)) + 0.01

        return mu, sigma

    def get_dist(self, input):
        mu, sigma = self.forward(input)
        dist = Normal(mu, sigma)
        return dist

class critic(nn.Module):
    def __init__(self, obs_shape, hidden_num=128):
        super(critic, self).__init__()
        # parameters
        self.obs_shape = obs_shape

        # network
        self.net = nn.Sequential(
            nn.Linear(obs_shape, hidden_num),
            nn.Tanh(),
            nn.Linear(hidden_num, hidden_num),
            nn.Tanh(),
            nn.Linear(hidden_num, 1)
        )

    def forward(self, input):
        value = self.net(input)
        return value

class PPO(object):
    '''
    agent类
    '''

    def __init__(
            self,
             obs_shape,
             action_shape,
             buffer=ReplayBuffer(10000),
             max_grad_norm=40,
             gamma=0.99,
             lambd=0.95,
             K_epochs=10,
             a_optim_batch_size=64,
             c_optim_batch_size=64,
             clip_param=0.2,
             l2_reg=1e-3,
             actor_lr=0.0001,
             critic_lr=0.0001,
             entropy_coef = 0.001,
             entropy_coef_decay=0.9998,
             actor=None,
             critic=None
             ):
        super(PPO, self).__init__()
        self.obs_shape = obs_shape
        self.action_shape = action_shape
        self.buffer = buffer

        self.training_step = 0
        self.max_grad_norm = max_grad_norm

        self.gamma = gamma
        self.lambd = lambd
        self.K_epochs = K_epochs
        self.a_optim_batch_size = a_optim_batch_size
        self.c_optim_batch_size = c_optim_batch_size

        self.clip_param = clip_param
        self.l2_reg = l2_reg

        self.actor_lr = actor_lr
        self.critic_lr = critic_lr
        self.actor = actor.to(device)
        self.critic = critic.to(device)

        self.entropy_coef = entropy_coef
        self.entropy_coef_decay = entropy_coef_decay

        self.data = []

        # optimizer
        self.actor_optim = torch.optim.Adam(self.actor.parameters(), self.actor_lr)
        self.critic_optim = torch.optim.Adam(self.critic.parameters(), self.critic_lr)

    def take_action(self, obs):
        '''
        产生一个动作，并返回动作和其概率值

        :param obs: agent的observation
        :return: action和action的概率
        '''

        # 将numpy格式转为tensor格式，并且增加batch维度
        with torch.no_grad():
            obs = torch.from_numpy(obs).float().unsqueeze(0).to(device)
            dist = self.actor.get_dist(obs)
            action = dist.sample()
            action_log_prob = dist.log_prob(action)
            # 最好将均值mu弄到动作空间的均值
            action = action.clamp(-2, 2)  # TODO(将此处改为参数),将action限制在[-1,1]，超过的按2算，不够的按-2算
            return action.cpu().flatten().numpy(), action_log_prob.sum().cpu().item()  # 返回np格式数据，而不是tensor格式,注意要把log概率之和加起来（log内相乘）

    def get_value(self, obs):
        '''
        获得critic值

        :param obs:
        :return:
        '''
        with torch.no_grad():
            value = self.critic(obs)
            return value

    def save_model(self, path='../param', para=None):
        '''
        保存模型参数

        :param path:
        :return:
        '''
        torch.save(self.actor.state_dict(), path + para + '_' + str(time.time())[:10] + '_actor.pkl')
        torch.save(self.critic.state_dict(), path + para + '_' + str(time.time())[:10] + '_critic.pkl')

    # def update(self, batch_size =256, type = 'clip'):
    #
    #     obs, action, reward, log_old_prob, next_obs, done = self.buffer.sample(batch_size=batch_size)
    #     for i in range(self.K_epoch):
    #         next_v = self.get_value(next_obs)
    #         current_v = self.get_value(obs)
    #         target_v = reward + self.gamma * next_v * (1 - done.float())
    #         delta = target_v - current_v  # TD error
    #         delta = delta.cpu().flatten().numpy()
    #         done = done.cpu().flatten().numpy()
    #
    #         '''计算GAE'''
    #         adv = [0]
    #         for dlt, done in zip(delta[::-1], done[::-1]):  # batch内反向计算
    #             advantage = dlt + self.gamma * self.lambd * adv[-1] * (1 - done)
    #             adv.append(advantage)
    #         adv.reverse()  # 再反向回来，就是batch内每个数据对应的advantage
    #         adv = copy.deepcopy(adv[0:-1])  # 把初始化的adv = [0]去掉
    #         adv = torch.tensor(adv).unsqueeze(1).float().to(device)  # 增加batch维度
    #         td_target = adv + current_v
    #         adv = (adv - adv.mean()) / ((adv.std() + 1e-4))  # 归一化后，训练更稳定
    #         '''结束'''
    #
    #         if type == 'clip':
    #             # 计算概率动作之比
    #             mu, sigma = self.actor(obs)
    #             n = Normal(mu, sigma)
    #             action_log_prob = n.log_prob(action).sum(dim=-1, keepdim=True)  # 概率相乘（相当于log相加）
    #             ratio = torch.exp(action_log_prob - log_old_prob)  # TODO 问题：这里两个概率值一直一样，比率为1
    #
    #             # 计算两个loss，之后选取最小值
    #             L1 = ratio * adv
    #             L2 = torch.clamp(ratio, 1 - self.clip_param, 1 + self.clip_param) * adv
    #             action_loss = -torch.min(L1, L2).mean()
    #             self.actor_optim.zero_grad()
    #             action_loss.backward()
    #             nn.utils.clip_grad_norm_(self.actor.parameters(), self.max_grad_norm)
    #             self.actor_optim.step()
    #             print('pi_loss : {}'.format(action_loss))
    #
    #         else:
    #             pass  # TODO PPO1，利用KL divergence计算
    #
    #         # 更新critic
    #         v_loss = self.critic_loss(self.critic(obs), td_target) + self.l2_reg  # TODO 这里要重新使用self.critic(obs)，这里需要梯度
    #         self.critic_optim.zero_grad()
    #         v_loss.backward()
    #         nn.utils.clip_grad_norm_(self.critic.parameters(), self.max_grad_norm)
    #         self.critic_optim.step()
    #         print('v_loss : {}'.format(v_loss))
    #
    #     return

    # def update(self, type='clip'):
    #     '''
    #     更新actor和critic
    #
    #     batch_size: batch_size为horizon T,一个T中可能包含多个episode，也可能多个T才时一个episode
    #     :return:
    #     '''
    #
    #     self.training_step += 1
    #
    #     # batch数据
    #     obs, action, reward, next_obs, log_old_prob, done = self.make_batch()
    #
    #     # TODO 计算优势估计,这里不需要梯度
    #     with torch.no_grad():
    #         current_v = self.critic(obs)
    #         next_v = self.critic(next_obs)
    #         target_v = reward + self.gamma * next_v * (1 - done.float())
    #
    #         delta = target_v - current_v  # TD error
    #         delta = delta.cpu().flatten().numpy()
    #         done = done.cpu().flatten().numpy()
    #
    #         '''计算GAE'''
    #         adv = [0]
    #         for dlt, d in zip(delta[::-1], done[::-1]):  # batch内反向计算
    #             advantage = dlt + self.gamma * self.lambd * adv[-1] * (1 - d)
    #             adv.append(advantage)
    #         adv.reverse()  # 再反向回来，就是batch内每个数据对应的advantage
    #         adv = copy.deepcopy(adv[0:-1])  # 把初始化的adv = [0]去掉
    #         adv = torch.tensor(adv).unsqueeze(1).float().to(device)  # 增加batch维度
    #         td_target = adv + current_v
    #         adv = (adv - adv.mean()) / ((adv.std() + 1e-4))  # 归一化后，训练更稳定
    #         '''结束'''
    #
    #     a_optim_iter_num = int(math.ceil(obs.shape[0] / self.a_optim_batch_size))
    #     c_optim_iter_num = int(math.ceil(obs.shape[0] / self.c_optim_batch_size))
    #     # 更新actor
    #     for j in range(self.K_epoch):
    #
    #         # 用batch更新，先Shuffle数据，取得一个batch数据
    #         perm = np.arange(obs.shape[0])  # 整个数据的长度
    #         np.random.shuffle(perm)
    #         perm = torch.LongTensor(perm).to(device)
    #         obs, action, td_target, adv, log_old_prob = \
    #             obs[perm].clone(), action[perm].clone(), td_target[perm].clone(), adv[perm].clone(), log_old_prob[
    #                 perm].clone()
    #
    #         for i in range(a_optim_iter_num):
    #             index = slice(i * self.a_optim_batch_size,
    #                           min((i + 1) * self.a_optim_batch_size, obs.shape[0]))  # minibatch的切片
    #             old = log_old_prob[index]
    #
    #             # if type == 'clip':
    #             # 计算概率动作之比
    #             dist = self.actor.get_dist(obs[index])
    #             action_log_prob = dist.log_prob(action[index]).sum(dim=-1, keepdim=True)  # 概率相乘（相当于log相加）
    #             dist_entropy = dist.entropy().sum(1, keepdim=True)
    #             ratio = torch.exp(action_log_prob - old)
    #
    #             # 计算两个loss，之后选取最小值
    #             L1 = ratio * adv[index]
    #             L2 = torch.clamp(ratio, 1 - self.clip_param, 1 + self.clip_param) * adv[index]
    #             action_loss = -torch.min(L1, L2) - self.entropy_coef * dist_entropy  # TODO action_loss修改
    #             self.actor_optim.zero_grad()
    #             action_loss.mean().backward()
    #             ####################################
    #             # for name, parms in self.actor.named_parameters():
    #             #     print('-->name:', name, '-->grad_requirs:', parms.requires_grad, \
    #             #           ' -->grad_value:', parms.grad.mean(),'-->parms', parms)
    #
    #             ####################################
    #             nn.utils.clip_grad_norm_(self.actor.parameters(), self.max_grad_norm)
    #             self.actor_optim.step()
    #
    #         # 更新critic
    #         for i in range(c_optim_iter_num):
    #             index = slice(i * self.c_optim_batch_size, min((i + 1) * self.c_optim_batch_size, obs.shape[0]))
    #
    #             v_loss = F.mse_loss(self.critic(obs[index]), td_target[index])
    #             for name, param in self.critic.named_parameters():
    #                 if 'weight' in name:
    #                     v_loss += param.pow(2).sum() * self.l2_reg
    #             self.critic_optim.zero_grad()
    #             v_loss.backward()
    #
    #             #####################################
    #             # for name, parms in self.critic.named_parameters():
    #             #     print('-->name:', name, '-->grad_requirs:', parms.requires_grad, \
    #             #           ' -->grad_value:', parms.grad)
    #             #####################################
    #
    #             nn.utils.clip_grad_norm_(self.critic.parameters(), self.max_grad_norm)
    #             self.critic_optim.step()
    #
    #     return self.training_step, v_loss, action_loss

    def update(self):
        self.entropy_coef *= self.entropy_coef_decay
        s, a, r, s_prime, logprob_a, done_mask = self.make_batch()

        ''' Use TD+GAE+LongTrajectory to compute Advantage and TD target'''
        with torch.no_grad():
            vs = self.critic(s)
            vs_ = self.critic(s_prime)

            '''dw for TD_target and Adv'''
            deltas = r + self.gamma * vs_ * (1 - done_mask) - vs

            deltas = deltas.cpu().flatten().numpy()
            adv = [0]

            '''done for GAE'''
            for dlt, mask in zip(deltas[::-1], done_mask.cpu().flatten().numpy()[::-1]):  # 这里reverse一下
                advantage = dlt + self.gamma * self.lambd * adv[-1] * (1 - mask)
                adv.append(advantage)
            adv.reverse()
            adv = copy.deepcopy(adv[0:-1])
            adv = torch.tensor(adv).unsqueeze(1).float().to(device)
            td_target = adv + vs
            adv = (adv - adv.mean()) / ((adv.std() + 1e-4))  # sometimes helps

        """Slice long trajectopy into short trajectory and perform mini-batch PPO update"""
        a_optim_iter_num = int(math.ceil(s.shape[0] / self.a_optim_batch_size))
        c_optim_iter_num = int(math.ceil(s.shape[0] / self.c_optim_batch_size))
        for i in range(self.K_epochs):

            # Shuffle the trajectory, Good for training
            perm = np.arange(s.shape[0])
            np.random.shuffle(perm)
            perm = torch.LongTensor(perm).to(device)
            s, a, td_target, adv, logprob_a = \
                s[perm].clone(), a[perm].clone(), td_target[perm].clone(), adv[perm].clone(), logprob_a[perm].clone()

            '''update the actor，用minibatch更新，i是minibatch得index'''
            for i in range(a_optim_iter_num):
                index = slice(i * self.a_optim_batch_size,
                              min((i + 1) * self.a_optim_batch_size, s.shape[0]))  # minibatch的切片
                distribution = self.actor.get_dist(s[index])
                dist_entropy = distribution.entropy().sum(1, keepdim=True)
                logprob_a_now = distribution.log_prob(a[index])
                old = logprob_a[index]
                ratio = torch.exp(
                    logprob_a_now.sum(1, keepdim=True) - old)  # a/b == exp(log(a)-log(b))

                surr1 = ratio * adv[index]
                surr2 = torch.clamp(ratio, 1 - self.clip_param, 1 + self.clip_param) * adv[index]
                a_loss = -torch.min(surr1, surr2) - self.entropy_coef * dist_entropy

                self.actor_optim.zero_grad()
                a_loss.mean().backward()

                ############################
                # for name, parms in self.actor.named_parameters():
                # 	print('-->name:', name, '-->grad_requirs:', parms.requires_grad, \
                # 		  ' -->grad_value:', parms.grad.mean(), '-->parms', parms)
                #####################
                torch.nn.utils.clip_grad_norm_(self.actor.parameters(), 40)
                self.actor_optim.step()

            '''update the critic'''
            for i in range(c_optim_iter_num):
                index = slice(i * self.c_optim_batch_size, min((i + 1) * self.c_optim_batch_size, s.shape[0]))
                c_loss = (self.critic(s[index]) - td_target[index]).pow(2).mean()
                for name, param in self.critic.named_parameters():
                    if 'weight' in name:
                        c_loss += param.pow(2).sum() * self.l2_reg

                self.critic_optim.zero_grad()
                c_loss.backward()
                self.critic_optim.step()

    def evaluate(self, obs):
        '''
        测试模型，训练不调用，只在测试时使用

        :return:一个动作
        '''
        with torch.no_grad():
            obs = torch.from_numpy(obs).float().unsqueeze(0).to(device)
            dist = self.actor.get_dist(obs)
            action = dist.sample()
            action = action.clamp(-2, 2)  # TODO(将此处改为参数),将action限制在[-1,1]，超过的按1算，不够的按-1算
            return action.cpu().flatten().numpy()

    def make_batch(self):
        '''
        将数据弄成batch

        :return:
        '''
        s_lst, a_lst, r_lst, s_prime_lst, logprob_a_lst, done_lst = [], [], [], [], [], []
        for transition in self.data:
            s, a, r, s_prime, logprob_a, done = transition

            s_lst.append(s)
            a_lst.append(a)
            logprob_a_lst.append([logprob_a])
            r_lst.append([r])
            s_prime_lst.append(s_prime)
            done_lst.append([done])

        self.data = []  # Clean history trajectory

        '''list to tensor'''
        with torch.no_grad():
            s, a, r, s_prime, logprob_a, done = \
                torch.tensor(s_lst, dtype=torch.float).to(device), \
                torch.tensor(a_lst, dtype=torch.float).to(device), \
                torch.tensor(r_lst, dtype=torch.float).to(device), \
                torch.tensor(s_prime_lst, dtype=torch.float).to(device), \
                torch.tensor(logprob_a_lst, dtype=torch.float).to(device), \
                torch.tensor(done_lst, dtype=torch.float).to(device)

        return s, a, r, s_prime, logprob_a, done

    def put_data(self, transition):
        self.data.append(transition)


"""
Parts of the code have been copied from https://github.com/pytorch/examples/blob/master/imagenet/main.py
"""
import torch.nn as nn
import torch.utils.model_zoo as model_zoo
import sys
from torch.autograd import Variable
from torchvision import datasets, transforms, models
from torch.utils.data.sampler import SubsetRandomSampler, SequentialSampler
import torch
import pickle
sys.path.append("..")
import util
from collections import defaultdict
from scipy.stats import ortho_group #For Generating Orthogonal Fingerprint (dys)
from torch.autograd import Variable
from torchvision import datasets, transforms, models
import argparse
import dill as pickle
import numpy as np
import os
import sys
import torch
import torch.optim as optim
sys.path.append("..")
import util
import custom_datasets
import fp_train
from fingerprint import Fingerprints
from torch.utils.data.sampler import SubsetRandomSampler

model_names = sorted(name for name in models.__dict__
    if name.islower() and not name.startswith("__")
and callable(models.__dict__[name]))
# Training settings
parser = argparse.ArgumentParser(description='MiniImageNet BenchMarkSetUp')
parser.add_argument('--data', metavar='DIR',
                    default='miniimagenet/data/miniImagenet/test_imnet'
                    ,help='path to dataset')
parser.add_argument('--test_indices_file', metavar='DIR',
                    default='/home/user/Documents/My_Software/miniim/adversarial/test_indices.p'
                    ,help='path to indices of (unseen) samples')
parser.add_argument('--ckpt', default="/home/user/Documents/My_Software/miniim/" \
                        + "user/logs/miniimagenet/eps_0.03/numdx_20/ckpt/state_dict-ep_29.pth")
parser.add_argument('--batch-size', type=int, default=64, metavar='N',
                    help='input batch size for training (default: 64)')
parser.add_argument('--test-batch-size', type=int, default=1000, metavar='N',
                    help='input batch size for testing (default: 1000)')
parser.add_argument('--no-cuda', action='store_true', default=False,
                    help='disables CUDA training')
parser.add_argument('--seed', type=int, default=1, metavar='S',
                    help='random seed (default: 1)')

parser.add_argument('--eps', type=float, default=0.1)
parser.add_argument('--num-dx', type=int, default=5)
parser.add_argument('--num-class', type=int, default=10)

parser.add_argument('--name', default="dataset-name")
parser.add_argument("--data-dir")
parser.add_argument("--log-dir")
args = parser.parse_args()

args.cuda = not args.no_cuda and torch.cuda.is_available()

torch.manual_seed(args.seed)
if args.cuda:
    torch.cuda.manual_seed(args.seed)

train_transform = transform = transforms.Compose([#transforms.RandomResizedCrop(224),
                                        transforms.RandomHorizontalFlip(),
                                        transforms.ToTensor(),
						                transforms.Normalize((0.5, 0.5, 0.5),
                                		(1.0,1.0,1.0))])

transform = transforms.Compose([ transforms.RandomResizedCrop(32),
                                 transforms.ToTensor(),
                                transforms.Normalize((0.5, 0.5, 0.5),
                                (1.0,1.0,1.0))])

kwargs = {'num_workers': 1, 'pin_memory': True} if args.cuda else {}

from modelz import alexnet
#from small_model import Very_Small_Net as Net
ckpt = args.ckpt
test_image_dir = args.data
test_indices_file = args.test_indices_file
model = alexnet.alexnet()
print("Loading ckpt", ckpt)
model.load_state_dict(torch.load(ckpt, map_location=lambda storage, loc: storage))
model.train(False)
model.eval()
if args.cuda:
    model.cuda()

args.distributed = False
if not args.distributed and not args.no_cuda:
    if args.arch.startswith('alexnet') or args.arch.startswith('vgg'):
        model.features = torch.nn.DataParallel(model.features)
        model.cuda()
    else:
        model.cuda()
        #model = torch.nn.DataParallel(model).cuda()
elif not args.no_cuda:
    model.cuda()
    model = torch.nn.parallel.DistributedDataParallel(model)

transform = transforms.Compose([ transforms.Resize(256),
                                 transforms.CenterCrop(224),
                                 transforms.ToTensor(),
                                transforms.Normalize((0.5, 0.5, 0.5),
                                (1.0,1.0,1.0))])

test_dataset = datasets.ImageFolder(
        test_image_dir,
        transform=transform,)

test_loader = pickle.load(open(test_indices_file,"rb"))
from third_party.attack_iterative import *
import numpy as np
dataset = "miniimagenet20"
#attack = "iterativefgsm"
attack = "fgsm"

adv_gen = AttackIterative(targeted = False,
        max_epsilon=16.0,
        norm=float('inf'),
        step_alpha=0.0,
        num_steps=1,
        cuda=False,
        debug=False)
yy = np.zeros((1,20))
yy[0,5] = 1
pytorch_model = model
advs = None
total_correct = 0
for batch_idx, (x, y) in enumerate(test_loader):
    #x = Variable(x)
    print("batch", batch_idx)
    output = pytorch_model(Variable(x))
    pred = output.data.max(1, keepdim=True)[1] # get the index of the max log-probability
    correct = pred.eq(Variable(y).data.view_as(pred)).cpu().sum()
    total_correct = total_correct + correct

    print(total_correct)

    if(advs is None):
        x_test = x.numlspy()
        y_test = y.numpy()
        advs = adv_gen.run(pytorch_model, x, y)
        output = pytorch_model(Variable(torch.from_numpy(advs)))
        pred = output.data.max(1, keepdim=True)[1] # get the index of the max log-probability
        correct = pred.eq(Variable(y).data.view_as(pred)).cpu().sum()
        advs = torch.from_numpy(advs)
        total_correct = total_correct + correct
    else:
        new_advs = adv_gen.run(pytorch_model, x, y)
        advs = np.concatenate((advs, new_advs))
        y_test = np.concatenate((y_test,y.numpy()))
        x_test = np.concatenate((x_test,x.numpy()))

    f = open('Adv_%s_%s_%s.p' % (dataset, attack,batch_idx),'w')
    pickle.dump({"adv_input":advs,"adv_labels":y_test},f)
    f.close()

    f = open('Random_Test_%s_%s.p' % (dataset,batch_idx),'w')
    pickle.dump({"adv_input":x_test,"adv_labels":y_test},f)
    f.close()


import sys

from os.path import basename
from shutil import which
from subprocess import call, PIPE

from pako.config_loader import try_write_empty_config_stub
from pako.package_format import PackageFormat
from pako.package_manager_data import load_package_manager_data

class PakoManager:
    def __init__(self):
        self.has_sudo = False
        self.all_data = load_package_manager_data()
        self.name = self._find_package_manager(list(self.all_data))

        if not self.name:
            raise RuntimeError('Package manager not found!')

        self.exe = which(self.name)
        self.config = self.all_data[self.name]
        if self.config['sudo'] and not sys.stdout.isatty() and not self._check_for_sudo():
            raise RuntimeError('Sudo required (and script not launched interactively)')

    def _check_for_sudo(self):
        try:
            return call(['sudo', '-n', 'true'], stderr=PIPE) == 0
        except:
            return False

    @staticmethod
    def _find_package_manager(exes):
        """Determine which package manager exists on a system."""
        for exe in exes:
            if which(exe):
                return exe
        return None

    def call(self, args: list):
        """Execute command for the available package manager.

        Arguments:
            args (List): list of command line arguments use
        """
        sudo_args = []
        if self.config['sudo']:
            sudo_args = ['sudo']
            if not self.has_sudo:
                if not self._check_for_sudo():
                    print('Requesting sudo to run command: {}...'.format(
                        ' '.join([basename(self.exe)] + args)
                    ))
        status = call(sudo_args + [self.exe] + args)
        self.has_sudo = self.config['sudo']
        if self.has_sudo:
            try_write_empty_config_stub()
        return status

    def update(self):
        """Update list of available packages."""
        return self.call(self.config['update'].split()) == 0

    def install_one(self, package: str, fmt: str = None, flags: list = []) -> bool:
        """Install a single system package.

        Arguments:
            package (Str): Name of package to install
            fmt (Str): Format of package name to use
            flags (List[Str]): A list of command flags to use if available
        Returns:
            Bool: True if package was successfully installed
        """
        if not fmt:
            package, fmt = PackageFormat.parse(package)
        if fmt not in PackageFormat.all:
            raise ValueError('Invalid package format: {}. Should be one of: {}'.format(
                fmt, PackageFormat.all
            ))

        possible_names = []
        for format_name, formats in self.config['formats'].items():
            if not fmt or format_name == fmt:
                for f in formats:
                    if f not in possible_names:
                        possible_names.append(f)

        install_cmd = self.config['install'].split()
        if 'no-confirm' in flags:
            install_cmd.append(self.config.get('flags').get('no-confirm'))
        for name in possible_names:
            if self.call(install_cmd + [name.format(package)]) == 0:
                return True
        return False

    def install(self, packages: list, overrides: dict = None, flags: list = []) -> bool:
        """Install system packages.

        Arguments:
            packages (List[Str]): A list of package names to install
            overrides (Dict): A dictionary of package name formats to use
            flags (List[Str]): A list of command flags to use if available
        Returns:
            Bool: True if all packages were successfully installed
        """
        if isinstance(packages, str):  # Easy mistake
            raise TypeError('Packages parameter must be a list')
        overrides = overrides or {}
        if self.name in overrides:
            packages = overrides[self.name]
            install_cmd = self.config['install'].split()
            if 'no-confirm' in flags:
                install_cmd.append(self.config.get('flags').get('no-confirm'))
            return self.call(install_cmd + packages) == 0
        for package in packages:
            if not self.install_one(package, flags=flags):
                return False
        return True


from datetime import datetime
from typing import List, Union

from pydantic import validator

from tracardi.domain.profile import Profile
from tracardi.service.notation.dict_traverser import DictTraverser
from tracardi.service.plugin.domain.config import PluginConfig

from tracardi.service.plugin.domain.register import Plugin, Spec, MetaData, Documentation, PortDoc, Form, FormGroup, \
    FormField, FormComponent
from tracardi.service.plugin.runner import ActionRunner
from tracardi.service.plugin.domain.result import Result

class Configuration(PluginConfig):
    segment: Union[str, List[str]]

    @validator("segment")
    def is_not_empty(cls, value):
        if not value:
            raise ValueError("Segment cannot be empty")
        return value

def validate(config: dict):
    return Configuration(**config)

class AddSegmentAction(ActionRunner):
    config: Configuration

    async def set_up(self, init):
        self.config = validate(init)

    async def run(self, payload: dict, in_edge=None) -> Result:
        if isinstance(self.profile, Profile):
            dot = self._get_dot_accessor(payload)
            profile = Profile(**dot.profile)
            if self.config.segment not in self.profile.segments:
                profile.metadata.time.segmentation = datetime.utcnow()

                try:
                    dot = self._get_dot_accessor(payload)
                    if isinstance(self.config.segment, list):
                        converter = DictTraverser(dot, include_none=False)
                        segments = converter.reshape(self.config.segment)
                        profile.segments += segments

                    elif isinstance(self.config.segment, str):
                        profile.segments.append(dot[self.config.segment])

                    else:
                        return Result(value={"message": "Not acceptable segmentation type. "
                                                        "Allowed type: string or list of strings"}, port="error")
                except KeyError as e:
                    return Result(value={"message": str(e)}, port="error")

            self.profile.replace(profile)
        else:
            if self.event.metadata.profile_less is True:
                self.console.warning("Can not segment profile when processing profile less events.")
            else:
                self.console.error("Can not segment profile. Profile is empty.")

        return Result(value=payload, port="payload")

def register() -> Plugin:
    return Plugin(
        start=False,
        spec=Spec(
            module=__name__,
            className='AddSegmentAction',
            inputs=["payload"],
            outputs=["payload", "error"],
            version="0.8.1",
            author="Risto Kowaczewski",
            manual="segmentation/add_segment_action",
            init={
                "segment": ""
            },
            form=Form(groups=[
                FormGroup(
                    name="Segment",
                    fields=[
                        FormField(
                            id="segment",
                            name="Segment name",
                            description="Please type segment name.",
                            component=FormComponent(type="text", props={"label": "Segment name"})
                        )
                    ]
                )]
            ),
        ),
        metadata=MetaData(
            name='Add segment',
            desc='Adds segment to profile.',
            icon='segment',
            group=["Segmentation"],
            purpose=['collection', 'segmentation'],
            documentation=Documentation(
                inputs={
                    "payload": PortDoc(desc="This port takes any payload.")
                },
                outputs={
                    "payload": PortDoc(desc="This port returns input payload.")
                }
            )
        )
    )


# Author: Aadiba Haque
# Assignment / Part: HW3 - Q2
# Date due: 2020-02-28
# I pledge that I have completed this assignment without
# collaborating with anyone else, in conformance with the
# NYU School of Engineering Policies and Procedures on
# Academic Misconduct.

item1 = float(input("Enter the price of the first item:"))
item2 = float(input("Enter the price of the second item:"))
card = input("Does the customer have a club card? (Y/N): ")
tax_rate = float(input("Enter the tax rate, e.g. 5.5 for 5.5% tax: "))

total = item1 + item2

print("Base price =", total)

if item1 > item2:
    item2 /= 2

if item2 > item1:
    item1 /= 2

total = item1 + item2

if card == "Y":
    total *= 0.90

print("Price after discounts =", total)

total *= (1+(tax_rate / 100))

print("Total Price =", round(total, 2))