import json

from app_blog.models import Article
from myweb.utils import JSONEncoder, validateEmail
from app_user.models import UserProfile

from django import forms
from django.apps import apps
from django.contrib.auth import get_user_model
from django.core import serializers
from django.core.cache import cache
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.db import models
from django.forms.models import modelform_factory
from django.forms.utils import ErrorDict
from django.http import QueryDict, request
from django.http.response import (Http404, HttpResponse, HttpResponseForbidden,
                                  JsonResponse)
from django.shortcuts import get_object_or_404, redirect
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from django.views.generic.edit import FormView
from django.views.generic.list import BaseListView, View
from loguru import logger

from .forms import CommentForm
from .models import Comments, MpComments

# model_class = apps.get_model('app_blog', 'article')

# 增加评论 old
class CommentPostView(FormView):
    form_class = CommentForm
    template_name = 'tp/文章详情.html' # get 请求返回

    def get(self, request, *args, **kwargs):
        article_slug = self.kwargs['article_slug']

        article = get_object_or_404(Article, slug=article_slug)
        url = article.get_absolute_url()
        print(20*'*')
        return redirect(url + "#comments")

    def form_invalid(self, form):
        '''如果表单无效，则呈现无效表单'''
        article_slug = self.kwargs['article_slug']

        article = get_object_or_404(Article, slug=article_slug)
        user = self.request.user
        if user.is_authenticated:
            form.fields.update({
                'email': forms.CharField(widget=forms.HiddenInput()),
                'name': forms.CharField(widget=forms.HiddenInput()),
            })
            form.fields["email"].initial = user.email
            form.fields["name"].initial = user.username

        return self.render_to_response({
            'comment_form': form,
            'article': article,
            'comments': article.comment_list()
        })

    def form_valid(self, form):
        """表单验证成功后的逻辑"""
        article_slug = self.kwargs['article_slug']

        article = get_object_or_404(Article, slug=article_slug)
        has_commented = self.request.session.get('has_commented', None)
        if (not has_commented) or (has_commented!=article_slug):

            user = self.request.user
            if not user.is_authenticated:
                email = form.cleaned_data['email']
                username = form.cleaned_data['name']
                user = get_user_model().objects.get_or_create(username=username, email=email)[0]
                # auth.login(self.request, user)
            comment = form.save(False)
            comment.content_object = article
            comment.author = user
            comment.save(True)
            # self.request.session['has_commented'] = article.id
            # self.request.session.set_expiry(300)
            return redirect("%s#div-comment-%d" %(article.get_absolute_url(), comment.pk))

        else:
            message = '你已经评论过了, 五分钟后可再次评论.'
            print(message)
            url = article.get_absolute_url()
            return redirect(url + "#comments_你已经评论过了, 五分钟后可再次评论")

class CommentsView(BaseListView):
    model = Comments
    paginate_by = 5

    def get(self, request, *args, **kwargs):
        self.object_list = self.get_queryset()
        allow_empty = self.get_allow_empty()

        if not allow_empty:
            # When pagination is enabled and object_list is a queryset,
            # it's better to do a cheap query than to load the unpaginated
            # queryset in memory.
            if self.get_paginate_by(self.object_list) is not None and hasattr(self.object_list, 'exists'):
                is_empty = not self.object_list.exists()
            else:
                is_empty = not self.object_list
            if is_empty:
                raise Http404(_("Empty list and '%(class_name)s.allow_empty' is False.") % {
                    'class_name': self.__class__.__name__,
                })

        try:
            context = self.get_context_data()
        except Exception as e:
            print(e)
            if request.is_ajax():
                return HttpResponse({'status':404, 'data': None})
            else:
                raise e

        if request.is_ajax():
            paginator = context['paginator']
            page_obj = context['page_obj']
            is_paginated = context['is_paginated']
            object_list = context['object_list']

            serialize_items = serializers.serialize("json", object_list,
                                                    fields=('body', 'mood', 'img', 'created'),
                                                    ensure_ascii=False)

            data = {
                'status': 200,
                'current_page': page_obj.number,    # 当前页码
                'has_next': page_obj.has_next(),    # 是否有下页
                'page_total': paginator.num_pages,  # 总页数
                'items_count': paginator.count,     # 元素总数
                'data': serialize_items,
            }

            return JsonResponse(data, encoder=JSONEncoder)

        return self.render_to_response(context)

class CommentsView(View):
    paginate_by = 5

    # 查询评论
    def get(self, request, *args, **kwargs):
        '''
        /comment/?content_type=article&object_id=1
        content_type: 关联模型名称 Model name
        object_id: 关联模型实例 ID
        '''

        try:
            content_type_model = request.GET['content_type']
            object_id = int(request.GET['object_id'])
            page = int(request.GET.get('page', 0))

            if page:
                start = self.paginate_by * (page-1)
                end = self.paginate_by * page
            else:
                start = end = None
        except Exception as e:
            return HttpResponse(json.dumps({'status':403,'err_msg':'请求参数错误'}, ensure_ascii=False), content_type="application/json; charset=UTF-8")

        comments = self.get_comments(start, end, serialize=True, content_type_model=content_type_model, object_id=object_id)
        if len(comments) < 1:
            return HttpResponse(json.dumps({'status':404,'err_msg':'请求数据为空'}, ensure_ascii=False), content_type="application/json; charset=UTF-8")

        data = {'status': 200, 'current_page':page, 'comments': comments}
        json_data = json.dumps(data, cls=JSONEncoder, separators=(',',':'))
        return HttpResponse(json_data, content_type="application/json; charset=UTF-8")

    def get_comments(self, start, end, serialize, content_type_model, object_id):
        cache_key = f'comments_s_{start}_e_{end}_ser_{serialize}_ctm_{content_type_model}_oid_{object_id}'
        data = cache.get(cache_key)
        if data != None:
            logger.info(f'获取文章评论缓存:{cache_key}')
            return data
        else:
            data = Comments.objects.visible_comments(start, end, serialize=True, content_type__model=content_type_model, object_id=object_id)
            cache.set(cache_key, data, 60 * 100)
            logger.info(f'设置文章评论缓存:{cache_key}')
            return data

    # 增加评论
    def post(self, request, *args, **kwargs):
        if request.is_ajax():

            article_slug = request.POST.get('object_slug', '')
            comment_body = request.POST.get('comment_body', '')

            replyto = request.POST.get('replyto', '')

            username = request.POST.get('username', '')
            nickname = request.POST.get('nickname', '')
            email = request.POST.get('email', '')

            if article_slug and comment_body:
                try:
                    article = Article.objects.get(slug=article_slug)
                except:
                    return JsonResponse({'status': 404, 'msg':'数据获取失败'})

                if request.user.is_authenticated and request.user.username==username:
                    wanderer = False
                    author = request.user.profile
                elif username and not (nickname and email):
                    return JsonResponse({'status': 402, 'msg':'页面过期请刷新页面'})
                else:
                    if not 0 < len(str(nickname)) < 20:
                        return JsonResponse({'status': 401, 'msg':'请求数据非法(nickname 控制在20字以内)'})
                    if not 0 < len(str(email)) < 50:
                        return JsonResponse({'status': 401, 'msg':'请求数据非法(email 控制在50字以内)'})
                    if not validateEmail(email):
                        return JsonResponse({'status': 401, 'msg':'请求数据非法(Email 格式错误)'})
                    if UserProfile.objects.filter(w_email=email).exists():
                        return JsonResponse({'status': 401, 'msg':f'{email} 已存在'})
                    try:
                        author = UserProfile.objects.create(w_name=nickname, w_email=email)
                        wanderer = True
                    except Exception as e:
                        return JsonResponse({'status': 401, 'msg':e})
                if replyto:
                    try:
                        parent_comment = Comments.objects.get(uuid=replyto)
                    except:
                        return JsonResponse({'status': 401, 'msg':'请求数据非法(目标评论不存在)'})
                else:
                    parent_comment = None
                try:
                    ip = request.META.get('REMOTE_ADDR')  # 这里获得代理ip
                    comment = Comments.objects.create(
                        body=comment_body,
                        author=author,
                        parent_comment=parent_comment,
                        ip_address=ip,
                        content_object=article
                    )
                    # mpcomment = MpComments.objects.create(
                    #     body=comment_body,
                    #     author=author,
                    #     parent=parent_comment,
                    #     ip_address=ip,
                    #     content_object=article
                    # )

                    # print(20*'++++++++++++++')
                    # print(mpcomment)
                except Exception as e:
                    if wanderer:
                        author.delete()
                    return JsonResponse({'status': 401, 'msg':e})
                return JsonResponse({'status': 200, 'msg':'评论成功'})
            else:
                return JsonResponse({'status': 401, 'msg':'请求数据非法'})
        else:
            return HttpResponseForbidden()

    # 置顶评论
    def patch(self, request, *args, **kwargs):
        '''
        接受的数据:
            {
                uid: comment 唯一id
                要修改的字段: 对应值
            }
        '''
        data = QueryDict(request.body)
        model_fields = [f.name for f in Comments._meta.fields]

        uuid = data.get('uid')
        if not uuid:
            return JsonResponse({'status':404, 'msg': 'uuid错误'})

        try:
            uuid = Comments._meta.get_field('uuid').to_python(uuid)
            obj = Comments.objects.get(uuid=uuid)
        except (ObjectDoesNotExist, ValidationError):
            return JsonResponse({'status':404, 'msg': 'uuid查询失败, 数据不存在'})

        fields = [f for f in data.keys() if f in model_fields]

        defaults = {
            "form": forms.ModelForm,
            "fields": fields,
            # "formfield_callback": self.formfield_for_dbfield,
        }
        form_class = modelform_factory(Comments, **defaults)
        form = form_class(instance=obj, data=data, files=request.FILES)

        result = {}
        if form.is_valid():
            try:
                form.save(commit=True)
                result['msg'] = 'success'
                result['status'] = 200

            except Exception as e:
                result['msg'] = str(e)
                result['status'] = 403
        else:
            result['msg'] = ErrorDict(form.errors).as_json()
            result['status'] = 403

        return JsonResponse(result)

    # 删除评论
    def delete(self, request, *args, **kwargs):
        data = QueryDict(request.body)
        uuid = data.get('uid')
        if not uuid:
            return JsonResponse({'status':401, 'msg': 'uuid 不存在'})

        try:
            comment = Comments.objects.get(uuid=uuid)
            comment.delete()
            return JsonResponse({'status':200, 'msg': '删除成功'})
        except Exception as e:
            return JsonResponse({'status':404, 'msg': str(e)})


import copy
import random

import cv2
import numpy as np
from scipy.io import loadmat

input = "../input/"

data = loadmat(input + 'matches.mat')
r1 = data['r1']
r2 = data['r2']
c1 = data['c1']
c2 = data['c2']
matches = data['matches']

x1 = c1[matches[:, 0] - 1]
y1 = r1[matches[:, 0] - 1]
x2 = c2[matches[:, 1] - 1]
y2 = r2[matches[:, 1] - 1]

def approxFunda(p1, p2):
    p1 = np.array(p1)
    p2 = np.array(p2)

    mn1 = np.sum(p1, axis=0) / p1.shape[0]
    mn2 = np.sum(p2, axis=0) / p2.shape[0]
    mn1 = np.reshape(mn1, (1, mn1.shape[0]))
    mn2 = np.reshape(mn2, (1, mn2.shape[0]))
    trnsl_p1 = p1 - mn1
    trnsl_p2 = p2 - mn2

    added_sqs1 = np.sum(trnsl_p1 ** 2, axis=1)
    added_sqs1 = np.reshape(added_sqs1, (trnsl_p1.shape[0], 1))

    mn_dist1 = np.sum(added_sqs1 ** (1 / 2), axis=0) / trnsl_p1.shape[0]
    factor1 = 2 ** (1 / 2) / mn_dist1[0]

    added_sqs2 = np.sum(trnsl_p2 ** 2, axis=1)
    added_sqs2 = np.reshape(added_sqs2, (trnsl_p2.shape[0], 1))

    mn_dist2 = np.sum(added_sqs2 ** (1 / 2), axis=0) / trnsl_p2.shape[0]
    factor2 = 2 ** (1 / 2) / mn_dist2[0]

    nmlz1 = factor1 * trnsl_p1
    nmlz2 = factor2 * trnsl_p2

    trnslmtrx1 = np.array([[1, 0, -mn1[0][0]], [0, 1, -mn1[0][1]], [0, 0, 1]])
    scmtrx1 = np.array([[factor1, 0, 0], [0, factor1, 0], [0, 0, 1]])

    trnslmtrx2 = np.array([[1, 0, -mn2[0][0]], [0, 1, -mn2[0][1]], [0, 0, 1]])
    scmtrx2 = np.array([[factor2, 0, 0], [0, factor2, 0], [0, 0, 1]])

    T1 = np.dot(scmtrx1, trnslmtrx1)
    T2 = np.dot(scmtrx2, trnslmtrx2)

    A = np.zeros((p1.shape[0], 9))
    for i in range(p1.shape[0]):
        A[i, :] = [nmlz2[i][0] * nmlz1[i][0], nmlz2[i][0] * nmlz1[i][1], nmlz2[i][0], nmlz2[i][1] * nmlz1[i][0],
                   nmlz2[i][1] * nmlz1[i][1], nmlz2[i][1], nmlz1[i][0], nmlz1[i][1], 1]

    U, S, Vt = np.linalg.svd(A)

    V = Vt.T
    V = V[:, -1]
    F = np.zeros((3, 3))
    count = 0
    for i in range(3):
        for j in range(3):
            F[i, j] = V[count]
            count += 1

    u, s, vt = np.linalg.svd(F)

    s[-1] = 0
    newS = np.zeros((3, 3))
    for i in range(3):
        newS[i, i] = s[i]

    newF = np.dot((np.dot(u, newS)), vt)

    regF = np.dot(np.dot(T2.T, newF), T1)
    regF = regF / regF[-1, -1]

    return regF

def RANSAC(feature1, feature2):
    thresh = 0.05
    prsntInliers = 0
    perfF = []
    p = 0.99
    N = 2000
    count = 0
    while count < N:
        inlier_count = 0
        random_feature1 = []
        random_feature2 = []
        RandList = np.random.randint(len(feature1), size=8)
        for k in RandList:
            random_feature1.append(feature1[k])
            random_feature2.append(feature2[k])
        F = approxFunda(random_feature1, random_feature2)

        One = np.ones((len(feature1), 1))
        X_1 = np.hstack((feature1, One))
        X_2 = np.hstack((feature2, One))
        E_1, E_2 = X_1 @ F.T, X_2 @ F
        err = np.sum(E_2 * X_1, axis=1, keepdims=True) ** 2 / np.sum(np.hstack((E_1[:, :-1], E_2[:, :-1])) ** 2, axis=1,
                                                                     keepdims=True)
        Inl = err <= thresh
        InlCnt = np.sum(Inl)
        if prsntInliers < InlCnt:
            prsntInliers = InlCnt
            coor = np.where(Inl == True)
            x1_ar = np.array(feature1)
            x2_ar = np.array(feature2)
            inlier_x1 = x1_ar[coor[0][:]]
            inlier_x2 = x2_ar[coor[0][:]]

            perfF = F

        RatioInl = InlCnt / len(feature1)
        if np.log(1 - (RatioInl ** 8)) == 0: continue
        N = np.log(1 - p) / np.log(1 - (RatioInl ** 8))
        count += 1
    return perfF, inlier_x1, inlier_x2

def randomPts(pt1_list, pt2_list, val):
    index = []
    for i in range(val):
        num = random.randint(0, len(pt1_list) - 1)
        index.append(num)
    new_pts1 = []
    new_pts2 = []
    for i in range(len(index)):
        val = index[i]
        new_pts1.append(pt1_list[val])
        new_pts2.append(pt2_list[val])

    new_pts1 = np.asarray(new_pts1)
    new_pts2 = np.asarray(new_pts2)
    return new_pts1, new_pts2

def drawlines(img1, lines, pts):
    i1 = copy.deepcopy(img1)
    r, c, _ = img1.shape
    for r, pt1 in zip(lines, pts):
        color1 = (0, 255, 0)
        color2 = (0, 0, 255)
        x0, y0 = map(int, [0, -r[2] / r[1]])
        x1, y1 = map(int, [c, -(r[2] + r[0] * c) / r[1]])
        i1 = cv2.line(i1, (x0, y0), (x1, y1), color1, 1)
        i1 = cv2.circle(i1, tuple(pt1), 2, color2, -1)
    return i1

def genPts(x1, y1):
    p1 = []
    for i in range(len(x1)):
        temp = [x1[i][0], y1[i][0]]
        p1.append(temp)

    return p1

def computeEpilines(img1, img2, x1, x2, y1, y2):
    p1 = genPts(x1, y1)
    p2 = genPts(x2, y2)
    F, pt1, pt2 = RANSAC(p1, p2)

    points1, points2 = randomPts(pt1, pt2, 7)

    ones = np.ones((points1.shape[0], 1))
    final_p1 = np.append(points1, ones, axis=1)
    final_p2 = np.append(points2, ones, axis=1)

    lines1 = np.dot(F, final_p1.T)
    lines2 = np.dot(final_p2, F)

    i1 = drawlines(img2, lines1.T, points2)
    i2 = drawlines(img1, lines2, points1)

    final_image = np.hstack((i1, i2))
    return final_image, F

img1 = cv2.imread(input + "chapel00.png")
img2 = cv2.imread(input + "chapel01.png")

final_image, F = computeEpilines(img1, img2, x1, x2, y1, y2)
# print("Fundamenal Matrix: \n", F)

out = "../output/"

cv2.imshow("Epilines", final_image)
cv2.imwrite(out + "epilines.jpg", final_image)
cv2.waitKey(0)
cv2.destroyAllWindows()


# -*- coding: utf-8 -*-
"""
Created on Mon Apr 27 23:16:23 2020

@author: lizijian
"""

import numpy as np
from constants import *
from environment import MECsystem
from decision import Agent
from utils import plot_learning
import matplotlib.pyplot as plt
import logging

logging.basicConfig(filename='testing.log', level=logging.ERROR,
                    format='%(message)s')

UEnet = Agent(alpha=0.000025, beta=0.00025, input_dims = 8, tau=0.001, \
              env=None, batch_size=64, layer1_size=500, layer2_size=300,
              n_actions=1)
env = MECsystem(apply_num, UEnet)
MECSnet = Agent(alpha=alpha, beta=beta, input_dims=input_dims, tau=0.001,
                env=env, batch_size=batch_size, layer1_size=layer1_size,
                layer2_size=layer2_size, n_actions=n_actions)

np.seed(0)

MECSnet.load_modules()

done = False
reward_history = []
task_fail_count = 0
time_history = []
obs = env.reset()
print('started')
k = 0
while not done:
    k += 1
    act = MECSnet.choose_action(obs, with_noise=False)
    new_state, reward, done, info = env.step(act)
    reward_history.append(reward)
    obs = new_state
    if k % 100 == 0:
        print(k)
        print('already done {} %'.format(env.time/time_total*100))

reward_mean = np.mean(reward_history)
reward_var = np.var(reward_history)
delay_mean = np.mean(delay_history)
delay_var = np.var(delay_history)
energy_mean = np.mean(energy_history)
energy_var = np.var(energy_history)

logging.error("module:\nreward:{}(+-{})\ndelay:{}(+-{})\nenergy{}(+-{})\n " \
              .format(reward_mean, reward_var, delay_mean, delay_var,
                      energy_mean, energy_var, task_fail_count))


import logging

import numpy as np
import pandas as pd
from emc_sim import options, simulations, prep
import emc_db
import multiprocessing as mp
import tqdm
import time
from itertools import chain
import pprint
logging.getLogger('matplotlib.font_manager').disabled = True

def simulate_single(
        simParams: options.SimulationParameters,
        simData: options.SimulationData,
        save: bool = False) -> (pd.DataFrame, options.SimulationParameters):
    # prep pulse gradient data
    # globals and sample are initiated within the SimulationParameters class
    prep.init_prep_for_visualization(simParams=simParams)

    # estimate single process
    simData, simParams = simulations.simulate_mese(
        simParams=simParams,
        simData=simData
    )

    logging.info(f'projected time for {simParams.settings.total_num_sim} curves: '
                 f'{simData.time * simParams.settings.total_num_sim / 3600 / simParams.config.mpNumCpus:.2f} h\t\t'
                 f'({simData.time * simParams.settings.total_num_sim / 60 / simParams.config.mpNumCpus:.1f} min)')

    param_list = simParams.settings.get_complete_param_list()
    emcAmplitude_resultlist = []

    logging.info("Simulate")
    for item in tqdm.tqdm(param_list, ncols=20):
        simData.set_run_params(*item)
        emcAmplitude, _ = simulations.simulate_mese(
            simParams=simParams,
            simData=simData
        )
        emcAmplitude_resultlist.append(emcAmplitude.to_dict())
    # create DB instance
    pd_df_database = pd.DataFrame(emcAmplitude_resultlist)
    database = emc_db.DB(pd_dataframe=pd_df_database, config=simParams.sequence, name=simParams.config.databaseName)

    if save:
        simParams.save_database(database=database)
    return database, simParams

def simulate_multi(
        simParams: options.SimulationParameters,
        simData: options.SimulationData,
        save=True):
    """
        This function creates and simulates the curves with multiprocessing, clocks full cpu power (check num_cpus)
        make sure simulation values are created accordingly

        :return: array of curves and corresponding simulation values
        """
    # prep pulse gradient data
    # globals and sample are initiated within the SimulationParameters class
    prep.init_prep_for_visualization(simParams=simParams)

    # estimate single process
    _, simParams = simulations.simulate_mese(
        simParams=simParams,
        simData=simData
    )

    # ---- using multiprocessing ---

    logging.info(f"Number of CPUs to use: {simParams.config.mpNumCpus}")
    logging.info(f'projected time for {simParams.settings.total_num_sim} curves: '
                 f'{2.5 * simData.time * simParams.settings.total_num_sim / 3600 / simParams.config.mpNumCpus:.2f} h\t\t'
                 f'({2.5 * simData.time * simParams.settings.total_num_sim / 60 / simParams.config.mpNumCpus:.1f} min)')

    logging.info("Simulate")
    # divide lists in as many parts as we have processes available (cpus)
    param_list = simParams.settings.get_complete_param_list()
    # use chunksize per cpu. this needs some speed testing its a blend between shipping data and computing!
    # choose oder of around 10 for now

    params_split = np.array_split(param_list, simParams.config.mpNumCpus * 10)
    mp_lists = [(simParams, simData, params_split[i]) for i in range(params_split.__len__())]

    start = time.time()

    with mp.Pool(simParams.config.mpNumCpus) as p:
        results = list(tqdm.tqdm(p.imap(wrapSimulateForMP, mp_lists), total=mp_lists.__len__(), desc="mp pes"))

    result_list_of_dict = list(chain(*results))
    # create DB instance
    pd_df_database = pd.DataFrame(result_list_of_dict)
    database = emc_db.DB(pd_dataframe=pd_df_database, config=simParams.sequence, name=simParams.config.databaseName)
    end = time.time()

    logging.info(f'Finished simulation! Total time: {((end - start) / 3600):.2} h \t\t ({((end - start) /60):.1f} min)')
    # df = pd.DataFrame(results)

    if save:
        database.save(path=simParams.config.savePath)

def wrapSimulateForMP(args) -> list:
    """
    When using multiprocessing we want to distribute lists of single run parameters to multiple processes.
    """
    simParams = args[0]
    simData = args[1]
    mp_list = args[2]
    emcAmplitude_resultlist = []
    for item in mp_list:
        simData.set_run_params(*item)
        emcAmplitude, _ = simulations.simulate_mese(
            simParams=simParams,
            simData=simData)
        emcAmplitude_resultlist.append(emcAmplitude.to_dict())
    return emcAmplitude_resultlist

def main():
    """
    Main function that runs training/prediction defined by command line arguments
    """
    parser, prog_args = options.createCommandlineParser()

    simParams = options.SimulationParameters.from_cmd_args(prog_args)
    # set logging level after possible config file read
    if simParams.config.debuggingFlag:
        level = logging.DEBUG
    else:
        level = logging.INFO

    logging.basicConfig(format='%(asctime)s %(levelname)s :: %(name)s --  %(message)s',
                        datefmt='%I:%M:%S', level=level)
    simData = options.SimulationData.from_cmd_args(prog_args)
    logging.info("starting simulation")
    logging.info("___ sequence dependent configuration of timing and pulses! ___")
    logging.info("Configuration")
    logging.info(f"spatial resolution set: {simParams.settings.lengthZ * 2 / simParams.settings.sampleNumber * 1e6:.3f}"
                 f" um")
    logging.debug(pprint.pformat(simParams.to_dict()))
    try:
        if simParams.config.multiprocessing:
            simulate_multi(simParams, simData)
        else:
            simulate_single(simParams, simData, save=True)
    except Exception as e:
        print(e)
        parser.print_usage()

def pulse_profile_sim():
    """
        Main function that runs training/prediction defined by command line arguments
        """
    parser, prog_args = options.createCommandlineParser()

    simParams = options.SimulationParameters.from_cmd_args(prog_args)
    # set logging level after possible config file read
    if simParams.config.debuggingFlag:
        level = logging.DEBUG
    else:
        level = logging.INFO

    logging.basicConfig(format='%(asctime)s %(levelname)s :: %(name)s --  %(message)s',
                        datefmt='%I:%M:%S', level=level)
    simData = options.SimulationData.from_cmd_args(prog_args)

    logging.info("starting simulation")
    logging.info(f"spatial resolution set: {simParams.settings.lengthZ * 2 / simParams.settings.sampleNumber * 1e6:.3f}"
                 f" um")
    logging.debug(pprint.pformat(simParams.to_dict()))
    try:
        simulations.simulate_pulse(simParams, simData)
    except (AttributeError, ValueError) as e:
        print(e)
        parser.print_usage()

def test():
    logging.basicConfig(level=logging.DEBUG)
    parser, prog_args = options.createCommandlineParser()
    simParams = options.SimulationParameters.from_cmd_args(prog_args)
    simData = options.SimulationData.from_cmd_args(prog_args)
    try:
        simulate_single(simParams, simData, save=True)
    except AttributeError as e:
        print(e)
        parser.print_usage()

if __name__ == '__main__':
    main()
    # pulse_profile_sim()


# -*- coding: utf-8 -*-
import requests
import logging
import random
import time
import hashlib
import string
import uuid
import json
import glob
import os
import sys

#重要参数
Salt_Sign = 'PVeGWIZACpxXZ1ibMVJPi9inCY4Nd4y2' #米游社签到salt
Salt_BBS = 't0qEgfub6cvueAPgR5m9aQWWVciEer7v' #米游社讨论区专用salt
mysVersion = "2.38.1" #米游社版本
mysClient_type = '2' # 1:ios 2:Android

#日志输出配置
log = logger = logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s %(levelname)s %(message)s',
    datefmt='%Y-%m-%dT%H:%M:%S')

#全局变量
VERSION = "v1.6.1"
IdentifyChar = "⨌" #删帖用的特殊识别符号
PATH = os.path.dirname(os.path.realpath(__file__))
Multi_ConfigPath = PATH + "/MultiConfig/{}-config-{}.json"
Multi_CookiePath = PATH + "/MultiConfig/{}-cookie.json"

def LoadConfig(path=f"{PATH}/config.json"):
    '''加载配置文件'''
    with open(path, "r",encoding="utf-8") as f:
            data = json.load(f)
            f.close()
            return data

def LoadCookie(path=f"{PATH}/cookie.json"):
    '''加载缓存的cookie文件'''
    with open(path, "r",encoding="utf-8") as f:
            data = json.load(f)
            f.close()
            return data

def Write(login_ticket,stuid,stoken,path=f"{PATH}/cookie.json"):
    '''写入cookie缓存'''
    params = {"login_ticket": "{}".format(login_ticket), "stuid": "{}".format(stuid), "stoken": "{}".format(stoken)}
    with open(path, "w",encoding="utf-8") as r:
        json.dump(params,r)
        r.close()

def Cleared(path=f"{PATH}/cookie.json"):
    '''清空缓存cookie'''
    if path != f"{PATH}/cookie.json":
        os.remove(path)
    else:
        params = {"login_ticket": "", "stuid": "", "stoken": ""}
        with open(path, "w",encoding="utf-8") as r:
            json.dump(params,r)
            r.close()

def GameHeader(Referer="https://webstatic.mihoyo.com/"): #此referer为默认值
    '''游戏签到福利的header'''
    headers = {
        'Cookie': Cookie,
        'User-Agent': 'Mozilla/5.0 (Linux; Android 12; vivo-s7 Build/RKQ1.211119.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/105.0.5195.79 Mobile Safari/537.36 miHoYoBBS/2.38.1',
        'Referer': Referer,
        'DS': DS_BBS(),
        'Accept': 'application/json, text/plain, */*',
        'Accept-Encoding': 'gzip, deflate',
        'Origin': 'https://webstatic.mihoyo.com',
        'X-Requested-With': 'com.mihoyo.hyperion',
        'x-rpc-device_id': str(uuid.uuid3(uuid.NAMESPACE_URL, Cookie)),
        'x-rpc-device_name': 'vivo s7',
        'x-rpc-device_model': 'vivo-s7',
        'x-rpc-sys_version': '12',
        'x-rpc-channel': 'vivo',
        'x-rpc-client_type': mysClient_type,
        'x-rpc-app_version': mysVersion
    }
    return headers

def SleepTime():
    '''随机延迟时间'''
    if delay :
        time.sleep(random.randint(5,10))
    else:
        time.sleep(1)

def md5(text):
    '''md5加密'''
    md5 = hashlib.md5()
    md5.update(text.encode())
    return md5.hexdigest()

def randomStr(n):
    '''生成指定位数的随机数'''
    return (''.join(random.sample(string.digits + string.ascii_letters, n))).lower()

def DS_BBS():
    '''生成米游社DS'''
    n = Salt_Sign
    i = str(int(time.time()))
    r = randomStr(6)
    c = md5("salt=" + n + "&t=" + i + "&r=" + r)
    return "{},{},{}".format(i, r, c)

def DS_discuss(gid):
    '''生成讨论区DS'''
    n = Salt_BBS
    i = str(int(time.time()))
    r = str(random.randint(100001, 200000))
    b = json.dumps({"gids": gid})
    c = md5("salt=" + n + "&t=" + i + "&r=" + r + "&b=" + b + "&q=") #q值为空
    return "{},{},{}".format(i, r, c)

def GetAllRoles():
    '''获取账号的全部角色信息'''
    log.info('获取账号的全部角色信息中......')
    url = 'https://api-takumi.mihoyo.com/binding/api/getUserGameRolesByCookie'

    response = requests.get(url,headers=GameHeader())
    data = json.loads(response.text.encode('utf-8'))
    if '登录失效，请重新登录' in data["message"]:
        log.warning(data)
        log.warning('Cookie已失效,请重新抓取Cookie')
        sys.exit()
    SleepTime()
    return data

def CreateCookieCache(ConfigPath=f"{PATH}/config.json", CookiePath=f"{PATH}/cookie.json"):
    Cookie_url  = "https://webapi.account.mihoyo.com/Api/cookie_accountinfo_by_loginticket?login_ticket={}"
    Cookie_url2 = "https://api-takumi.mihoyo.com/auth/api/getMultiTokenByLoginTicket?login_ticket={}&token_types=3&uid={}"

    log.info("更新Cookie数据中......")
    CookieCache = LoadConfig(ConfigPath)
    if "login_ticket" in CookieCache["Cookie"]:
        c = CookieCache["Cookie"].split(";")
        for i in c:
            if i.split("=")[0] == " login_ticket": #login_ticket前面的空格是必须的
                LoginTicket = i.split("=")[1]
                break
        response = requests.get(url=Cookie_url.format(LoginTicket)) #获取stuid
        data = json.loads(response.text.encode('utf-8'))
        if "成功" in data["data"]["msg"]:
            stuid = data["data"]["cookie_info"]["account_id"]
            response = requests.get(url=Cookie_url2.format(LoginTicket, stuid)) #获取stoken
            data = json.loads(response.text.encode('utf-8'))
            stoken = data["data"]["list"][0]["token"]
            Write(LoginTicket,stuid,stoken,CookiePath)
            log.info('更新成功')
        else:
            log.warning(data)
            log.warning('Cookie已失效,请重新抓取Cookie')
            Cleared(CookiePath)
            sys.exit()
    else:
        log.warning('Cookie中没有"login_ticket"数据,请重新抓取Cookie!')
        sys.exit()

def Multi_Load():
    '''多账号加载'''
    MultiPath = []
    for m in range(len(glob.glob(Multi_ConfigPath.format("*","*")))):
        try:#从1开始轮询，避免顺序错误
            MultiPath.append(glob.glob(Multi_ConfigPath.format(m+1,"*"))[0])
        except:
            break
    MultiCookie = glob.glob(Multi_CookiePath.format("*"))
    TotalConfig = len(MultiPath)
    TotalCookie = len(MultiCookie)

    Cookie_url  = "https://webapi.account.mihoyo.com/Api/cookie_accountinfo_by_loginticket?login_ticket={}"
    Cookie_url2 = "https://api-takumi.mihoyo.com/auth/api/getMultiTokenByLoginTicket?login_ticket={}&token_types=3&uid={}"

    #获取备注信息
    MultiRemark = []
    for r in MultiPath:
        MultiRemark.append(r.split("/")[-1].split("-")[-1].split(".")[0])

    log.info('共找到 {} 个配置文件, {} 个缓存的Cookie数据'.format(TotalConfig,TotalCookie))

    #获取没有缓存cookie的账号序号并创建cookie缓存
    ConfigOrdinal = []
    for c1 in MultiPath:
        ConfigOrdinal.append(c1.split("/")[-1].split("-")[0])
    CookieOrdinal = []
    for c2 in MultiCookie:
        CookieOrdinal.append(c2.split("/")[-1].split("-")[0])
    if TotalCookie != TotalConfig:
        for D in ConfigOrdinal:
            if D not in CookieOrdinal:
                d = int(D)
                log.info('正在为 [{}] 更新cookie数据......'.format(MultiRemark[d-1]))
                CreateCookieCache(ConfigPath=Multi_ConfigPath.format(d, MultiRemark[d-1]), CookiePath=Multi_CookiePath.format(d))
                time.sleep(random.randint(2,5)) #写死休眠时间,防止触发风控
    return MultiPath,MultiRemark,TotalConfig

class BH2_Checkin():

    #api
    Referer_url     = 'https://webstatic.mihoyo.com/'
    Sign_url        = 'https://api-takumi.mihoyo.com/event/luna/sign' #POST json
    Check_url       = 'https://api-takumi.mihoyo.com/event/luna/info?lang=zh-cn&act_id={}&region={}&uid={}'

    #value
    Act_id          = 'e202203291431091'
    Game_biz        = 'bh2_cn'

    def bh2_sign(self,region,uid):
        sign_data = {
            "act_id": self.Act_id,
            "region": region,
            "uid": uid,
            "lang": "zh-cn"
        }
        log.info('崩坏2: 正在为舰长「{}」签到'.format(uid))
        response = requests.post(url=self.Sign_url, headers=GameHeader(Referer=self.Referer_url), json=sign_data)
        data = json.loads(response.text.encode('utf-8'))
        SleepTime()
        return data

    def run(self,list):
        region = list["region"]
        uid = list["game_uid"]

        for i in range(3):
            sign_data = self.bh2_sign(region,uid) #签到并返回数据
            try:
                #从专门的api检查签到结果
                response = requests.get(url=self.Check_url.format(self.Act_id,region,uid), headers=GameHeader())
                data = json.loads(response.text.encode('utf-8'))
                if "OK" in sign_data["message"]:
                    if data["data"]["is_sign"]: # "is_sign" 为布尔值
                        log.info('崩坏2: 签到成功')
                        break
                    else:
                        log.info('崩坏2: 第 {} 次请求成功但并未签上'.format(i + 1))
                elif "已签到" in sign_data["message"]:
                    log.info('崩坏2: 重复签到')
                    break
                else:
                    log.warning(sign_data)
                    log.warning('崩坏2: 签到出现错误,请及时检查')
                    break
            except:
                #直接输出签到api返回的签到结果
                log.warning(data)
                log.warning("未能从检查api得到签到结果,下面为签到api返回的结果,有概率漏签")
                if "OK" in sign_data["message"]:
                    log.info('崩坏2: 签到成功')
                elif '已签到' in sign_data["message"]:
                    log.info('崩坏2: 重复签到')
                else:
                    log.warning(sign_data)
                    log.warning('崩坏2: 签到出现错误,请及时检查')
                break

class BH3_Checkin():

    #api
    Referer_url     = 'https://webstatic.mihoyo.com/bbs/event/signin/bh3/index.html?bbs_auth_required=true&act_id={}&bbs_presentation_style=fullscreen&utm_source=bbs&utm_medium=mys&utm_campaign=icon'
    Sign_url        = 'https://api-takumi.mihoyo.com/event/luna/sign' #POST json
    Check_url       = 'https://api-takumi.mihoyo.com/event/luna/info?lang=zh-cn&act_id={}&region={}&uid={}'

    #value
    Act_id          = 'e202207181446311'
    Game_biz        = 'bh3_cn'

    def bh3_sign(self,region,uid):
        sign_data = {
            "act_id": self.Act_id,
            "region": region,
            "uid": uid,
            "lang": "zh-cn"
        }
        log.info('崩坏3: 正在为舰长「{}」签到'.format(uid))
        response = requests.post(url=self.Sign_url, headers=GameHeader(Referer=self.Referer_url.format(self.Act_id)), json=sign_data)
        data = json.loads(response.text.encode('utf-8'))
        SleepTime()
        return data

    def run(self,list):
        region = list["region"]
        uid = list["game_uid"]

        for i in range(3):
            sign_data = self.bh3_sign(region,uid) #签到并返回数据
            try:
                #从专门的api检查签到结果
                response = requests.get(url=self.Check_url.format(self.Act_id,region,uid), headers=GameHeader())
                data = json.loads(response.text.encode('utf-8'))
                if "OK" in sign_data["message"]:
                    if data["data"]["is_sign"]: # "is_sign" 为布尔值
                        log.info('崩坏3: 签到成功')
                        break
                    else:
                        log.info('崩坏3: 第 {} 次请求成功但并未签上'.format(i + 1))
                elif "已签到" in sign_data["message"]:
                    log.info('崩坏3: 重复签到')
                    break
                else:
                    log.warning(sign_data)
                    log.warning('崩坏3: 签到出现错误,请及时检查')
                    break
            except:
                #直接输出签到api返回的签到结果
                log.warning(data)
                log.warning("未能从检查api得到签到结果,下面为签到api返回的结果,有概率漏签")
                if "OK" in sign_data["message"]:
                    log.info('崩坏3: 签到成功')
                elif '已签到' in sign_data["message"]:
                    log.info('崩坏3: 重复签到')
                else:
                    log.warning(sign_data)
                    log.warning('崩坏3: 签到出现错误,请及时检查')
                break

class YS_Checkin():

    #api
    Referer_url     = 'https://webstatic.mihoyo.com/bbs/event/signin-ys/index.html?bbs_auth_required=true&act_id={}&utm_source=bbs&utm_medium=mys&utm_campaign=icon'
    Sign_url        = 'https://api-takumi.mihoyo.com/event/bbs_sign_reward/sign' #POST json
    Check_url       = 'https://api-takumi.mihoyo.com/event/bbs_sign_reward/info?act_id={}&region={}&uid={}'

    #value
    Act_id          = 'e202009291139501'
    Game_biz        = 'hk4e_cn'

    def ys_sign(self,region,uid):
        sign_data = {
            "act_id": self.Act_id,
            "region": region,
            "uid": uid
        }
        log.info('原神: 正在为旅行者「{}」签到'.format(uid))
        response = requests.post(url=self.Sign_url, headers=GameHeader(Referer=self.Referer_url.format(self.Act_id)), json=sign_data)
        data = json.loads(response.text.encode('utf-8'))
        SleepTime()
        return data

    def run(self,list):
        region = list["region"]
        uid = list["game_uid"]

        for i in range(3):
            sign_data = self.ys_sign(region,uid) #签到并返回数据
            try:
                #从专门的api检查签到结果
                response = requests.get(url=self.Check_url.format(self.Act_id,region,uid), headers=GameHeader())
                data = json.loads(response.text.encode('utf-8'))
                if "OK" in sign_data["message"]:
                    if data["data"]["is_sign"]: # "is_sign" 为布尔值
                        log.info('原神: 签到成功')
                        break
                    else:
                        log.info('原神: 第 {} 次请求成功但并未签上'.format(i + 1))
                elif "旅行者,你已经签到过了" in sign_data["message"]:
                    log.info('原神: 重复签到')
                    break
                else:
                    log.warning(sign_data)
                    log.warning('原神: 签到出现错误,请及时检查')
                    break
            except:
                #直接输出签到api返回的签到结果
                log.warning(data)
                log.warning("未能从检查api得到签到结果,下面为签到api返回的结果,有概率漏签")
                if "OK" in sign_data["message"]:
                    log.info('原神: 签到成功')
                elif "旅行者,你已经签到过了" in sign_data["message"]:
                    log.info('原神: 重复签到')
                else:
                    log.warning(sign_data)
                    log.warning('原神: 签到出现错误,请及时检查')
                break

class MihoyoBBS():
    #api
    Cookie_url          = "https://webapi.account.mihoyo.com/Api/cookie_accountinfo_by_loginticket?login_ticket={}"
    Cookie_url2         = "https://api-takumi.mihoyo.com/auth/api/getMultiTokenByLoginTicket?login_ticket={}&token_types=3&uid={}"
    Sign_url            = "https://bbs-api.mihoyo.com/apihub/app/api/signIn"  # POST json
    List_url            = "https://bbs-api.mihoyo.com/post/api/getForumPostList?forum_id={}&is_good=false&is_hot=false&page_size=20&sort_type=1"
    GetPostFull_url     = "https://bbs-api.mihoyo.com/post/api/getPostFull?post_id={}"
    Share_url           = "https://bbs-api.mihoyo.com/apihub/api/getShareConf?entity_id={}&entity_type=1"
    UpVote_url          = "https://bbs-api.mihoyo.com/apihub/sapi/upvotePost"  # POST json
    UserBusinesses_url  = "https://bbs-api.mihoyo.com/user/api/getUserBusinesses?uid={}" #获取"我的频道"信息
    Missions_url        = "https://bbs-api.mihoyo.com/apihub/api/getUserMissionsState?" #获取任务完成情况
    Draft_url           = "https://bbs-api.mihoyo.com/post/api/draft/save"  # POST json 草稿
    ReleasePost_url     = "https://bbs-api.mihoyo.com/post/api/releasePost/v2" # POST json 发帖
    ReleaseReply_url    = "https://bbs-api.mihoyo.com/post/api/releaseReply"# POST json 发评论
    SelfPostList_url    = "https://bbs-api.mihoyo.com/painter/api/user_instant/list?offset={}&size=20&uid={}" #offset初始值为0
    DeletePost_url      = "https://bbs-api.mihoyo.com/post/api/deletePost" # POST json 删帖

    #米游社分区
    BBS_List = [
        {
            "id": "1",
            "forumId": "1",
            "name": "崩坏3",
            "url": "https://bbs.mihoyo.com/bh3/"
        },
        {
            "id": "2",
            "forumId": "26",
            "name": "原神",
            "url": "https://bbs.mihoyo.com/ys/"
        },
        {
            "id": "3",
            "forumId": "30",
            "name": "崩坏2",
            "url": "https://bbs.mihoyo.com/bh2/"
        },
        {
            "id": "4",
            "forumId": "37",
            "name": "未定事件簿",
            "url": "https://bbs.mihoyo.com/wd/"
        },
        {
            "id": "5",
            "forumId": "34",
            "name": "大别野",
            "url": "https://bbs.mihoyo.com/dby/"
        },
        {
            "id": "6",
            "forumId": "52",
            "name": "星穹铁道",
            "url": "https://bbs.mihoyo.com/sr/"
        },
        {
            "id": "8",
            "forumId": "57",
            "name": "绝区零",
            "url": "https://bbs.mihoyo.com/zzz/"
        }
    ]

    def __init__(self):
        self.LoginTicket = CookieCache["login_ticket"]
        self.stuid = CookieCache["stuid"]
        self.stoken = CookieCache["stoken"]

        self.CheckMission = True #检查任务完成情况

        if self.LoginTicket == "" or self.stuid == "" or self.stoken == "":
            CreateCookieCache()

        self.headers = {
            "Cookie": f'login_ticket={self.LoginTicket};stuid={self.stuid};stoken={self.stoken}',
            'User-Agent': "okhttp/4.8.0",
            "DS": "", #随用随更新，保证实时性
            "x-rpc-client_type": mysClient_type,
            "x-rpc-app_version": mysVersion,
            "x-rpc-device_id": str(uuid.uuid3(uuid.NAMESPACE_URL, Cookie)),
            "x-rpc-device_name": "vivo s7",
            "x-rpc-device_model": "vivo-s7",
            "x-rpc-sys_version": "12",
            "x-rpc-channel": "miyousheluodi",
            "Accept-Encoding": "gzip",
            "Referer": "https://app.mihoyo.com",
            "Host": "bbs-api.mihoyo.com"
        }

        self.BBS_WhiteList = self.UserBusinesses()["data"]["businesses"]

    def UserBusinesses(self):
        log.info('获取米游社 “我的频道” 信息中......')
        self.headers["DS"] = DS_BBS()
        response = requests.get(url=self.UserBusinesses_url.format(self.stuid), headers=self.headers)
        data = json.loads(response.text.encode('utf-8'))
        if "OK" in data["message"]:
            SleepTime()
            return data
        elif "登录失效，请重新登录" in data["message"]:
            log.warning('Cookie已失效,请重新抓取Cookie')
            Cleared()
        else:
            log.warning(data)
            log.warning('获取账号 “我的频道” 信息失败,退出签到')
            sys.exit()

    def SignIn(self):
        log.info('讨论区签到中......')
        for i in self.BBS_List:
            if i["id"] in self.BBS_WhiteList:
                self.headers["DS"] = DS_discuss(gid=i["id"])
                response = requests.post(url=self.Sign_url, headers=self.headers, json={"gids": i["id"]})
                data = json.loads(response.text.encode('utf-8'))
                if "登录失效，请重新登录" not in data["message"]:
                    log.info(i["name"] + ": " + data["message"])
                    SleepTime()
                else:
                    log.warning(data)
                    log.warning('签到失败,你的Cookie可能已过期,请重新抓取Cookie')
                    Cleared()
                    sys.exit()

    def Only_MYB(self):
        '''米游币任务'''
        List = []
        #获取帖子列表
        self.headers["DS"] = DS_BBS()
        response = requests.get(url=self.List_url.format('26'), headers=self.headers) #获取的原神频道帖子
        data = json.loads(response.text.encode('utf-8'))
        #将获取到的帖子的id写入List
        PostSum = len(data["data"]["list"])
        for n in range(PostSum):
            List.append(data["data"]["list"][n]["post"]["post_id"])

        #看帖3篇
        Success = 0
        Count = 0
        log.info('浏览3篇帖子中......')
        while Success < 3 and Count < PostSum:
            self.headers["DS"] = DS_BBS()
            response = requests.get(url=self.GetPostFull_url.format(List[Count]), headers=self.headers)
            data = json.loads(response.text.encode('utf-8'))
            if "OK" in data["message"]:
                Count += 1
            elif "帖子不存在" in data["message"]:
                log.warning('帖子(ID:{})可能已被作者或管理员删除'.format(List[Count]))
                Count += 1
            else:
                log.warning(data)
                log.warning("浏览帖子出现问题,请及时检查")
                Count = 114514 #手动定义数值达到退出循环的效果
            #检查任务是否完成
            if Count >= 3 and self.CheckMission:
                try:
                    response = requests.get(url=self.Missions_url + "point_sn=myb", headers=GameHeader()) #使用的是游戏签到header!!!
                    data = json.loads(response.text.encode('utf-8'))
                    for i in data["data"]["states"]:
                        if i["mission_key"] == "view_post_0": #米游币每日任务-浏览3个帖子
                            Success = i["happened_times"]
                            break
                except:
                    log.warning("检查任务是否完成失败,跳过检查")
                    log.warning(data)
                    self.CheckMission = False
            else:
                Success = Count
            SleepTime()
        else:
            if self.CheckMission:
                log.info("检查结果: 浏览成功 {} 篇".format(Success))
            else:
                log.info('未能从检查api得到签到结果,有概率漏签')
            if Success < 3:
                if Count == 114514:
                    Count = ' "( !!出现错误!! )" '
                log.warning('尝试了{}篇帖子,浏览成功{}篇,未能完成任务'.format(Count,Success))

        #点赞5篇
        Success = 0
        Count = 0
        log.info('点赞5篇帖子中......')
        while Success < 5 and Count < PostSum:
            self.headers["DS"] = DS_BBS()
            response = requests.post(url=self.UpVote_url, headers=self.headers,json={"post_id": List[Count], "is_cancel": False})
            data = json.loads(response.text.encode('utf-8'))
            if "OK" in data["message"]:
                Count += 1
            elif "帖子不存在" in data["message"]:
                log.warning('帖子(ID:{})可能已被作者或管理员删除'.format(List[Count]))
                Count += 1
            else:
                log.warning(data)
                log.warning("点赞出现问题,请及时检查")
                Count = 114514 #手动定义数值达到退出循环的效果
            #检查任务是否完成
            if Count >= 5 and self.CheckMission:
                response = requests.get(url=self.Missions_url + "point_sn=myb", headers=GameHeader()) #使用的是游戏签到header!!!
                data = json.loads(response.text.encode('utf-8'))
                #上一步"看帖3篇"try过了
                for i in data["data"]["states"]:
                    if i["mission_key"] == "post_up_0": #米游币每日任务-完成5次点赞
                        Success = i["happened_times"]
                        break
            else:
                Success = Count
            SleepTime()
        else:
            if self.CheckMission:
                log.info("检查结果: 点赞成功 {} 篇".format(Success))
            else:
                log.info('未能从检查api得到签到结果,有概率漏签')
            if Success < 5:
                if Count == 114514:
                    Count = ' "( !!出现错误!! )" '
                log.warning('尝试了{}篇帖子,点赞成功{}篇,未能完成任务'.format(Count,Success))

        #分享1篇
        Success = 0
        Count = 0
        log.info('分享1篇帖子中......')
        while Success < 1 and Count < PostSum:
            self.headers["DS"] = DS_BBS()
            response = requests.get(url=self.Share_url.format(List[Count]), headers=self.headers)
            data = json.loads(response.text.encode('utf-8'))
            if "OK" in data["message"]:
                Count += 1
            elif "帖子不存在" in data["message"]:
                log.warning('帖子(ID:{})可能已被作者或管理员删除'.format(List[Count]))
                Count += 1
            else:
                log.warning(data)
                log.warning("点赞出现问题,请及时检查")
                Count = 114514 #手动定义数值达到退出循环的效果
            #检查任务是否完成
            if Count >= 1 and self.CheckMission:
                response = requests.get(url=self.Missions_url + "point_sn=myb", headers=GameHeader()) #使用的是游戏签到header!!!
                data = json.loads(response.text.encode('utf-8'))
                #上上步"看帖3篇"try过了
                for i in data["data"]["states"]:
                    if i["mission_key"] == "share_post_0": #米游币每日任务-分享帖子
                        Success = i["happened_times"]
                        break
            else:
                Success = Count
            SleepTime()
        else:
            if self.CheckMission:
                log.info("检查结果: 分享成功 {} 篇".format(Success))
            else:
                log.info('未能从检查api得到签到结果,有概率漏签')
            if Success < 1:
                if Count == 114514:
                    Count = ' !!出现错误!! '
                log.warning('尝试了分享{}篇帖子,居然一篇都没有成功,未能完成任务'.format(Count))

    def Channel_UpVote(self,channel):
        '''各频道点赞任务'''
        List = []
        log.info("正在执行「{}」频道 “点赞10篇帖子” 任务......".format(channel["name"]))
        #获取该频道帖子列表
        self.headers["DS"] = DS_BBS()
        response = requests.get(url=self.List_url.format(channel["forumId"]), headers=self.headers)
        data = json.loads(response.text.encode('utf-8'))
        #将获取到的帖子的id写入List
        PostSum = len(data["data"]["list"])
        for n in range(PostSum):
            List.append(data["data"]["list"][n]["post"]["post_id"])

        #点赞10篇
        Success = 0
        Count = 0
        while Success < 10 and Count < PostSum:
            self.headers["DS"] = DS_BBS()
            response = requests.post(url=self.UpVote_url, headers=self.headers,json={"post_id": List[Count], "is_cancel": False})
            data = json.loads(response.text.encode('utf-8'))
            if "OK" in data["message"]:
                Count += 1
            elif "帖子不存在" in data["message"]:
                log.warning('帖子(ID:{})可能已被作者或管理员删除'.format(List[Count]))
                Count += 1
            else:
                log.warning(data)
                log.warning("点赞出现问题,请及时检查")
                Count = 114514 #手动定义数值达到退出循环的效果
            #检查任务是否完成
            if Count >= 10 and self.CheckMission:
                try:
                    response = requests.get(url=self.Missions_url + "gids={}".format(channel["id"]), headers=GameHeader()) #使用的是游戏签到header!!!
                    data = json.loads(response.text.encode('utf-8'))
                    for i in data["data"]["states"]:
                        if i["mission_key"] == "post_up_{}".format(channel["id"]):
                            Success = i["happened_times"]
                            break
                except:
                    log.warning("检查任务是否完成失败,跳过检查")
                    self.CheckMission = False
            else:
                Success = Count
            SleepTime()
        else:
            if self.CheckMission:
                log.info("检查结果: 点赞成功 {} 篇".format(Success))
            else:
                log.info('未能从检查api得到签到结果,有概率漏签')
            if Success < 10:
                if Count == 114514:
                    Count = ' "( !!出现错误!! )" '
                log.warning('尝试了{}篇帖子,点赞成功{}篇,未能完成任务'.format(Count,Success))

    #以下为实验性功能
    #↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
    def RandomElements(self):
        '''随机标题与内容'''
        SubjectList = ["水帖一篇","水水水","今日份的水帖","水帖得经验","随手一水","灌水灌水","欸嘿水一帖","每日任务之水帖"]
        ContentList = ["水帖不知道写啥","求点赞评论收藏","为了经验大家快来水帖呀","今日份的经验我来啦","水帖多多经验多多","愿早日升级"]
        Reply1List = ["我回我自己","发帖回复一条龙","为了经验必须回帖","今日份的经验get","水个回复","回复一下愿早日升级"]
        Reply2List = ["每日任务需要的回复","什么大伟出奇迹","飞鱼丸与包菜与应急食品","勤劳的孩子升级快","Ok经验到手","回复一下加快升级"]
        Subject = SubjectList[random.randint(0,len(SubjectList) - 1)]
        Content = ContentList[random.randint(0,len(ContentList) - 1)]
        Reply1 = Reply1List[random.randint(0,len(Reply1List) - 1)]
        Reply2 = Reply2List[random.randint(0,len(Reply2List) - 1)]
        return Subject,Content,Reply1,Reply2

    def Draft(self,subject,content,forumId,id):
        '''写入草稿'''
        draft_data = {
            "collection_id": 0,
            "content": "\u003cp\u003e" + content + "\u003c/p\u003e",
            "cover": "",
            "draft_id": "",
            "forum_cate_id": "",
            "forum_id": forumId,
            "gids": id,
            "is_original": 0,
            "is_profit": False,
            "link_card_list": [],
            "republish_authorization": 1,
            "structured_content": "[{\"insert\":\"" + content + "\\n\"}]",
            "subject": subject,
            "topic_ids": ["877"], #"每日一水"话题分区id
            "view_type": 1
        }
        count = 0
        while count < 3:
            self.headers["DS"] = DS_BBS()
            response = requests.post(url=self.Draft_url, headers=self.headers, json=draft_data)
            data = json.loads(response.text.encode('utf-8'))
            if data["retcode"] == 0:
                return(data["data"]["draft_id"])
            elif data["retcode"] == -1:
                count += 1
                log.info('系统繁忙,1分钟后再次尝试')
                time.sleep(random.randint(55,65)) #写死休眠时间,防止触发风控
            else:
                log.warning(data)
                log.warning('写入草稿发生错误')
                return("err")
        else:
            log.warning(data)
            log.warning('写入草稿失败')
            return("err")

    def ReleasePost(self,subject,content,forumId,id,DraftId):
        '''发帖'''
        post_data = {
            "collection_id": 0,
            "content": "\u003cp\u003e" + content + "\u003c/p\u003e",
            "cover": "",
            "draft_id": DraftId,
            "forum_cate_id": "",
            "f_forum_id": forumId,
            "gids": id,
            "is_original": 0,
            "is_pre_publication": False,
            "is_profit": False,
            "post_id": "",
            "republish_authorization": 0,
            "structured_content": "[{\"insert\":\"" + content + "\\n\"}]",
            "subject": subject,
            "topic_ids": ["877"], #"每日一水"话题分区id
            "view_type": 1
        }
        count = 0
        while count < 3:
            self.headers["DS"] = DS_BBS()
            response = requests.post(url=self.ReleasePost_url, headers=self.headers, json=post_data)
            data = json.loads(response.text.encode('utf-8'))
            if data["retcode"] == 0:
                return(data["data"]["post_id"])
            elif data["retcode"] == -1:
                count += 1
                log.info('系统繁忙,1分钟后再次尝试')
                time.sleep(random.randint(55,65)) #写死休眠时间,防止触发风控
            else:
                log.warning(data)
                log.warning('发帖发生错误')
                return("err")
        else:
            log.warning(data)
            log.warning('发帖失败')
            return("err")

    def ReleaseReply(self,PostId,reply):
        '''发评论'''
        reply_data = {
            "content": reply,
            "post_id": PostId,
            "reply_id": "",
            "structured_content": "[{\"insert\":\"" + reply + "\"}]"
        }
        count = 0
        while count < 3:
            self.headers["DS"] = DS_BBS()
            response = requests.post(url=self.ReleaseReply_url, headers=self.headers, json=reply_data)
            data = json.loads(response.text.encode('utf-8'))
            if data["retcode"] == 0:
                return data["message"]
            elif "帖子不存在" in data["message"]:
                log.warning('帖子不存在,无法评论')
                return("err")
            elif data["retcode"] == -1: #这条if未经验证,仅为推测,"帖子不存在"的retcode值也为-1
                count += 1
                log.info('系统繁忙,1分钟后再次尝试')
                time.sleep(random.randint(55,65)) #写死休眠时间,防止触发风控
            else:
                log.warning(data)
                log.warning('发评论发生错误')
                return("err")
        else:
            log.warning(data)
            log.warning('评论失败')
            return("err")

    def DeletePost(self):
        #获取需要删除的帖子
        SelfPost = []
        offset,limit = 0,0
        last = False
        while last == False and limit < 5:
            limit += 1
            self.headers["DS"] = DS_BBS()
            response = requests.get(url=self.SelfPostList_url.format(offset,self.stuid), headers=self.headers)
            data = json.loads(response.text.encode('utf-8'))
            if data["retcode"] == 0:
                offset = data["data"]["next_offset"]
                last = data["data"]["is_last"]
                for list in data["data"]["list"]:
                    PostSubject = list["post"]["post"]["subject"]
                    #双重识别,避免误删
                    if IdentifyChar in PostSubject:
                        if PostSubject.split(IdentifyChar)[-1] == "(1/2)" or PostSubject.split(IdentifyChar)[-1] == "(2/2)":
                            SelfPost.append(list["post"]["post"]["post_id"])
            else:
                log.info(data)
            SleepTime()
        #开始删帖
        for id in SelfPost:
            delete_data = {
                "operate_type": 0,
                "post_id": id
                }
            self.headers["DS"] = DS_BBS()
            response = requests.post(url=self.DeletePost_url, headers=self.headers, json=delete_data)
            data = json.loads(response.text.encode('utf-8'))
            if data["retcode"] == 0:
                log.info('删除帖子(id:{}) 成功'.format(id))
            else:
                log.info(data)
            SleepTime()

    def Channel_Publish(self,channel):
        '''执行各频道发帖&发评论任务'''
        for i in range(2): #发帖2篇
            subject,content,reply1,reply2 = self.RandomElements()
            subject += " {}({}/2)".format(IdentifyChar, i+1)

            #写入草稿
            DraftId = self.Draft(subject,content,channel["forumId"],channel["id"])
            time.sleep(random.randint(12,15)) #写死休眠时间,防止触发风控

            #发帖子
            if DraftId == "err":
                return("err")
            else:
                PostId = self.ReleasePost(subject,content,channel["forumId"],channel["id"],DraftId)
                time.sleep(random.randint(20,30)) #写死休眠时间,防止触发风控

            #给每篇自己的帖子回复2次
            if PostId == "err":
                Reply1Data = "未发帖"
                Reply2Data = "未发帖"
            else:
                Reply1Data = self.ReleaseReply(PostId,reply1)
                time.sleep(random.randint(150,180)) #写死休眠时间,防止触发风控
                Reply2Data = self.ReleaseReply(PostId,reply2)

            #改为全角,保证对齐,强迫症直呼满足
            PerfectName = channel["name"] #传递给中间参数，避免修改源数据
            if PerfectName == "崩坏3":
                PerfectName = "崩坏３"
            elif PerfectName == "崩坏2":
                PerfectName = "崩坏２"
            elif PerfectName == "原神":
                PerfectName = "原　神"

            log.info('频道:{:　<4}, 次数:{}, DraftID:{}, PostID:{}, 评论:{},{}'.format(PerfectName, i+1, DraftId, PostId, Reply1Data, Reply2Data))
            time.sleep(random.randint(300,320)) #亲测发帖间隔5分钟以上较为安全不太会触发风控
    #↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
    #以上为实验性功能

    def run(self):
        if Enable["BBS"]:
            log.info('开始执行米游币任务......')
            self.SignIn() #讨论区签到
            self.Only_MYB() #3次浏览,5次点赞,1次分享

        if Enable["ChannelUpVote"]:
            log.info('开始执行各频道点赞任务......')
            for channel in self.BBS_List:
                if channel["id"] in self.BBS_WhiteList:
                    self.Channel_UpVote(channel)

        if Enable["DeleteOldPost"]:
            log.info('开始删除今天之前水的贴子')
            self.DeletePost()

        if Enable["ChannelPublish"]:
            log.info('开始执行各频道发帖&评论任务......')
            log.info('!!!作者亲测发帖间隔5分钟以上较为安全不太会触发风控,故完成本项任务需要巨长时间!!!')
            log.info('预估完成本项任务需要 {} 分钟,请一定要耐心等待'.format(len(self.BBS_WhiteList) * 2 * 8)) #乘数的第一个为每频道发帖次数，第二个为间隔时间
            for channel in self.BBS_List:
                if channel["id"] in self.BBS_WhiteList:
                    result = self.Channel_Publish(channel)
                    if result == "err":
                        log.warning('退出执行各频道发帖&评论任务')
                        break

def StartRun():
    if delay:
        log.info('已启用随机延迟,请耐心等待')

    #实验性功能警告
    if Enable["ChannelPublish"] or Enable["DeleteOldPost"]:
        log.info('!!!您已开启实验性功能,可能存在未知bug,且会有各种不可预估的风险(如禁言,封号),请酌情选择使用!!!')

    #游戏每日签到
    if Enable["BH2"] or Enable["BH3"] or Enable["YS"]:
        for list in GetAllRoles()["data"]["list"]:
            if list["game_biz"] == "bh2_cn" and list["game_uid"] not in Game_BlackList["BH2"] and Enable["BH2"]:
                BH2_Checkin().run(list) #崩坏2每日签到
            if list["game_biz"] == "bh3_cn" and list["game_uid"] not in Game_BlackList["BH3"] and Enable["BH3"]:
                BH3_Checkin().run(list) #崩坏3福利补给
            elif list["game_biz"] == "hk4e_cn" and list["game_uid"] not in Game_BlackList["YS"] and Enable["YS"]:
                YS_Checkin().run(list) #原神每日签到

    #米游社签到
    if Enable["BBS"] or Enable["ChannelUpVote"] or Enable["ChannelPublish"] or Enable["DeleteOldPost"]:
        MihoyoBBS().run()

if __name__ == '__main__':
    if "updata" in sys.argv: #手动更新cookie
        log.info('请选择为哪个模式的config更新cookie(1:单账号  2:多账号)')
        mode = int(input("请输入对应的数字："))
        if mode == 1:
            CreateCookieCache()
        elif mode == 2:
            number = int(input('请输入多账号config文件的序号：'))
            CreateCookieCache(ConfigPath=glob.glob(Multi_ConfigPath.format(number,"*"))[0], CookiePath=Multi_CookiePath.format(number))

    elif "multi" in sys.argv: #多账号
        log.info(f'欢迎使用 MihoyoBBS-AutoSign {VERSION} (多账号版)')

        #读取多账号配置
        MultiPath,MultiRemark,TotalConfig = Multi_Load()
        for n in range(TotalConfig):
            log.info('{:=^46}'.format(f'正在执行第 {n+1} 个账号(备注信息:{MultiRemark[n]})'))
            try:
                config = LoadConfig(Multi_ConfigPath.format(n+1,MultiRemark[n]))
                delay = config["Delay"]
                Cookie = config["Cookie"]
                Game_BlackList = config["Game_BlackList"]
                Enable = config["Enable"]
                CookieCache = LoadCookie(Multi_CookiePath.format(n+1))
            except:
                log.warning('有配置信息未能读取,请检查配置文件是否有错误或是否为最新版')
                sys.exit()
            StartRun()

    else:#单账号
        log.info(f'欢迎使用 MihoyoBBS-AutoSign {VERSION}')

        #读取配置文件
        try:
            config = LoadConfig()
            delay = config["Delay"]
            Cookie = config["Cookie"]
            Game_BlackList = config["Game_BlackList"]
            Enable = config["Enable"]
            CookieCache = LoadCookie()
        except:
            log.warning('有配置信息未能读取,请检查配置文件是否有错误或是否为最新版')
            sys.exit()
        StartRun()

    log.info('任务全部完成\n')


import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
from torch.autograd import Variable
from torch.utils.data import dataloader
from torch.backends import cudnn
import random
from tqdm import *
import decimal
from copy import deepcopy
from dataset import return_similar_word_to_vector
from transformer import EncoderLayer
from transformer import PositionalEncoder
from copy import deepcopy

# torch.has_cudnn = False

def frange(x, y, jump):
  while x < y:
    yield x
    x += jump

def squash(tensor, dim=-1):
    squared_norm = (tensor ** 2).sum(dim=dim, keepdim=True)
    scale = squared_norm / (1 + squared_norm)
    return scale * tensor / torch.sqrt(squared_norm)

def load_saved_model(num_of_topics=11, hidden_size=128, topic_hidden_size=32, drop_out_prob=0.6,
                     model_path='./topic-attention'):
    model = TopicAttention(num_of_topics=num_of_topics, hidden_size=hidden_size,
                           topic_hidden_size=topic_hidden_size, drop_out_prob=drop_out_prob)
    model.load_state_dict(torch.load(model_path))
    model.eval()
    return model

def examine_context_vectors(num_of_topics, model_path):
    model = load_saved_model(num_of_topics, model_path)
    for vector in model.attn_context:
        print(return_similar_word_to_vector(vector.detach().numpy()))

def get_sentence_weights(model_path, sentence):
    model = load_saved_model()
    sentence = Variable(torch.from_numpy(sentence)).float()
    if torch.has_cudnn:
        sentence.cuda()
    weights, probs = model.return_weights(sentence.unsqueeze(0))
    return weights, probs

class topkCE(nn.Module):
    def __init__(self, k=0.7):
        super(topkCE, self).__init__()
        self.loss = nn.NLLLoss()
        self.k = k
        self.softmax = nn.LogSoftmax(dim=1)
        return

    def forward(self, input, target):
        softmax_result = self.softmax(input)

        loss = Variable(torch.Tensor(1).zero_())
        if torch.has_cudnn:
            loss = loss.cuda()
        for idx, row in enumerate(softmax_result):
            gt = target[idx]
            pred = torch.unsqueeze(row, 0)
            cost = self.loss(pred, gt.unsqueeze(0))
            loss = torch.cat((loss, cost.unsqueeze(0)), 0)
        loss = loss[1:]
        if self.k == 1.0:
            valid_loss = loss
        else:
            index = torch.topk(loss, int(self.k * loss.size()[0]))
            valid_loss = loss[index[1]]
        return torch.mean(valid_loss)

class TopicAttentionLayer(nn.Module):
    def __init__(self, input_size, num_of_topics):
        super(TopicAttentionLayer, self).__init__()
        self.attention_context = nn.ModuleList([nn.Linear(input_size, input_size) for _ in range(num_of_topics)])
        self.attention_linear_trans = nn.ModuleList([nn.Linear(input_size, input_size)
                                                       for _ in range(num_of_topics)])
        self.num_of_topics = num_of_topics
        self.attention_drop_out = nn.Dropout(.1)

    def forward(self, x, mask=None):
        topic_output = []
        for i in range(self.num_of_topics):
            energy = self.attention_context[i](x)
            if mask is not None:
                energy = energy.masked_fill(mask, -np.inf)
            probs = F.softmax(energy, dim=1)
            probs = self.attention_drop_out(probs)
            out = torch.sum(x * probs, dim=1)

            out = self.attention_linear_trans[i](out)
            out = F.relu(out)

            topic_output.append(out.unsqueeze(1))
        return torch.cat(topic_output, dim=1)

    def regularization_loss(self):
        attn_contexts = torch.cat([self.attention_context[i].weight.view(-1).unsqueeze(0) for i in range(self.num_of_topics)], dim=0)
        normalized_context_values = F.normalize(attn_contexts, dim=1)
        reg_term = torch.mm(normalized_context_values, normalized_context_values.t())
        identity = Variable(torch.eye(self.num_of_topics, self.num_of_topics))
        if torch.has_cudnn:
            identity = identity.cuda()
        return torch.norm(reg_term - identity, p=2.0)

class MemNet(nn.Module):
    def __init__(self, input_size):
        super(MemNet, self).__init__()
        self.query = nn.Parameter(torch.randn(input_size), requires_grad=True)

        self.linear_transform = torch.nn.Conv1d(input_size, input_size, kernel_size=5, padding=2)
        self.bn = torch.nn.BatchNorm1d(input_size)
        self.drop_out = nn.Dropout(.6)

        self.layer_norm = nn.LayerNorm(input_size)

        self.halt_prob = nn.Linear(input_size, 2)
        self.max_hop = 10

    def forward(self, x, mask):
        query = torch.cat([self.query.unsqueeze(0) for _ in range(x.size(0))], dim=0).contiguous()
        halted = torch.zeros(x.size(0)).byte().contiguous()
        halted = Variable(halted)
        if torch.has_cudnn:
            halted = halted.cuda()

        halting_hop = [self.max_hop] * x.size(0)
        n_hop = 0
        while not (halted.any() or n_hop == self.max_hop):
            energies = torch.bmm(x, query.unsqueeze(-1)).squeeze(-1)
            energies = energies.masked_fill(mask.squeeze(-1), -np.inf)
            probs = F.softmax(energies, dim=-1)

            x = F.leaky_relu(self.bn(self.linear_transform(x.transpose(1, 2))).transpose(1, 2))
            x = x.masked_fill(mask, .0)

            o = torch.bmm(x.transpose(1, 2), probs.unsqueeze(-1)).squeeze(-1)
            halt_prob = F.softmax(self.halt_prob(o + query), dim=-1)
            query = o + query * (1 - halted).unsqueeze(1).float()

            for b in range(len(halted)):
                halted[b] = halted[b] or (torch.argmax(halt_prob[b], dim=-1) == 1)
                if halting_hop[b] == self.max_hop and halted[b]:
                    halting_hop[b] = n_hop
            n_hop += 1
        # if not self.training:
        #     print(max_prob)
        #     print(min_prob)
        #     print(sum(halting_hop) / len(halting_hop))
        return query

class TopicAttention(nn.Module):
    def __init__(self, num_of_topics=11, hidden_size=150, input_size=300, classification_size=12, topic_hidden_size=20,
                 drop_out_prob=0.6, sentence_length=65, embeddings=None):
        super(TopicAttention, self).__init__()
        torch.manual_seed(0)
        np.random.seed(0)
        if torch.has_cudnn:
            torch.cuda.manual_seed(0)
        cudnn.benchmark = True
        random.seed(0)
        self.num_of_topics = num_of_topics
        self.sentence_length = sentence_length
        self.classification_size = classification_size
        self.topic_hidden_size = topic_hidden_size
        self.hidden_size = hidden_size
        self.input_size = input_size

        self.vocab_size = len(embeddings)

        self.embeddings = nn.Embedding(num_embeddings=self.vocab_size, embedding_dim=300, padding_idx=self.vocab_size - 1)
        self.embeddings.weight = nn.Parameter(torch.from_numpy(embeddings).float(), requires_grad=False)

        self.dim_reduction = nn.Linear(300, 128)
        self.pos_emb = PositionalEncoder(300, 66, 128, self.vocab_size - 1)

        self.layer_norm = torch.nn.LayerNorm(128)

        self.mem_net = nn.ModuleList([
            MemNet(128) for _ in range(num_of_topics)
        ])
        self.output_layer = torch.nn.Linear(num_of_topics * 128, classification_size)
        self.drop_out = nn.Dropout(p=drop_out_prob)

        self.topic_hidden = nn.ModuleList([
                nn.Linear(128, topic_hidden_size) for _ in range(num_of_topics)])
        self.attn_aspect_context = nn.Parameter(torch.randn(classification_size, topic_hidden_size))
        self.linear_aspect = nn.ModuleList([nn.Linear(topic_hidden_size * num_of_topics, 2 * topic_hidden_size)
                                            for _ in range(self.classification_size)])
        self.encoder = torch.nn.GRU(input_size=input_size, hidden_size=64, batch_first=True, bidirectional=True)

    def forward(self, x_indices, lengths, validate=False):
        mask = Variable(torch.ones(x_indices.size(0), x_indices.size(1), 1))
        if torch.has_cudnn:
            mask = mask.cuda()
        max_len = lengths[torch.argmax(lengths)].item()
        for i, l in enumerate(lengths):
            if l < max_len:
                mask[i, l:] = 0
        mask = mask.byte()
        # attention_mask = 1 - torch.bmm(mask, mask.transpose(1, 2))
        mask = 1 - mask

        x = self.embeddings(x_indices) + self.pos_emb(x_indices)
        x = self.dim_reduction(x)
        x = x.masked_fill(mask, .0)
        x = self.drop_out(x)

        # x = torch.nn.utils.rnn.pack_padded_sequence(x, lengths, batch_first=True)
        # x, _ = self.encoder(x)
        # x, lengths = torch.nn.utils.rnn.pad_packed_sequence(x, batch_first=True)
        # x = self.layer_norm(x)

        x = torch.cat([self.mem_net[i](x, mask) for i in range(self.num_of_topics)], dim=-1)
        x = torch.sigmoid(self.output_layer(x))
        return x, self.regularization_loss()

    def regularization_loss(self):
        attn_contexts = torch.cat([self.mem_net[i].query.unsqueeze(0) for i in range(self.num_of_topics)], dim=0)
        normalized_context_values = F.normalize(attn_contexts, dim=1)
        reg_term = torch.mm(normalized_context_values, normalized_context_values.t())
        identity = Variable(torch.eye(self.num_of_topics, self.num_of_topics))
        if torch.has_cudnn:
            identity = identity.cuda()
        return torch.norm(reg_term - identity, p=2.0)

    def return_weights(self, x):
        weights = []
        existance = []
        x, hidden = self.rnn(x)
        for i in range(self.num_of_topics):
            context = torch.stack([self.attn_context[i]] * x.size(0))
            energy = torch.bmm(x, context.unsqueeze(-1))
            probs = F.softmax(energy, dim=1)
            weights.append(probs)
            out = torch.bmm(x.transpose(1, 2), probs).squeeze(-1)
            out = self.topic_hidden[i](out)
            out = squash(out)
            existance.append(torch.norm(out[0]))
        return weights, existance

class TopicAttentionWithoutSquash(nn.Module):
    def __init__(self, num_of_topics=6, hidden_size=150, input_size=300, classification_size=5, topic_hidden_size=20,
                 drop_out_prob=0.6, sentence_length=65):
        super(TopicAttentionWithoutSquash, self).__init__()
        torch.manual_seed(0)
        np.random.seed(0)
        if torch.has_cudnn:
            torch.cuda.manual_seed(0)
        cudnn.benchmark = True
        random.seed(0)
        self.num_of_topics = num_of_topics
        self.sentence_length = sentence_length
        self.classification_size = classification_size
        self.topic_hidden_size = topic_hidden_size
        self.drop_out = nn.Dropout(p=drop_out_prob)

        self.rnn = nn.GRU(input_size=input_size,
                          hidden_size=hidden_size,
                          num_layers=1, batch_first=True, bidirectional=True)
        self.attn_context = nn.Parameter(torch.randn(num_of_topics, hidden_size * 2))
        self.topic_hidden = nn.ModuleList([
                nn.Linear(hidden_size * 2, topic_hidden_size) for _ in range(num_of_topics)])
        self.output_layer = nn.Linear(topic_hidden_size * num_of_topics, classification_size, bias=True)

        self.attn_aspect_context = nn.Parameter(torch.randn(classification_size, topic_hidden_size))
        self.linear_aspect = nn.ModuleList([nn.Linear(topic_hidden_size * num_of_topics, 2 * topic_hidden_size)
                                            for _ in range(self.classification_size)])

        self.reconstruction_layer = nn.Sequential(
            nn.Linear(topic_hidden_size * num_of_topics, 2 ** 10),
            nn.ReLU(),
            nn.Linear(2 ** 10, 2 ** 12),
            nn.ReLU(),
            nn.Linear(2 ** 12, sentence_length * input_size),
            nn.Sigmoid()
        )
        self.reconstruction_loss_function = nn.MSELoss()

    def forward(self, x, validate=False):
        x = self.drop_out(x)
        self.rnn.flatten_parameters()
        x, hidden = self.rnn(x)
        x = self.drop_out(x)
        x_ = None
        for i in range(self.num_of_topics):
            context = torch.stack([self.attn_context[i]] * x.size(0))
            energy = torch.bmm(x, context.unsqueeze(-1))
            probs = F.softmax(energy, dim=1)
            out = torch.bmm(x.transpose(1, 2), probs).squeeze(-1)
            out = self.topic_hidden[i](out)
            out = F.relu(out)
            if x_ is None:
                x_ = out
            else:
                x_ = torch.cat((x_, out), dim=1)
        x = self.output_layer(x_)
        x = torch.sigmoid(x)
        return x, self.regularization_loss()

    def regularization_loss(self):
        normalized_context_values = F.normalize(self.attn_context, dim=1)
        reg_term = torch.mm(normalized_context_values, normalized_context_values.t())
        identity = Variable(torch.eye(self.num_of_topics, self.num_of_topics))
        if torch.has_cudnn:
            identity = identity.cuda()
        return torch.norm(reg_term - identity)

    def reconstruction_loss(self, hidden_topic, init_input):
        recon_input = self.reconstruction_layer(hidden_topic)
        init_input_flat = None
        for i in range(init_input.size(1)):
            if init_input_flat is None:
                init_input_flat = init_input[:, i]
            else:
                init_input_flat = torch.cat((init_input_flat, init_input[:, i]), dim=1)
        return self.reconstruction_loss_function(recon_input, init_input_flat)

    def return_weights(self, x):
        weights = []
        existance = []
        x, hidden = self.rnn(x)
        for i in range(self.num_of_topics):
            context = torch.stack([self.attn_context[i]] * x.size(0))
            energy = torch.bmm(x, context.unsqueeze(-1))
            probs = F.softmax(energy, dim=1)
            weights.append(probs)
            out = torch.bmm(x.transpose(1, 2), probs).squeeze(-1)
            out = self.topic_hidden[i](out)
            out = squash(out)
            existance.append(torch.norm(out[0]))
        return weights, existance

class VanillaAttention(nn.Module):
    def __init__(self, hidden_size=150, input_size=300, classification_size=5, drop_out_prob=0.6, embeddings=None):
        super(VanillaAttention, self).__init__()
        torch.manual_seed(0)
        np.random.seed(0)
        if torch.has_cudnn:
            torch.cuda.manual_seed(0)
        cudnn.benchmark = True
        random.seed(0)

        self.vocab_size = len(embeddings)

        self.embeddings = nn.Embedding(num_embeddings=self.vocab_size, embedding_dim=300, padding_idx=self.vocab_size - 1)
        self.embeddings = self.embeddings.from_pretrained(torch.from_numpy(embeddings).float(), freeze=True)

        self.drop_out = nn.Dropout(drop_out_prob)
        self.rnn = nn.GRU(input_size=input_size,
                          hidden_size=hidden_size,
                          num_layers=1, batch_first=True, bidirectional=True)
        self.attn_context = nn.Linear(2 * hidden_size, 1)
        self.hidden_layer = nn.Linear(2 * hidden_size, hidden_size)
        self.output_layer = nn.Linear(hidden_size, classification_size)

    def forward(self, x, validate=False):
        x = self.embeddings(x)
        x = self.drop_out(x)
        x, _ = self.rnn(x)
        x = self.drop_out(x)

        energy = self.attn_context(x)
        probs = F.softmax(energy, dim=1)
        x = torch.bmm(x.transpose(1, 2), probs).squeeze(-1)
        x = self.hidden_layer(x)
        x = F.relu(x)
        x = self.output_layer(x)
        if not self.training:
            x = torch.sigmoid(x)
        return x

class Net:
    def __init__(self, epochs, train_loader, test_loader, validation_loader, learning_rate, model_type,
                 mode, min_delta, patience, num_of_topics=6, hidden_size=150, input_size=300,
                 topic_hidden_size=20, drop_out_prob=0.6, embeddings=None):
        if model_type == 'topic-attention':
            self.model = TopicAttention(num_of_topics=num_of_topics, hidden_size=hidden_size, input_size=input_size,
                                        classification_size=train_loader.get_num_of_classes(),
                                        topic_hidden_size=topic_hidden_size, drop_out_prob=drop_out_prob,
                                        sentence_length=train_loader.get_sentence_length(),
                                        embeddings=embeddings)
        elif model_type == 'vanilla-attention':
            self.model = VanillaAttention(hidden_size=hidden_size, input_size=input_size,
                                          classification_size=train_loader.get_num_of_classes(),
                                          drop_out_prob=drop_out_prob, embeddings=embeddings)
        elif model_type == 'topic-attention-without-squash':
            self.model = TopicAttentionWithoutSquash(num_of_topics=num_of_topics, hidden_size=hidden_size, input_size=input_size,
                                                     classification_size=train_loader.get_num_of_classes(),
                                                     topic_hidden_size=topic_hidden_size, drop_out_prob=drop_out_prob,
                                                     sentence_length=train_loader.get_sentence_length(), embeddings=embeddings)
        if torch.has_cudnn:
            self.model.cuda()

        self.model_type = model_type
        self.num_epochs = epochs
        # self.LossFunction = topkCE(k=k)
        # self.loss_function = nn.CrossEntropyLoss()
        # self.loss_function = nn.MSELoss()
        # self.validate_loss = nn.MSELoss()
        self.loss_function = nn.BCELoss()
        self.validate_loss = nn.BCELoss()

        self.optimizer = torch.optim.Adam(self.model.parameters(), lr=learning_rate, weight_decay=1e-4)
        # self.optimizer = torch.optim.Adadelta(self.model.parameters(), weight_decay=1e-5)
        self.early_stopping = EarlyStopping(mode, min_delta, patience)
        # lr_decay = lambda epoch: 1 / (1 + epoch * 0.2)
        # self.optimizer = torch.optim.SGD(self.model.parameters(), lr=learning_rate, momentum=0.9)
        # self.scheduler = torch.optim.lr_scheduler.LambdaLR(self.optimizer, lr_lambda=lr_decay)
        self.train_loader = train_loader
        self.test_loader = test_loader
        self.validation_loader = validation_loader

        self.validation_data_loader = dataloader.DataLoader(dataset=self.validation_loader,
                                                            batch_size=128,
                                                            shuffle=False,
                                                            num_workers=0,
                                                            collate_fn=collate_fn)
        self.train_data_loader = dataloader.DataLoader(dataset=self.train_loader,
                                                       batch_size=128,
                                                       shuffle=True,
                                                       num_workers=0,
                                                       collate_fn=collate_fn)
        self.test_data_loader = dataloader.DataLoader(dataset=self.test_loader,
                                                      batch_size=128,
                                                      shuffle=False,
                                                      num_workers=0,
                                                      collate_fn=collate_fn)

    def to_var(self, x):
        # x = torch.from_numpy(x).float()
        try:
            x = x.float()
        except AttributeError:
            x = torch.from_numpy(x).float()
        if torch.has_cudnn:
            x = Variable(x).cuda()
        else:
            x = Variable(x)
        return x

    def train(self, batch_size, validate=False):
        validate_loss = []
        train_loss = []

        for epoch in tqdm(range(self.num_epochs)):
            self.model.train()
            total_loss = []
            reg_total_loss = []
            for i, (data, labels, lengths) in enumerate(self.train_data_loader):
                reg_loss = 0.0
                data = self.to_var(data).long()
                labels = self.to_var(labels)
                self.optimizer.zero_grad()
                if self.model_type == 'topic-attention' or self.model_type == 'topic-attention-without-squash':
                    outputs, reg_loss = self.model(data, lengths)
                elif self.model_type == 'vanilla-attention':
                    outputs = self.model(data)
                # print(outputs)
                label_loss = self.loss_function(outputs, labels.squeeze(1))
                if self.model_type == 'topic-attention' or self.model_type == 'topic-attention-without-squash':
                    loss = label_loss + reg_loss
                elif self.model_type == 'vanilla-attention':
                    loss = label_loss

                loss.backward()
                # torch.nn.utils.clip_grad_norm_(self.model.parameters(), 1.0)
                self.optimizer.step()
                total_loss.append(float(label_loss))
                reg_total_loss.append(float(reg_loss))

            # print('Epoch [%d/%d], Avr Loss: %.4f, Avr Reg Loss: %.4f'
            #       % (epoch + 1, self.num_epochs, sum(total_loss) / len(total_loss), sum(reg_total_loss) / len(reg_total_loss)))

            if validate is True:
                valid_loss = self.validate()
                validate_loss.append(valid_loss)
                train_loss.append(float(sum(total_loss) / len(total_loss)))
            else:
                # valid_f1 = self.eval_validation_data()[0]
                valid_loss = self.validate()
                result = self.test()
                print('F1: %.4f, precision: %.4f, recall: %.4f' % (result[2], result[0], result[1]))
                print('train loss: ' + str(float(sum(total_loss) / len(total_loss))))
                print('validation loss: ' + str(valid_loss))
                # print(valid_loss)
                train_loss.append(float(sum(total_loss) / len(total_loss)))
                if self.early_stopping.step(valid_loss, deepcopy(self.model)) is True:
                    print('Early stopping at ' + str(epoch))
                    self.model = self.early_stopping.best_model
                    result = self.test()
                    print('F1: ' + str(result[0]))
                    print('P: ' + str(result[1]))
                    print('R: ' + str(result[2]))
                    # break
        if validate is True:
            return train_loss, validate_loss
        else:
            torch.save(self.model.state_dict(), './' + self.model_type)
            return result

    def test(self):
        self.model.eval()
        pred_labels = []
        true_labels = []
        for data, labels, lengths in self.test_data_loader:
            data = data.long()
            labels = labels.long()
            lengths = lengths.long()
            if torch.has_cudnn:
                data = data.cuda()
                labels = labels.cuda()
                lengths = lengths.cuda()
            # print(data)
            if self.model_type == 'topic-attention' or self.model_type == 'topic-attention-without-squash':
                outputs, _ = self.model(data, lengths)
            elif self.model_type == 'vanilla-attention':
                outputs = self.model(data)
            pred_labels += deepcopy(list(outputs.cpu().detach().numpy()))
            true_labels += deepcopy(list(labels.cpu().squeeze(1).detach().numpy()))
        pred_labels = list(pred_labels)
        # print(pred_labels)
        true_labels = list(true_labels)
        threshold = self.eval_validation_data()[1]
        print('Best threshold value: ' + str(threshold))
        return self.calculate_F1(pred_labels, true_labels, threshold)

    def eval_validation_data(self):
        self.model.eval()
        pred_labels = []
        true_labels = []
        best_result = [0.0, 0]
        for data, labels, lengths in self.validation_data_loader:
            data = data.long()
            labels = labels.long()
            lengths = lengths.long()
            if torch.has_cudnn:
                data = data.cuda()
                labels = labels.cuda()
                lengths = lengths.cuda()
            if self.model_type == 'topic-attention' or self.model_type == 'topic-attention-without-squash':
                outputs, _ = self.model(data, lengths)
            elif self.model_type == 'vanilla-attention':
                outputs = self.model(data)
            pred_labels += deepcopy(list(outputs.cpu().detach().numpy()))
            true_labels += deepcopy(list(labels.cpu().squeeze(1).detach().numpy()))
        for threshold in list(frange(0, 1, decimal.Decimal('0.01'))):
            precision, recall, f1 = self.calculate_F1(pred_labels, true_labels, threshold)
            if f1 > best_result[0]:
                best_result[0] = f1
                best_result[1] = threshold
        print('validation f1: ' + str(best_result[0]))
        return best_result

    def calculate_F1(self, pred_labels, true_labels, threshold):
        TP = 0
        FP = 0
        FN = 0
        for idx in range(len(pred_labels)):
            output = pred_labels[idx]
            label = true_labels[idx]
            for i in range(len(list(output))):
                if output[i] >= threshold:
                    if label[i] == 1:
                        TP += 1
                    else:
                        FP += 1
                else:
                    if label[i] == 1:
                        FN += 1
        try:
            precision = TP / (TP + FP)
            recall = TP / (TP + FN)
            f1 = 2 * (precision * recall) / (precision + recall)
        except ZeroDivisionError:
            precision = .0
            recall = .0
            f1 = .0
        # if len(pred_labels) == 642:
        #     print(str(TP) + ' ' + str(FP) + ' ' + str(FN))
        return precision, recall, f1

    def validate(self):
        self.model.eval()
        valid_loss = 0
        count = 0
        for data, labels, lengths in self.validation_data_loader:
            data = data.long()
            labels = labels.float()
            lengths = lengths.long()
            if torch.has_cudnn:
                data = data.cuda()
                labels = labels.cuda()
                lengths = lengths.cuda()
            if self.model_type == 'topic-attention' or self.model_type == 'topic-attention-without-squash':
                outputs, _ = self.model(data, lengths, validate=True)
            elif self.model_type == 'vanilla-attention':
                outputs = self.model(data)
            valid_loss += self.validate_loss(outputs, labels.squeeze(1)).item()
            count += 1
        valid_loss = float(valid_loss) / count
        return valid_loss

class EarlyStopping(object):
    def __init__(self, mode='min', min_delta=0, patience=10):
        self.mode = mode
        self.min_delta = min_delta
        self.patience = patience
        self.best = None
        self.num_bad_epochs = 0
        self.is_better = None
        self._init_is_better(mode, min_delta)

        if patience == 0:
            self.is_better = lambda a, b: True

    def step(self, metrics, model):
        if self.best is None:
            self.best = metrics
            self.best_model = model
            return False

        if np.isnan(metrics):
            return True

        if self.is_better(metrics, self.best):
            self.num_bad_epochs = 0
            self.best = metrics
            self.best_model = model
        else:
            self.num_bad_epochs += 1

        if self.num_bad_epochs >= self.patience:
            return True

        return False

    def _init_is_better(self, mode, min_delta):
        if mode not in {'min', 'max'}:
            raise ValueError('mode ' + mode + ' is unknown!')
        if mode == 'min':
            self.is_better = lambda a, best: a < best - min_delta
        if mode == 'max':
            self.is_better = lambda a, best: a > best + min_delta

    def return_best_model(self):
        return self.best_model

def collate_fn(data):
    sentence_lengths = [data[i][2] + 1 for i in range(len(data))]
    sorted_indices = np.argsort(sentence_lengths)[::-1]

    max_sentence_length = max(sentence_lengths)

    sentence_rep = np.array([data[idx][0][:max_sentence_length] for idx in sorted_indices])
    # try:
    label = np.array([data[i][1] for i in sorted_indices])
    sentence_lengths = np.array([sentence_lengths[idx] for idx in sorted_indices])

    sentence_rep = np.stack(sentence_rep)
    label = np.stack(label)
    sentence_lengths = np.stack(np.array(sentence_lengths))

    return torch.from_numpy(sentence_rep), torch.from_numpy(label), torch.from_numpy(sentence_lengths)


from django.contrib.gis.gdal.geometries import MultiPolygon as GdalMultiPolygon
from django.contrib.gis.gdal.geometries import Point as GdalPoint
from django.contrib.gis.gdal.geometries import Polygon as GdalPolygon
from django.contrib.gis.geos import MultiPolygon, Point, Polygon
from django.test import TestCase

from axis_order_cache.utils import switch_axis_order

class TestUtils(TestCase):

    def setUp(self):
        self.polygon = "SRID=4326;POLYGON((0 1,0 50,51 50,50 0,0 1))"
        self.polygon_expected = "SRID=4326;POLYGON((1 0,50 0,50 51,0 50,1 0))"

        self.multipolygon = "SRID=4326;MULTIPOLYGON(((0 1,0 50,51 50,50 0,0 1)),((0 1,0 50,51 50,50 0,0 1)))"
        self.multipolygon_expected = "SRID=4326;MULTIPOLYGON(((1 0,50 0,50 51,0 50,1 0)),((1 0,50 0,50 51,0 50,1 0)))"

        self.point = "SRID=4326;POINT(0 1)"
        self.point_expected = "SRID=4326;POINT(1 0)"

    def test_switch_axis_order_with_geos_polygon(self):
        polygon = Polygon.from_ewkt(self.polygon)
        expected = Polygon.from_ewkt(self.polygon_expected)
        result = switch_axis_order(geometry=polygon)

        self.assertEqual(expected, result)

    def test_switch_axis_order_with_gdal_polygon(self):
        polygon = GdalPolygon(self.polygon)
        expected = GdalPolygon(self.polygon_expected)
        result = switch_axis_order(geometry=polygon)

        self.assertEqual(expected, result)

    def test_switch_axis_order_with_geos_multipolygon(self):
        polygon = MultiPolygon.from_ewkt(self.multipolygon)
        expected = MultiPolygon.from_ewkt(self.multipolygon_expected)
        result = switch_axis_order(geometry=polygon)

        self.assertEqual(expected, result)

    def test_switch_axis_order_with_geos_multipolygon(self):
        polygon = GdalMultiPolygon(self.multipolygon)
        expected = GdalMultiPolygon(self.multipolygon_expected)
        result = switch_axis_order(geometry=polygon)

        self.assertEqual(expected, result)

    def test_switch_axis_order_with_geos_point(self):
        polygon = Point.from_ewkt(self.point)
        expected = Point.from_ewkt(self.point_expected)
        result = switch_axis_order(geometry=polygon)

        self.assertEqual(expected, result)

    def test_switch_axis_order_with_gdal_point(self):
        polygon = GdalPoint(self.point)
        expected = GdalPoint(self.point_expected)
        result = switch_axis_order(geometry=polygon)

        self.assertEqual(expected, result)