import dash_html_components as html
import dash_bootstrap_components as dbc

# needed only if running this as a single page app
#external_stylesheets = [dbc.themes.LUX]

#app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

# change to app.layout if running as single page app instead
layout = html.Div([
    dbc.Container([
        dbc.Row([
            dbc.Col(html.H1("ABOUT THIS PROJECT", className="text-center")
                    , className="mb-5 mt-5")
        ]),
        dbc.Row([
            dbc.Col(html.H5(children='This application is part of India PropTech Hackathon organised by Hacker Earth.'
                                     )
                    , className="mb-4")
            ]),

        dbc.Row([
            dbc.Col(html.H5(children='This app is based on using Neural network and machine learning to make a model '
                                     'that can be used to analyze residential market and attract middle class individual of India '
                                     'by value addition. This app shows proposed model for such approach. '
                                     'Description and approach of this project can be understood from chart below.')
                    , className="mb-5")
        ]),

        dbc.Row([
        html.Img(src="/assets/project.jpg"),
        html.Hr(),

               html.Hr(),
               html.H5("If you find any bug or abnormalities in this project, please report me on hamzahshabbir7@gmail.com"
                      ),
                      html.A("Special Thanks to “NEC Corporation India” and “Mitsubishi Corporation India” for providing this oppurtunity of working in this project",
                             href="https://www.hackerearth.com/"),

    ])

])
])
# needed only if running this as a single page app
# if __name__ == '__main__':
# app.run_server(host='127.0.0.1', debug=True)


t = int(input())
for _ in range(t):
	n = int(input())
	S = list(map(int, input().split()))
	if n == 1:
		print(-1)
		continue
	if n == 2:
		print(S[0]^S[1])
		continue
	S.sort()
	ans = -1
	for k in range(1, 1024):
		R = sorted([x^k for x in S])
		ans = k
		for i in range(n):
			if S[i] != R[i]:
				ans = -1
				break
		if ans > 0: break
	print(ans)


# 1. set, 리스트만으로
from itertools import combinations

def solution(relation):
    col_n = len(relation[0])
    entity_n = len(relation)

    all = []
    for n in range(1, col_n+1):
        for tmp in combinations(range(col_n), n):
            all.append(tmp)

    # 유일성
    answer = []
    for case in all:
        tmp_set = set()
        for entity in relation:
            tmp = []
            for i in case:
                tmp.append(entity[i])
            tmp = tuple(tmp)
            tmp_set.add(tmp) # tmp_set에 중복은 한번만 더해짐
        if len(tmp_set) == entity_n:
            answer.append(case)

    # 최소성
    new_answer = set(answer)
    for i in range(len(answer)):
        for j in range(i+1, len(answer)):
            if len(answer[i]) == len( set(answer[i]) & set(answer[j])):
                new_answer.discard(answer[j])

    return len(new_answer)

# 2. 비트 마스킹
# 2-1) 엔티티 구분용 - 튜플로
def solution(relation):
    col_n = len(relation[0])
    entity_n = len(relation)

    all = list(range(1, 1 << col_n))

    answer = []
    # 1) 전체 케이스
    for case in all:
        # 2) 유일성 검사
        tmp_set = set()
        for entity in relation:  # 한 엔티티의 해당 키조합
            tmp = []
            for i in range(col_n):
                if case & (1 << i):
                    tmp.append(entity[i])
            tmp = tuple(tmp)
            tmp_set.add(tmp)

        # 3) 최소성 검사
        if len(tmp_set) == entity_n:
            flag = True
            for num in answer:
                if (num & case) == num:
                    flag = False
                    break
            if flag:
                answer.append(case)

    return len(answer)

# 2-2) 엔티티 구분용 str
def solution(relation):
    answer = []
    col_n = len(relation[0])
    entity_n = len(relation)

    all = list(range(1, 1 << col_n))
    for case in all: #모든 조합에 대한 검사
            tmp_set = set()
            for e in range(entity_n): # entity 마다
                tmp = ""
                for col in range(col_n): # col 마다
                    if case & (1<<col):
                        tmp += str(relation[e][col]) # 엔티티 구분용

                tmp_set.add(tmp)

                if len(tmp_set) == entity_n:
                    answer.append(case)

    answer_set = set(answer)
    for i in answer:
        for j in range(i, len(answer)):
            if i & answer[j] == i:
                answer_set.discard(answer[j])

    return len(answer_set)


import random

import matplotlib.pyplot as plt

class Point2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y

class Line2D:
    def __init__(self, p1, p2):
        self.p1 = p1
        self.p2 = p2

def check_turn_right(p1, p2, p3):
    vec1 = (p2.x - p1.x, p2.y - p1.y)
    vec2 = (p3.x - p2.x, p3.y - p2.y)
    return vec1[0] * vec2[1] < vec2[0] * vec1[1]

def slow_convex_hull(p):
    e = []
    for i in range(len(p)):
        for j in range(len(p)):
            if i != j:
                valid = True
                for k in range(len(p)):
                    if k != i and k != j:
                        if check_turn_right(p[i], p[j], p[k]):
                            valid = False
                            break
                if valid:
                    e.append(Line2D(p[i], p[j]))
    l = []
    for edge in e:
        l.append(edge.p1)
    l.append(e[-1].p2)
    return l

# 设置随机种子
random.seed(42)

# 生成随机点集
num_points = 30
points = [Point2D(random.uniform(0, 10), random.uniform(0, 10)) for _ in range(num_points)]

points[1].x = 0.26535969683863625+10
points[1].y = 1.988376506866485 + 2.0

points[8].x = 0.26535969683863625+5
points[8].y = 1.988376506866485 + 4.0

# 计算凸包
hull = slow_convex_hull(points)

# 提取点集的坐标
x = [point.x for point in points]
y = [point.y for point in points]

# 提取凸包的坐标
hull_x = [point.x for point in hull]
hull_y = [point.y for point in hull]

# 绘制点集和凸包
plt.scatter(x, y, color='blue', label='Points')
plt.plot(hull_x, hull_y, color='red', label='Convex Hull')
plt.xlabel('x')
plt.ylabel('y')
plt.title('Points and Convex Hull')
plt.legend()
plt.show()


from django.urls import path
from . import views

# app_name 변수는 주소에 라벨링을 합니다.
app_name="polls"

urlpatterns = [
        path('main', views.index, name='index'),
        path('test', views.test, name = 'test'),
        path('get', views.get, name ='get'),
        path('getform', views.getform, name = 'getform'),
        path('name', views.get_name, name = 'name'),
        path('nameform', views.nameform, name = 'nameform'),
        path('<int:temp>/ctof', views.temp),
        path('<int:height>/<int:weight>/bmi', views.bmi),
        path('<int:number>/content', views.get_number),
        # int가 들어왔을 때 number라는 변수에 저장
        # number라는 변수는 get_number라는 함수로 전달
        # 127.0.0.1:8000/polls/50
        #                         number
        path('<int:age>/check', views.get_adult_result),
        path('<int:num1>/<int:num2>/two', views.get_two),
        path('<str:name>/name', views.get_name_2),
        path('<str:money1>/<str:money2>/exchange', views.money),
        path('qtest', views.qtest),
        path('<int:price>/price', views.bus_price),
        path('<int:id>', views.detail, name='detail'),
        path('<int:question_id>/results/', views.results, name='results'),
        path('<int:question_id>/vote/', views.vote, name='vote'),

    ]
# http://www.127.0.0.1:8080/main


from httpx import AsyncClient
from pytest import mark
from csv import writer, QUOTE_NONNUMERIC
from io import StringIO
from datetime import datetime
from schemas_mock import *
from app.services.auth import AuthHandler

auth = AuthHandler()

@mark.anyio
async def test_healthcheck(client: AsyncClient, refresh_db):
    response = await client.get('/')
    assert response.status_code == 200
    assert response.json() == {'status': 'Working from CI/CD GitHub actions by Samandar Khodjiyev for Meduzzen!'}

@mark.anyio
async def test_worker_create(client: AsyncClient):
    await client.post('/users/add', json=user1.dict())
    await client.post('/users/add', json=user2.dict())
    await client.post('/users/add', json=user3.dict())
    response_location = await client.post('/locations/add',json=location1.dict())
    response_location.status_code == 200
    assert response_location.json() == db_location1
    token = await client.post('/users/login', json=login_user1.dict())
    token = token.json()
    admin_response = await client.post("/users/add_admin?id=1", headers={'Token':token})
    assert admin_response.status_code == 200
    response = await client.post('/workers/add', json=worker1.dict(), headers={'Token':token})
    assert response.json() == db_worker1
    assert response.status_code == 200

@mark.anyio
async def test_work_get_auth(client: AsyncClient):
    response = await client.get('/workers/all')
    assert response.status_code == 200
    assert response.json() == [db_worker1.dict()]

@mark.anyio
async def  test_worker_get_list(client: AsyncClient):
    token = await client.post('/users/login', json=login_user1.dict())
    token = token.json()
    response = await client.delete('workers/delete?worker_id=1&id=1',headers={'Token':token})
    assert response.status_code == 200
    assert response.json()['detail'] == 'Worker with id 1 is successfully deleted'


"""
This is a debugging test for FuncPipe
Local environment used
"""
import sys
sys.path.append('../')

import torch
import torch.optim as optim
import torch.nn.functional as F
from torch.utils.data import DataLoader

from funcpipe import FuncPipe
from funcpipe.platforms import Platform
from funcpipe.debugger import Logger
from funcpipe.timeline import Timeline

from pipetests.models.bert.model.language_model import make_bert_nsp
from pipetests.models.bert.dataset import BERTDataset, WordVocab

"""
User-defined model
"""

def test_import(params):
    print("Import and call success!")
    print(params)

# entrance function
def handler(event, context=None):
    # event = eval(event)
    Logger.use_logger(Logger.NATIVE, Logger.DEBUG, "rank%d" % int(event["rank"]))
    Logger.debug("Start")

    # training configuration
    my_rank = int(event["rank"])
    dataset_size = int(event["dataset_size"])
    batch_size = int(event["batch_size"])
    epoches = int(event["epoch_num"])
    learning_rate = float(event["learning_rate"])
    platform_type = event["platform"]

    # choose the serverless platform
    Platform.use(platform_type)

    # settings
    vocab_path = "./models/bert/dataset/vocab.small"
    train_dataset_path = "./models/bert/dataset/corpus.small"
    # test_dataset_path = ""
    max_seq_len = 64
    on_memory = False

    # loading dataset
    Logger.debug("Loading Vocab: ".format(vocab_path))
    vocab = WordVocab.load_vocab(vocab_path)
    Logger.debug("Vocab Size: ".format(len(vocab)))
    Logger.debug("Loading Train Dataset: ".format(train_dataset_path))
    train_dataset = BERTDataset(train_dataset_path, vocab, seq_len=max_seq_len,
                                corpus_lines=dataset_size, on_memory=on_memory)
    # print("Loading Test Dataset: ", test_dataset_path)
    # test_dataset = BERTDataset(test_dataset_path, vocab, seq_len=max_seq_len, on_memory=on_memory)
    Logger.debug("Creating Dataloader")
    Logger.debug("Batchsize:{}".format(batch_size))
    train_data_loader = DataLoader(train_dataset, batch_size=batch_size)
    # test_data_loader = DataLoader(test_dataset, batch_size=batch_size)
    profiling_loader = DataLoader(train_dataset, batch_size=1)
    profiling_sample = None
    for bid, batch in enumerate(profiling_loader):
        profiling_sample = (batch["bert_input"], batch["segment_label"])
        break
    del profiling_loader

    Logger.debug("Building BERT model")
    vocab_size = len(vocab)
    bert_size = (4, 256, 4)  # bert-small: layer-4 hidden-256 attn_heads-4
    model = make_bert_nsp(vocab_size, n_layers=bert_size[0], hidden=bert_size[1], attn_heads=bert_size[2])
    optimizer = optim.Adam
    loss_func = torch.nn.NLLLoss(ignore_index=0)
    model = FuncPipe(model, loss_func=loss_func, optim_class=optimizer, learning_rate=learning_rate,
                     batch_size=batch_size)
    Logger.debug("Model built")

    # partition plan
    # we manually specify the partition for test
    # multiple stages
    def bert_partition():
        model.planner.partition_plan = [2,1,1,2]
        model.planner.tensor_parallelism = [1 for i in model.planner.partition_plan]
        model.planner.data_parallelism = [1 for i in model.planner.partition_plan]
        model.planner.micro_batchsize = 1

    bert_partition()

    # todo: some of the information can be deprecated since they have already been passed to the model
    model.init(event, profiling_sample = profiling_sample)

    #try:
    Timeline.start("Entire training process")
    for epoch_id in range(epoches):
        for batch_id, batch in enumerate(train_data_loader):
            Logger.info("Rank: %d   Epoch: %d   Batch: %d" % (my_rank, epoch_id, batch_id))
            model.pipeline_train((batch["bert_input"], batch["segment_label"]), batch["is_next"])
            #print(batch["bert_input"].shape)
            #print(batch["segment_label"].dtype)
            #exit()
    Timeline.end("Entire training process")
    #except Exception as e:
    #    Logger.info('\n\n\n' + str(e) + '\n\n')

    model.end()

# direct trigger
if __name__ == "__main__":
    params = {}
    # the starting rank must be 0
    params["rank"] = 0
    params["dataset_size"] = 4
    params["batch_size"] = 4
    params["epoch_num"] = 1
    params["learning_rate"] = 0.001
    params["platform"] = "local"
    params["function_name"] = "pipetests.test_local_launch_bert.handler"

    handler(params)


class Properties(object):
    def _update_properties(self, src=None):
        if hasattr(self, '_properties'):
            for idx, prop in enumerate(self._properties):
                target_prop = prop
                if hasattr(self, '_renamed_properties'):
                    if self._renamed_properties:
                        target_prop = self._renamed_properties[idx]
                setattr(self, target_prop, None)
                if hasattr(src, prop):
                    setattr(self, target_prop, getattr(src, prop))

    def __bool__(self):
        return self.id is not None

    def _dump(self):
        for prop in dir(self):
            if prop.startswith('_'):
                continue
            print(prop, getattr(self, prop))


import numpy as np
import matplotlib.pyplot as plt
import PIL.Image as Image
import gymnasium
import random
import pygame
from gymnasium import Env, spaces
import time

from .agent import Agent
from .bullet import Bullet
from .enemy import Enemy

class EvaderEnv_v1(Env):
    metadata = {"render_modes": ["human", "rgb_array"], "render_fps": 4}

    def __init__(self, render_mode=None, **kwargs) -> None:
        super().__init__()

        # dimensions of the grid
        self.width = kwargs.get('width',600)
        self.height = kwargs.get('height',600)

        assert render_mode is None or render_mode in self.metadata["render_modes"]
        self.render_mode = render_mode

        map_bounds = (0,0,self.width,self.height)
        self.map_bounds = map_bounds

        self.enemies = [Enemy(map_bounds,'x'),Enemy(map_bounds,'+'),Enemy(map_bounds,'.')]
        self.agents = [Agent(map_bounds)]
        self.bullets = []

        self.window_size = (self.width,self.height)
        self.observation_shape = (self.width, self.height, 3)
        self.observation_space = spaces.Box(low = 0,
                                            high = 255,
                                            shape= self.observation_shape,
                                            dtype = np.int8)

        self.action_space = spaces.Discrete(9)
        self.window = None
        self.clock = None

        self.agent_dead = False

    def _get_obs(self):
        canvas = pygame.Surface(self.window_size)
        canvas.fill((255, 255, 255))

        # Now we draw the agent

        for p in self.agents:
            p.draw(canvas)

        for b in self.bullets:
            b.draw(canvas)

        for e in self.enemies:
            e.draw(canvas)

        return np.transpose(
                np.array(pygame.surfarray.pixels3d(canvas)), axes=(1, 0, 2)
            )

        pass

    def _get_reward(self):
        return 0 if self.agent_dead else 1

    def _get_info(self):
        b5 = self._choose_nearest_bullets()
        agent = self.agents[0]
        return {
            "agent": agent,
            "bullets": b5
        }

    def _update_state(self,action):

        for p in self.agents:
            p.move(action)

        target = [self.agents[0].x, self.agents[0].y]

        for e in self.enemies:
            e.move()
            if(e.shoot_wait == e.shoot_frequency):
                self.bullets.extend(e.shoot(target))

        b2 = []
        for b in self.bullets:
            b.move()
            if(b.check_bounds()):
                b2.append(b)

        self.bullets = b2

        pass

    def _choose_nearest_bullets(self):
        b5 = []
        for b in self.bullets:
            b.color = (255,0,0)
        agent = self.agents[0]
        for _ in range(1):
            best = None
            best_d = self.width**2
            for b in self.bullets:
                d = agent.calc_distance_to([b.x,b.y])
                if( d < best_d and not b in b5):
                    best = b
                    best_d = d

            if best:
                best.color = (255,0,255)
                b5.append(best)

        return b5

    def step(self, action):
        self._update_state(action)

        agent = self.agents[0]

        for b in self.bullets:
            if(agent.check_collision(b)):
                self.agent_dead = True
                break

        terminated = self.agent_dead
        truncated = False
        return self._get_obs(), self._get_reward(), terminated, truncated, self._get_info()

    def reset(self, seed=None, options=None):
        super().reset(seed=seed)

        self.agent_dead = False
        self.enemies = [Enemy(self.map_bounds,'x'),Enemy(self.map_bounds,'+'),Enemy(self.map_bounds,'.')]
        self.agents = [Agent(self.map_bounds)]
        self.bullets = []

        return self._get_obs(), self._get_info()

    def render(self):
        if self.render_mode == "rgb_array":
            return self._render_frame()
        else:
            self._render_frame()

    def _render_frame(self):
        if self.window is None and self.render_mode == "human":
            pygame.init()
            pygame.display.init()
            self.window = pygame.display.set_mode(self.window_size)
        if self.clock is None and self.render_mode == "human":
            self.clock = pygame.time.Clock()

        canvas = pygame.Surface(self.window_size)
        canvas.fill((255, 255, 255))

        # Now we draw the agent

        for p in self.agents:
            p.draw(canvas)

        for b in self.bullets:
            b.draw(canvas)

        for e in self.enemies:
            e.draw(canvas)

        if self.render_mode == "human":
            # The following line copies our drawings from `canvas` to the visible window
            self.window.blit(canvas, canvas.get_rect())
            pygame.event.pump()
            pygame.display.update()

            # We need to ensure that human-rendering occurs at the predefined framerate.
            # The following line will automatically add a delay to keep the framerate stable.
            self.clock.tick(self.metadata["render_fps"])
        else:  # rgb_array
            return np.transpose(
                np.array(pygame.surfarray.pixels3d(canvas)), axes=(1, 0, 2)
            )
            # return np.array(pygame.surfarray.pixels3d(canvas))


from __future__ import absolute_import
from __future__ import print_function
from __future__ import division

import sys
import os

import time

import numpy as np

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

from ..smplifyx.utils_mics.mesh_viewer import MeshViewer
from ..smplifyx import utils_mics
from ..smplifyx.utils_mics import misc_utils
from ..smplifyx import fitting as single_view_fitting
from ..constants import SKELETON_IDX, HAND_IDX
# from .fitting_video_orientation_temporal_loss import SMPLifyBodyOrientLoss, MultiViewSMPLifyBodyOrientLoss
from loguru import logger

from .body_pose_utils import BODY_POSE_TO_OP, FEET_IN_SMPL, FEET_IN_OP
# from .fitting_video_persubject_orientation_temporal import *
# ! SMPLifyX: init in OpenGL CS.

class SMPLifyLoss3D(single_view_fitting.SMPLifyLoss):

    def __init__(self, search_tree=None,
                 pen_distance=None, tri_filtering_module=None,
                 rho=100,
                 body_pose_prior=None,
                 shape_prior=None,
                 angle_prior=None,
                 dtype=torch.float32,
                 data_weight=1.0,
                 body_pose_weight=0.0,
                 shape_weight=0.0,
                 bending_prior_weight=0.0,
                 hand_prior_weight=0.0,
                 expr_prior_weight=0.0, jaw_prior_weight=0.0,
                 reduction='sum',
                 **kwargs):

        super(SMPLifyLoss3D, self).__init__(search_tree=search_tree,
                 pen_distance=pen_distance, tri_filtering_module=tri_filtering_module,
                 rho=rho,
                 body_pose_prior=body_pose_prior,
                 shape_prior=shape_prior,
                 angle_prior=angle_prior,
                 dtype=torch.float32,
                 data_weight=data_weight,
                 body_pose_weight=body_pose_weight,
                 shape_weight=shape_weight,
                 bending_prior_weight=bending_prior_weight,
                 reduction='sum',
                 **kwargs)
        # import pdb;pdb.set_trace()
        self.ground_plane_support = kwargs['ground_plane_support']
        self.gp_support_loss_weight = kwargs['gp_support_loss_weight_init']

    def forward(self, body_model_output, gt_joints,
                body_model_faces,
                use_vposer=False, pose_embedding=None,
                scene_model=None,
                **kwargs):

        batch_size = gt_joints.shape[0]
        # import pdb;pdb.set_trace()
        # Calculate the distance of the projected joints from
        # the ground truth 2D detections

        #### OLD used in CVPR submission.
        # joint_diff = (gt_joints - body_model_output.joints[:,:15,:]) ** 2 / batch_size
        # joint_loss = (torch.sum(joint_diff) * self.data_weight ** 2) / batch_size
        #### end of OLD used in CVPR submission.

        logger.info('run 3D joint loss.')
        # delete face contour in original name
        joint_len = min(gt_joints.shape[1], body_model_output.joints.shape[1])

        print('joints shape', gt_joints.shape[-1])
        # TODO: check the idx.
        if gt_joints.shape[-1] == 3:
            joint_diff = (gt_joints[:, :joint_len, :] - body_model_output.joints[:,:joint_len,:]) ** 2
        elif gt_joints.shape[-1] == 4:
            joint_diff = (gt_joints[:, :joint_len, :-1] - body_model_output.joints[:,:joint_len,:]) ** 2
            joint_diff *= gt_joints[:, :joint_len, -1:]

        joint_loss = (torch.sum(joint_diff) * self.data_weight ** 2)

        # visualizaion: use updated translated 3D joint in Perspective Camera.
        # Render Joint is still not accurate with the original joints.
        if False:
            import pdb;pdb.set_trace()
            cameras = kwargs['cameras']

            import cv2
            img_path  = '//ps/scratch/hyi/HCI_dataset/20210109_capture/C0034/hci_test/000379.jpg'
            cv2_img = cv2.imread(img_path)
            cam = cameras[0]
            proj_kpts = cam(gt_joints)
            ori_proj = proj_kpts.squeeze(0).detach().cpu().numpy().T
            for j in range(ori_proj.shape[1]):
                print(ori_proj[0, j], ori_proj[1, j])
                cv2_img = cv2.circle(cv2_img, (int(ori_proj[0, j]), int(ori_proj[1, j])), \
                            radius=5, color=(255, 0, 0), thickness=4)

            proj_kpts = cam(body_model_output.joints[:,:15,:])
            ori_proj = proj_kpts.squeeze(0).detach().cpu().numpy().T
            for j in range(ori_proj.shape[1]):
                print(ori_proj[0, j], ori_proj[1, j])
                cv2_img = cv2.circle(cv2_img, (int(ori_proj[0, j]), int(ori_proj[1, j])), \
                            radius=5, color=(0, 0, 255), thickness=4)

            # cv2.imshow("2d joint", cv2_img)
            cv2.imwrite("render.png", cv2_img)
            cv2.waitKey(5)
            import pdb;pdb.set_trace()

        # Calculate the loss from the Pose prior
        if use_vposer:
            pprior_loss = (pose_embedding.pow(2).sum() *
                           self.body_pose_weight ** 2)
        else:
            pprior_loss = torch.sum(self.body_pose_prior(
                body_model_output.body_pose,
                body_model_output.betas)) * self.body_pose_weight ** 2
        pprior_loss = pprior_loss / batch_size

        shape_loss = torch.sum(self.shape_prior(
            body_model_output.betas)) * self.shape_weight ** 2 / batch_size

        # Calculate the prior over the joint rotations. This a heuristic used
        # to prevent extreme rotation of the elbows and knees
        body_pose = body_model_output.full_pose[:, 3:66]
        angle_prior_loss = torch.sum(
            self.angle_prior(body_pose)) * self.bending_prior_weight  / batch_size

        total_loss = (joint_loss + pprior_loss + shape_loss + angle_prior_loss)

        message = f'Init joint|weight: {joint_loss}|{self.data_weight.item():.3f},  \
            shape_loss: {shape_loss}|{self.shape_weight}, \
            pprior_loss: {pprior_loss}|{self.body_pose_weight}, \
            angle_prior: {angle_prior_loss}|{self.bending_prior_weight:.3f}, \
            total_loss: {total_loss}'

        # Delete gp plane
        # if self.ground_plane_support:
        #     ## ground plane support loss: human should above the ground plane.
        #     assert scene_model is not None
        #     gt_ground_plane = scene_model.ground_plane
        #     # batch average
        #     # gp_support_loss = F.smooth_l1_loss(torch.max(body_model_output.vertices[:, :, 1]), gt_ground_plane)
        #     # gp_support_loss = gp_support_loss * self.gp_support_loss_weight
        #     gp_support_loss = F.relu(torch.max(body_model_output.vertices[:, :, 1], -1)[0] - gt_ground_plane).mean()
        #     total_loss = total_loss + gp_support_loss * self.gp_support_loss_weight
        #     message += f'gp: {gp_support_loss}'

        logger.info(message)

        tb_debug = kwargs['tb_debug']
        if tb_debug:
            debug_loss_dict = {
                'loss_joint': joint_loss,
                'loss_pprior': pprior_loss,
                'loss_shape': shape_loss,
                'loss_angle_prior': angle_prior_loss,
                'loss_total_SMPLifyLoss3D': total_loss,
            }
            # if self.ground_plane_support:
            #     debug_loss_dict['loss_gp_support_body'] = gp_support_loss
            return total_loss, debug_loss_dict
        else:
            return total_loss

####################
### body skeletons add hands.
####################
class SMPLifyLoss3D_withHands(single_view_fitting.SMPLifyLoss):
    def __init__(self, search_tree=None,
                 pen_distance=None, tri_filtering_module=None,
                 rho=100,
                 body_pose_prior=None,
                 shape_prior=None,
                 angle_prior=None,
                 dtype=torch.float32,
                 data_weight=1.0,
                 body_pose_weight=0.0,
                 shape_weight=0.0,
                 bending_prior_weight=0.0,
                 hand_prior_weight=0.0,
                 reduction='sum',
                 hand_joints_weights=1.0,
                 expr_prior_weight=0.0,
                 jaw_prior_weight=0.0,
                 hand_idx=None,
                 **kwargs):

        super(SMPLifyLoss3D_withHands, self).__init__(search_tree=search_tree,
                 pen_distance=pen_distance, tri_filtering_module=tri_filtering_module,
                 rho=rho,
                 body_pose_prior=body_pose_prior,
                 shape_prior=shape_prior,
                 angle_prior=angle_prior,
                 dtype=torch.float32,
                 data_weight=data_weight,
                 body_pose_weight=body_pose_weight,
                 shape_weight=shape_weight,
                 bending_prior_weight=bending_prior_weight,
                 hand_prior_weight=hand_prior_weight,
                 expr_prior_weight=expr_prior_weight,
                 jaw_prior_weight=jaw_prior_weight,
                 reduction='sum',
                 **kwargs)
        self.hand_joints_weights = hand_joints_weights

        # self.hand_idx_map=hand_idx
        # import pdb;pdb.set_trace()
    def forward(self, body_model_output, gt_joints,
                body_model_faces,
                use_vposer=False, pose_embedding=None,
                scene_model=None,
                **kwargs):

        batch_size = gt_joints.shape[0]
        # import pdb;pdb.set_trace()
        # Calculate the distance of the projected joints from
        # the ground truth 2D detections

        #### OLD used in CVPR submission.
        # joint_diff = (gt_joints - body_model_output.joints[:,:15,:]) ** 2 / batch_size
        # joint_loss = (torch.sum(joint_diff) * self.data_weight ** 2) / batch_size
        #### end of OLD used in CVPR submission.

        logger.info('run 3D joint loss.')
        # delete face contour in original name
        joint_len = min(gt_joints.shape[1], body_model_output.joints.shape[1])

        print('joints shape', gt_joints.shape[-1])
        # TODO: check the idx.
        if gt_joints.shape[-1] == 3:
            joint_diff = (gt_joints[:, :joint_len, :] - body_model_output.joints[:,:joint_len,:]) ** 2
        elif gt_joints.shape[-1] == 4:
            joint_diff = (gt_joints[:, :joint_len, :-1] - body_model_output.joints[:,:joint_len,:]) ** 2
            joint_diff *= gt_joints[:, :joint_len, -1:]

        # TODO: check the data_weight.
        # import pdb;pdb.set_trace()
        ske_loss = (torch.sum(joint_diff[:, SKELETON_IDX]) * self.data_weight ** 2)
        hand_loss = (torch.sum(joint_diff[:, HAND_IDX]) * self.hand_joints_weights ** 2)
        joint_loss = ske_loss + hand_loss
        # Calculate the loss from the Pose prior
        if use_vposer:
            pprior_loss = (pose_embedding.pow(2).sum() *
                           self.body_pose_weight ** 2)
        else:
            pprior_loss = torch.sum(self.body_pose_prior(
                body_model_output.body_pose,
                body_model_output.betas)) * self.body_pose_weight ** 2

        pprior_loss = pprior_loss / batch_size

        shape_loss = torch.sum(self.shape_prior(
            body_model_output.betas)) * self.shape_weight ** 2 / batch_size

        # Calculate the prior over the joint rotations. This a heuristic used
        # to prevent extreme rotation of the elbows and knees
        body_pose = body_model_output.full_pose[:, 3:66]
        angle_prior_loss = torch.sum(
            self.angle_prior(body_pose)) * self.bending_prior_weight  / batch_size

        # Apply the prior on the pose space of the hand
        # import pdb;pdb.set_trace()
        left_hand_prior_loss, right_hand_prior_loss = 0.0, 0.0
        if self.use_hands and self.left_hand_prior is not None:
            left_hand_prior_loss = torch.sum(
                self.left_hand_prior(
                    body_model_output.left_hand_pose)) * \
                self.hand_prior_weight ** 2 / batch_size

        if self.use_hands and self.right_hand_prior is not None:
            right_hand_prior_loss = torch.sum(
                self.right_hand_prior(
                    body_model_output.right_hand_pose)) * \
                self.hand_prior_weight ** 2 / batch_size

        total_loss = (joint_loss + pprior_loss + shape_loss + angle_prior_loss \
                + right_hand_prior_loss + left_hand_prior_loss)

        message = f'skeleton: {ske_loss}, hand: {hand_loss}, \
            joint|weight: {joint_loss}|{self.data_weight.item():.3f},  \
            shape_loss: {shape_loss}|{self.shape_weight}, \
            pprior_loss: {pprior_loss}|{self.body_pose_weight}, \
            angle_prior: {angle_prior_loss}|{self.bending_prior_weight:.3f}, \
            left_hand_prior: {left_hand_prior_loss}|{self.hand_prior_weight:.3f}, \
            total_loss: {total_loss}'

        # Delete gp plane
        # if self.ground_plane_support:
        #     ## ground plane support loss: human should above the ground plane.
        #     assert scene_model is not None
        #     gt_ground_plane = scene_model.ground_plane
        #     # batch average
        #     # gp_support_loss = F.smooth_l1_loss(torch.max(body_model_output.vertices[:, :, 1]), gt_ground_plane)
        #     # gp_support_loss = gp_support_loss * self.gp_support_loss_weight
        #     gp_support_loss = F.relu(torch.max(body_model_output.vertices[:, :, 1], -1)[0] - gt_ground_plane).mean()
        #     total_loss = total_loss + gp_support_loss * self.gp_support_loss_weight
        #     message += f'gp: {gp_support_loss}'

        logger.info(message)

        tb_debug = kwargs['tb_debug']
        if tb_debug:
            debug_loss_dict = {
                'loss_joint': joint_loss,
                'loss_pprior': pprior_loss,
                'loss_shape': shape_loss,
                'loss_angle_prior': angle_prior_loss,
                'loss_total_SMPLifyLoss3D': total_loss,
                'loss_left_hand_prior': left_hand_prior_loss,
                'loss_right_hand_prior': right_hand_prior_loss,
            }
            # if self.ground_plane_support:
            #     debug_loss_dict['loss_gp_support_body'] = gp_support_loss
            return total_loss, debug_loss_dict
        else:
            return total_loss

################################
### based on 2D observation.
################################
# TODO: add online POSA.
class MultiViewSMPLifyLoss(single_view_fitting.SMPLifyLoss):
    def __init__(self, **kwargs):

        super(MultiViewSMPLifyLoss, self).__init__(**kwargs)
        # import pdb;pdb.set_trace()
        ## ground_plane_support
        self.ground_plane_support = kwargs['ground_plane_support']
        if self.ground_plane_support:
            self.register_buffer('gp_support_loss_weight',
                                    torch.tensor(kwargs['gp_support_loss_weight'], dtype=kwargs['dtype']))

        ## ground_contact_support
        self.ground_contact_support = kwargs['ground_contact_support']
        if self.ground_contact_support:
            self.register_buffer('gp_contact_loss_weight',
                                    torch.tensor(kwargs['gp_contact_loss_weight'], dtype=kwargs['dtype']))
            self.ground_contact_vertices_ids = kwargs['ground_contact_vertices_ids']
            self.rho_contact = kwargs['rho_contact']
            logger.info(f'rho_contact in gp_contact_robustifier: {kwargs["rho_contact"]}')
            self.gp_contact_robustifier = misc_utils.GMoF_unscaled(rho=self.rho_contact)

        ## human & scene object inter-penetration
        self.sdf_penetration = kwargs['sdf_penetration']
        if self.sdf_penetration:
            self.register_buffer('sdf_penetration_loss_weight',
                                    torch.tensor(kwargs['sdf_penetration_loss_weight'], dtype=kwargs['dtype']))

        ## human & scene object contact loss
        self.contact = kwargs['contact']
        if self.contact or True:
            self.register_buffer('contact_loss_weight',
                                    torch.tensor(kwargs['contact_loss_weight'], dtype=kwargs['dtype']))
            self.contact_verts_ids = kwargs['contact_verts_ids']
            self.rho_contact = kwargs['rho_contact']
            self.contact_angle = kwargs['contact_angle']
            self.ftov = kwargs['ftov']
            self.contact_robustifier = misc_utils.GMoF(rho=self.rho_contact) # rho is the distance. 5cm
        # else:
        #     self.contact_verts_ids = None
        #     self.rho_contact = None
        #     self.contact_angle = None
        #     self.ftov = None
        # self.contact_robustifier = misc_utils.GMoF_unscaled(rho=self.rho_contact) #used in prox

        ## depth ordinal loss
        self.ordinal_depth = kwargs['ordinal_depth']
        if self.ordinal_depth:
            self.register_buffer('ordinal_depth_loss_weight', torch.tensor(kwargs['ordinal_depth_loss_weight'], dtype=kwargs['dtype']))

        self.scene = kwargs['scene']
        if self.scene:
            self.register_buffer('scene_loss_weight',
                                    torch.tensor(kwargs['scene_loss_weight'], dtype=kwargs['dtype']))

        self.loss_use_sum = kwargs['loss_use_sum']
        self.video_smooth = kwargs['video_smooth']
        self.constant_velocity = kwargs['constant_velocity']
        # learn from AMASS dataset
        self.motion_smooth_prior_flag = kwargs['motion_smooth_prior']

        if self.video_smooth:
            self.register_buffer('smooth_2d_weight',
                                    torch.tensor(kwargs['smooth_2d_weight'], dtype=kwargs['dtype']))
            self.register_buffer('smooth_3d_weight',
                                    torch.tensor(kwargs['smooth_3d_weight'], dtype=kwargs['dtype']))

            self.robustifier_3DJoint = misc_utils.GMoF(rho=0.1)

            if self.motion_smooth_prior_flag:
                self.register_buffer('motion_prior_weight',
                                    torch.tensor(kwargs['motion_prior_weight'], dtype=kwargs['dtype']))
                sys.path.append('/is/cluster/hyi/workspace/HCI/amass_infiller')
                from motion_smooth_prior import MotionSmoothPrior
                self.motion_smooth_prior = MotionSmoothPrior()

        ## pare pose loss weight
        self.pare_pose_prior = kwargs['pare_pose_prior']
        if self.pare_pose_prior:
            self.register_buffer('pare_pose_weight',
                                torch.tensor(kwargs['pare_pose_weight'], dtype=kwargs['dtype']))

        ## for ordinal depth loss
        self.use_human_depth = kwargs['use_human_depth']

        self.depth_robustifier = None # ! without using it, sometimes, it will fail.
        # self.depth_robustifier = kwargs['depth_robustifier'] # default is None, set 10

        # gp contact loss
        self.rho_gp_flag = None
        self.l1_gp_flag = None

        # posa model for online pose

        # posa pre-calculated contact labels
        self.posa_flag=kwargs['posa_flag']
        self.online_pose=kwargs['online_pose']
        self.posa_body_contact_labels = kwargs['posa_body_contact_labels']

        # codebase selection
        self.use_scene_loss = kwargs['use_scene_loss']
        # import pdb;pdb.set_trace()
        self.save_dir = kwargs['output_folder']

        # TODO: add motion smooth loss term.

    def forward(self, body_model_output, cameras, gt_joints, joints_conf,
                body_model_faces, joint_weights,
                use_vposer=False, pose_embedding=None,
                gt_contact_value=None,
                scene_model=None,
                perframe_mask=None,
                pare_body_pose=None, pare_body_flag=None,
                **kwargs):

        ################################
        # joints_conf: smpl format;
        # gt_joints: openpose 2D joints;
        ################################
        batch_size = gt_joints.shape[0]

        # TODO: the shape is not correct !!!
        gt_joints = gt_joints.transpose(0, 1)
        joints_conf = joints_conf.unsqueeze(0)

        joint_loss = 0

        # for motion smooth loss term.
        smooth_j2d_loss = 0
        smooth_j3d_loss = 0
        motion_prior_loss = 0
        for vi, cam in enumerate(cameras):
            projected_joints = cam(body_model_output.joints)
            # Calculate the weights for each joints
            # batch * n_joints_1

            if False:
                import cv2
                img_path  = '//ps/scratch/hyi/HCI_dataset/20210109_capture/C0034/hci_test/000379.jpg'
                cv2_img = cv2.imread(img_path)
                cam = cameras[0]
                ori_proj = gt_joints[vi, :,:15,:].squeeze(0).detach().cpu().numpy().T
                for j in range(ori_proj.shape[1]):
                    print(ori_proj[0, j], ori_proj[1, j])
                    cv2_img = cv2.circle(cv2_img, (int(ori_proj[0, j]), int(ori_proj[1, j])), \
                                radius=5, color=(255, 0, 0), thickness=4)

                proj_kpts = cam(body_model_output.joints[:,:15,:])
                ori_proj = proj_kpts.squeeze(0).detach().cpu().numpy().T
                for j in range(ori_proj.shape[1]):
                    print(ori_proj[0, j], ori_proj[1, j])
                    cv2_img = cv2.circle(cv2_img, (int(ori_proj[0, j]), int(ori_proj[1, j])), \
                                radius=5, color=(0, 0, 255), thickness=4)

                # cv2.imshow("2d joint", cv2_img)
                cv2.imwrite("mvs_render.png", cv2_img)
                cv2.waitKey(5)
                import pdb;pdb.set_trace()

            weights = (joint_weights * joints_conf[vi]
                       if self.use_joints_conf else
                       joint_weights).unsqueeze(dim=-1)

            # Calculate the distance of the projected joints from
            # the ground truth 2D detections
            # TODO: the shape is not correct !!!
            joint_diff = self.robustifier(gt_joints[vi, :, :, :] - projected_joints)
            joint_loss = joint_loss + (torch.sum(weights ** 2 * joint_diff) *
                        self.data_weight ** 2) / batch_size

            if self.video_smooth:
                if self.motion_smooth_prior_flag:
                    # TODO:
                    pass
                    model_verts = body_model_output.vertices
                    model_joints = body_model_output.joints
                    # * factor out missing frame effects.
                    # import pdb;pdb.set_trace()
                    motion_useful_conf = (joints_conf[vi, 1:, :].sum(-1) > 0) & (joints_conf[vi, 0:-1, :].sum(-1) > 0)
                    motion_prior_loss = self.motion_smooth_prior(model_verts, model_joints, conf=motion_useful_conf) # per-single image loss.
                    motion_prior_loss = self.motion_prior_weight * motion_prior_loss

                if self.constant_velocity:
                    # import pdb;pdb.set_trace()
                    if True:
                        ## smooth 2D joint loss
                        joint_conf_diff = joints_conf[vi, 1:-1, :]
                        joints_2d_diff = self.robustifier(projected_joints[2:]+projected_joints[:-2] - 2 * projected_joints[1:-1])
                        weights_diff = (joint_weights * joint_conf_diff
                                if self.use_joints_conf else
                                joint_conf_diff)
                        smooth_j2d_loss = (weights_diff ** 2) * joints_2d_diff.sum(dim=-1)
                        smooth_j2d_loss = torch.cat(
                                [torch.zeros(1, smooth_j2d_loss.shape[1], device=joints_conf.device), smooth_j2d_loss]
                            ).sum(dim=-1)
                        smooth_j2d_loss = (self.smooth_2d_weight ** 2) * smooth_j2d_loss / batch_size
                        smooth_j2d_loss = smooth_j2d_loss.sum()

                        ## smooth 3D joint loss
                        model_joints = body_model_output.joints
                        joints_3d_diff = self.robustifier_3DJoint(model_joints[2:]+model_joints[:-2] - 2 * model_joints[1:-1])
                        smooth_j3d_loss = (weights_diff ** 2) * joints_3d_diff.sum(dim=-1)
                        smooth_j3d_loss = torch.cat(
                                [torch.zeros(1, smooth_j3d_loss.shape[1], device=joints_conf.device), smooth_j3d_loss]
                            ).sum(dim=-1)
                        smooth_j3d_loss = (self.smooth_3d_weight ** 2) * smooth_j3d_loss / batch_size
                        smooth_j3d_loss = smooth_j3d_loss.sum()
                    else: # static motion prior
                        ## smooth 2D joint loss
                        joint_conf_diff = joints_conf[vi, 1:, :]
                        joints_2d_diff = projected_joints[1:] - projected_joints[:-1]
                        weights_diff = (joint_weights * joint_conf_diff
                                if self.use_joints_conf else
                                joint_conf_diff)
                        smooth_j2d_loss = (weights_diff ** 2) * joints_2d_diff.abs().sum(dim=-1)
                        smooth_j2d_loss = torch.cat(
                                [torch.zeros(1, smooth_j2d_loss.shape[1], device=joints_conf.device), smooth_j2d_loss]
                            ).sum(dim=-1)
                        smooth_j2d_loss = (self.smooth_2d_weight ** 2) * smooth_j2d_loss / batch_size
                        smooth_j2d_loss = smooth_j2d_loss.sum()

                        ## smooth 3D joint loss
                        model_joints = body_model_output.joints
                        joints_3d_diff = model_joints[1:] - model_joints[:-1]
                        smooth_j3d_loss = (weights_diff ** 2) * joints_3d_diff.abs().sum(dim=-1)
                        smooth_j3d_loss = torch.cat(
                                [torch.zeros(1, smooth_j3d_loss.shape[1], device=joints_conf.device), smooth_j3d_loss]
                            ).sum(dim=-1)
                        smooth_j3d_loss = (self.smooth_3d_weight ** 2) * smooth_j3d_loss / batch_size
                        smooth_j3d_loss = smooth_j3d_loss.sum()

        pare_pose_prior_loss = 0.0
        if self.pare_pose_prior and self.pare_pose_weight > 0:
            # import pdb;pdb.set_trace()
            assert pare_body_pose.shape[0] == pare_body_flag.shape[0]
            # pose embedding to body pose (angles)
            tmp_body_pose = body_model_output.body_pose
            use_ful = [map_flag != -1 for map_flag in BODY_POSE_TO_OP]
            use_op = [map_flag for map_flag in BODY_POSE_TO_OP if map_flag != -1]
            op_conf_body = torch.ones((batch_size, 21)).type_as(joints_conf) # warining: inverse op conf
            op_conf_body[:, use_ful] = joints_conf[0][:, use_op]

            op_conf_body = op_conf_body.unsqueeze(-1).repeat(1, 1, 3).reshape(batch_size, -1)
            # TODO: modify pare_body_pose detach
            # import pdb;pdb.set_trace()
            # pare_pose_penalty = (1- op_conf_body) * torch.abs(body_model_output.body_pose - pare_body_pose.detach()).pow(2).sqrt()#.reshape(-1, 21, 3).reshape(batch_size, -1)
            pare_pose_penalty = (1 - op_conf_body) * torch.abs(body_model_output.body_pose - pare_body_pose.detach())
            pare_pose_prior_loss = torch.sum(pare_body_flag * pare_pose_penalty, 1)
            pare_pose_prior_loss = (pare_pose_prior_loss * self.pare_pose_weight ** 2).mean()

        # Calculate the loss from the Pose prior
        if use_vposer:
            pprior_loss = (pose_embedding.pow(2).sum() *
                           self.body_pose_weight ** 2)
        else:
            pprior_loss = torch.sum(self.body_pose_prior(
                body_model_output.body_pose,
                body_model_output.betas)) * self.body_pose_weight ** 2
        pprior_loss = pprior_loss / batch_size

        shape_loss = torch.sum(self.shape_prior(
            body_model_output.betas)) * self.shape_weight ** 2 / batch_size
        # Calculate the prior over the joint rotations. This a heuristic used
        # to prevent extreme rotation of the elbows and knees
        body_pose = body_model_output.full_pose[:, 3:66]
        angle_prior_loss = torch.sum(
            self.angle_prior(body_pose)) * self.bending_prior_weight / batch_size

        # Apply the prior on the pose space of the hand
        left_hand_prior_loss, right_hand_prior_loss = 0.0, 0.0
        if self.use_hands and self.left_hand_prior is not None:
            left_hand_prior_loss = torch.sum(
                self.left_hand_prior(
                    body_model_output.left_hand_pose)) * \
                self.hand_prior_weight ** 2 / batch_size

        if self.use_hands and self.right_hand_prior is not None:
            right_hand_prior_loss = torch.sum(
                self.right_hand_prior(
                    body_model_output.right_hand_pose)) * \
                self.hand_prior_weight ** 2 / batch_size

        expression_loss = 0.0
        jaw_prior_loss = 0.0
        if self.use_face:
            expression_loss = torch.sum(self.expr_prior(
                body_model_output.expression)) * \
                self.expr_prior_weight ** 2 / batch_size

            if hasattr(self, 'jaw_prior'):
                jaw_prior_loss = torch.sum(
                    self.jaw_prior(
                        body_model_output.jaw_pose.mul(
                            self.jaw_prior_weight))) / batch_size

        pen_loss = 0.0
        # Calculate the loss due to interpenetration
        if (self.interpenetration and self.coll_loss_weight.item() > 0):

            batch_size = gt_joints.shape[1]
            triangles = torch.index_select(
                body_model_output.vertices, 1,
                body_model_faces).view(batch_size, -1, 3, 3)

            with torch.no_grad():
                collision_idxs = self.search_tree(triangles)

            # Remove unwanted collisions
            if self.tri_filtering_module is not None:
                collision_idxs = self.tri_filtering_module(collision_idxs)

            if collision_idxs.ge(0).sum().item() > 0:
                pen_loss = torch.sum(
                    self.coll_loss_weight *
                    self.pen_distance(triangles, collision_idxs)) / batch_size

        ############## ! warning: calculate in world coordinates system.
        body_vertices_world = body_model_output.vertices

        # gp_support_loss = 0.0
        # if self.ground_plane_support and self.gp_support_loss_weight > 0 and scene_model is not None:
        #     gt_ground_plane = scene_model.ground_plane
        #     ## ground plane support loss
        #     assert gt_ground_plane is not None
        #     gp_support_loss = F.smooth_l1_loss(torch.max(body_vertices_world[:, :, 1]), gt_ground_plane)
        #     gp_support_loss = gp_support_loss * self.gp_support_loss_weight

        # gp_contact_loss = 0.0
        # if self.ground_contact_support and self.gp_contact_loss_weight > 0 and scene_model is not None :
        #     # import pdb;pdb.set_trace()
        #     gt_ground_plane = scene_model.ground_plane
        #     assert gt_contact_value is not None
        #     # the distance from heel and toe to the mesh surface should be
        #     # consist with the distance from joint to the ground plane

        #     # gt_contact_value[gt_contact_value==0] = 0 #-0.2
        #     # gp_contact_loss = torch.abs(body_vertices_world[:, self.ground_contact_vertices_ids, 1] - gt_ground_plane)
        #     # gp_contact_loss = (gt_contact_value * gp_contact_loss.mean(-1) * self.gp_contact_loss_weight).sum() / (gt_contact_value.sum().to(torch.float32) + 1e-9)

        #     gp_contact_loss = F.smooth_l1_loss(body_vertices_world[:, self.ground_contact_vertices_ids, 1].mean(-1), gt_ground_plane, reduction='none')

        #     gp_contact_loss = self.contact_robustifier(gp_contact_loss)
        #     gp_contact_loss = self.gp_contact_loss_weight * gp_contact_loss.mean()

        # if self.ground_contact_support and self.gp_contact_loss_weight > 0 and scene_model is not None :
        #     # import pdb;pdb.set_trace()
        #     gt_ground_plane = scene_model.ground_plane
        #     assert gt_contact_value is not None
        #     # the distance from heel and toe to the mesh surface should be
        #     # consist with the distance from joint to the ground plane

        #     gt_contact_value[gt_contact_value==0] = 0 #-0.2
        #     gp_contact_loss = torch.abs(body_vertices_world[:, self.ground_contact_vertices_ids, 1] - gt_ground_plane)
        #     gp_contact_loss = (gt_contact_value * gp_contact_loss.mean(-1) * self.gp_contact_loss_weight).sum() / (gt_contact_value.sum() + 1e-9)
        gp_support_loss = 0.0
        gp_contact_loss = 0.0

        # import pdb;pdb.set_trace()
        if True: # old version for single image contact loss.
            # import pdb;pdb.set_trace()
            if self.ground_plane_support:
                gt_ground_plane = scene_model.ground_plane
                gp_support_loss = F.relu(torch.max(body_vertices_world[:, :, 1], -1)[0] - gt_ground_plane).mean()
                gp_support_loss = gp_support_loss * self.gp_support_loss_weight

            if self.ground_contact_support and self.gp_contact_loss_weight > 0:
                # contact ids from POSA <-> ground plane
                # TODO: contact_loss(contacted feet <-> ground plane)
                gt_ground_plane = scene_model.ground_plane
                dist_gp = self.gp_contact_robustifier(torch.abs(body_vertices_world[:, :, 1] - gt_ground_plane))
                tmp_feet_label = torch.zeros(self.posa_body_contact_labels.shape).type_as(self.posa_body_contact_labels)
                tmp_feet_label[:, self.ground_contact_vertices_ids.reshape(-1)] = 1
                body_vertices_contact_label_feet = self.posa_body_contact_labels & tmp_feet_label
                # import pdb;pdb.set_trace()
                if body_vertices_contact_label_feet.sum() > 0:
                    gp_contact_loss = dist_gp[body_vertices_contact_label_feet.squeeze(-1)].mean() * self.gp_contact_loss_weight
                else:
                    gp_contact_loss = torch.zeros(1).cuda()

        elif False:
            if self.ground_contact_support and self.gp_contact_loss_weight > 0:
                # import pdb;pdb.set_trace()
                gt_ground_plane = scene_model.ground_plane
                feet_vertices = body_vertices_world[:, self.ground_contact_vertices_ids, 1] # B, 2, N
                # reliable feet: op conf > thre
                # import pdb;pdb.set_trace()
                # joints_conf[vi][:, FEET_IN_OP]
                feet_conf = joints_conf[vi, :, FEET_IN_OP].reshape(-1, 2, 3)
                # TODO: with using POSA
                feet_conf = feet_conf.mean(-1)#[0] before 0613 # B, 2
                reliable_feet = feet_conf >= 0.4
                logger.info(f'reliable_feet length: {reliable_feet.shape}')
                # use scene object contact information
                # contact_flag = scene_model.get_contact_flag(body_vertices_world)

                # import pdb;pdb.set_trace()
                # if the feet far away from the ground plane
                contact_flag = (gt_ground_plane - torch.max(feet_vertices, -1)[0]) > 0.1

                into_gp = (torch.max(feet_vertices, -1)[0] - gt_ground_plane) > 0
                # not reliable feet, max(feet) < gp, if exists contact with other objects, use GM contact loss
                rho_gp_flag = reliable_feet | (~reliable_feet & contact_flag & ~into_gp)
                logger.info(f'rho_gp_flag length: {rho_gp_flag.sum()}')
                if self.rho_gp_flag is None:
                    self.rho_gp_flag = rho_gp_flag

                ############## GM_contact_ground_loss
                # two feets contact gp
                tmp_gp_contact_dist = F.mse_loss(feet_vertices, gt_ground_plane.expand_as(feet_vertices), reduction='none').sqrt()
                tmp_gp_contact_loss = self.gp_contact_robustifier(tmp_gp_contact_dist)
                # logger.info(f'tmp_gp_contact_loss length: {tmp_gp_contact_loss.shape}')
                reliable_gp_contact_loss = tmp_gp_contact_loss.mean(-1) * self.rho_gp_flag.detach().type(torch.float)

                # gp_contact_loss = self.robustifier(smplx_model_vertices[:, ground_contact_vertices_ids, 1] - self.ground_plane)
                # gp_contact_loss = (ground_contact_value * gp_contact_loss.mean(-1)).sum() / ( num_labels + 1e-9)

                ############## abs_loss
                # two feets support by a ground plane
                # not reliable feet, max(feet) > gp
                l1_gp_dist = torch.abs(torch.max(feet_vertices.reshape(batch_size, -1), -1)[0] - gt_ground_plane).unsqueeze(1).expand(-1, 2)
                # not reliable feet, max(feet) < gp, check contact, if not
                l1_gp_flag = (~reliable_feet) & (into_gp | (~contact_flag))
                logger.info(f'l1_gp_flag length: {l1_gp_flag.sum()}')
                if self.l1_gp_flag is None:
                    self.l1_gp_flag = l1_gp_flag
                gp_l1_loss = l1_gp_dist * self.l1_gp_flag.detach().type(torch.float)

                assert (rho_gp_flag | l1_gp_flag).sum() == rho_gp_flag.numel()
                # import pdb;pdb.set_trace()
                # gp_contact_loss = (reliable_gp_contact_loss + gp_l1_loss).mean() # * batch_size

                gp_contact_loss = l1_gp_dist.mean()

                if torch.isnan(gp_contact_loss):
                    import pdb;pdb.set_trace()
        else:
            if self.ground_contact_support and self.gp_contact_loss_weight > 0:
                import pdb;pdb.set_trace()
                gt_ground_plane = scene_model.ground_plane
                feet_vertices = body_vertices_world[:, self.ground_contact_vertices_ids, 1] # B, 2, N
                # reliable feet: op conf > thre
                feet_conf = joints_conf[vi, :, FEET_IN_OP].reshape(-1, 2, 3)
                # TODO: with using POSA
                feet_conf = feet_conf.mean(-1)#[0] before 0613 # B, 2
                reliable_feet = feet_conf >= 0.4
                logger.info(f'reliable_feet length: {reliable_feet.shape}')
                # use scene object contact information
                # contact_flag = scene_model.get_contact_flag(body_vertices_world)

                # if the feet far away from the ground plane
                contact_flag = (gt_ground_plane - torch.max(feet_vertices, -1)[0]) > 0.15

                into_gp = (torch.max(feet_vertices, -1)[0] - gt_ground_plane) > 0
                # not reliable feet, max(feet) < gp, if exists contact with other objects, use GM contact loss
                rho_gp_flag = reliable_feet | (~reliable_feet & contact_flag & ~into_gp)
                logger.info(f'rho_gp_flag length: {rho_gp_flag.sum()}')
                if self.rho_gp_flag is None:
                    self.rho_gp_flag = rho_gp_flag

                ############## GM_contact_ground_loss
                # two feets contact gp
                tmp_gp_contact_dist = F.mse_loss(feet_vertices, gt_ground_plane.expand_as(feet_vertices), reduction='none').sqrt()
                tmp_gp_contact_loss = self.gp_contact_robustifier(tmp_gp_contact_dist)
                # logger.info(f'tmp_gp_contact_loss length: {tmp_gp_contact_loss.shape}')
                reliable_gp_contact_loss = tmp_gp_contact_loss.mean(-1) * self.rho_gp_flag.detach().type(torch.float)

                # gp_contact_loss = self.robustifier(smplx_model_vertices[:, ground_contact_vertices_ids, 1] - self.ground_plane)
                # gp_contact_loss = (ground_contact_value * gp_contact_loss.mean(-1)).sum() / ( num_labels + 1e-9)

                ############## abs_loss
                # two feets support by a ground plane
                # not reliable feet, max(feet) > gp
                l1_gp_dist = torch.abs(torch.max(feet_vertices.reshape(batch_size, -1), -1)[0] - gt_ground_plane).unsqueeze(1).expand(-1, 2)
                # not reliable feet and not far away from gp | max(feet) < gp
                l1_gp_flag = ((~reliable_feet) & (~contact_flag)) | into_gp
                logger.info(f'l1_gp_flag length: {l1_gp_flag.sum()} / {l1_gp_flag.numel()}')
                if self.l1_gp_flag is None:
                    self.l1_gp_flag = l1_gp_flag
                gp_l1_loss = l1_gp_dist * self.l1_gp_flag.detach().type(torch.float)

                # assert (rho_gp_flag | l1_gp_flag).sum() == rho_gp_flag.numel()
                # import pdb;pdb.set_trace()
                # gp_contact_loss = (reliable_gp_contact_loss + gp_l1_loss).mean() # * batch_size
                gp_contact_loss = (gp_l1_loss).mean()
                if torch.isnan(gp_contact_loss):
                    import pdb;pdb.set_trace()

        # print(kwargs['use_scene_loss'])
        if self.use_scene_loss: # add scene constraints from PROX / POSA
            if False:
                ### scene constraint loss.
                sdf_penetration_loss = 0.0
                # TODO: check whether is batch size average?
                # TODO: add scene sdf calculation in scene_model.
                if self.sdf_penetration and self.sdf_penetration_loss_weight >0:
                    assert scene_model is not None
                    cnt = 0
                    for idx in range(batch_size):
                        # import pdb;pdb.set_trace()
                        sdf_penetration_loss_tmp = scene_model(body_vertices_world[idx:idx+1], stage=1)
                        if sdf_penetration_loss_tmp > 0:
                            cnt = cnt + 1
                        sdf_penetration_loss = sdf_penetration_loss + sdf_penetration_loss_tmp
                        #stage=11) not reasonable
                    # sdf_penetration_loss = self.sdf_penetration_loss_weight * sdf_penetration_loss / batch_size
                    sdf_penetration_loss = self.sdf_penetration_loss_weight * sdf_penetration_loss / (cnt+1e-9)

                contact_loss = 0.0
                # TODO: add scene v, vn calculation in scene model.
                if self.contact and self.contact_loss_weight >0:
                    # import pdb;pdb.set_trace()
                    assert scene_model is not None
                    cnt = 0
                    for idx in range(batch_size):
                        # if idx == 7:
                        #     import pdb;pdb.set_trace()
                        tmp_contact = scene_model(body_vertices_world[idx:idx+1], stage=2, contact_verts_ids=self.contact_verts_ids,
                                                contact_angle=self.contact_angle, contact_robustifier=self.contact_robustifier, ftov=self.ftov)
                        # logger.info(f'{idx}: {tmp_contact.item()}')

                        if tmp_contact > 0:
                            cnt = cnt + 1
                        contact_loss = contact_loss + tmp_contact
                    # contact_loss = self.contact_loss_weight * contact_loss / batch_size
                    contact_loss = self.contact_loss_weight * contact_loss / (cnt+1e-9)

                # TODO: calculate depth map for scene model, and use it as constraint.
                depth_loss = 0.0
                if self.ordinal_depth and self.ordinal_depth_loss_weight > 0:
                    cnt = 0
                    assert scene_model is not None
                    for idx in range(batch_size):
                    # input current new per-frame mask.
                        # import pdb;pdb.set_trace()
                        # this
                        tmp_depth_loss = scene_model.compute_ordinal_depth_loss_perframe(body_vertices_world[idx:idx+1], idx, use_for_human=self.use_human_depth)['loss_depth']
                        # import pdb;pdb.set_trace()
                        if torch.isnan(tmp_depth_loss): # the depth loss is too big, generates large gradient, leads to error !!!
                            import pdb;pdb.set_trace()
                            # tmp_depth_loss = scene_model.compute_ordinal_depth_loss_perframe(body_vertices_world[idx:idx+1], idx, use_for_human=self.use_human_depth)['loss_depth']

                        if tmp_depth_loss > 0:
                            cnt += 1

                        depth_loss = depth_loss + self.ordinal_depth_loss_weight * tmp_depth_loss
                    # depth_loss = depth_loss / batch_size
                    depth_loss = depth_loss / (cnt+1e-9)
                    # ! Warning: add robustifier: for Nan problem, the gradients are so big! on 03.08
                    # import pdb;pdb.set_trace()
                    # if self.depth_robustifier is not None:
                    #     squared_res = depth_loss ** 2
                    #     depth_loss = torch.div(squared_res, squared_res + self.depth_robustifier ** 2)
            else:
                # loss weight > 0: leads to results;
                scene_constraint_loss_weight = {}
                sdf_penetration_loss = torch.tensor(0.0).cuda()
                contact_loss = torch.tensor(0.0).cuda()
                depth_loss = torch.tensor(0.0).cuda()
                if self.sdf_penetration and self.sdf_penetration_loss_weight >0:
                    scene_constraint_loss_weight['lw_sdf'] = self.sdf_penetration_loss_weight
                if self.contact and self.contact_loss_weight >0:
                    scene_constraint_loss_weight['lw_contact'] = self.contact_loss_weight
                # if self.posa and self.posa_loss_weight >0:
                #     scene_constraint_loss_weight['lw_posa'] = self.posa_loss_weight
                #     posa
                if self.ordinal_depth and self.ordinal_depth_loss_weight > 0:
                    scene_constraint_loss_weight['lw_depth'] = self.ordinal_depth_loss_weight
                scene_loss_dict = scene_model(body_vertices_world,
                                                save_dir=self.save_dir,
                                                loss_weights=scene_constraint_loss_weight,
                                                stage=3, contact_verts_ids=self.contact_verts_ids,
                                                contact_angle=self.contact_angle, \
                                                contact_robustifier=self.gp_contact_robustifier, ftov=self.ftov) #self.contact_robustifier
                if 'loss_sdf' in scene_loss_dict:
                    sdf_penetration_loss = scene_loss_dict['loss_sdf'] * self.sdf_penetration_loss_weight
                if 'loss_contact' in scene_loss_dict:
                    contact_loss = scene_loss_dict['loss_contact'] * self.contact_loss_weight
                if 'loss_depth' in scene_loss_dict:
                    depth_loss = scene_loss_dict['loss_depth'] * self.ordinal_depth_loss_weight
                if 'loss_posa' in scene_loss_dict:
                    contact_loss = scene_loss_dict['loss_contact'] * self.posa_loss_weight

                # best version for only contact & sdf loss: 1, 1
        else:
            sdf_penetration_loss = 0.0
            contact_loss = 0.0
            depth_loss = 0.0
        scene_loss = 0.0
        message_scene = ''
        # if self.scene and self.scene_loss_weight > 0:
        #     assert scene_model  is not None
        #     init_pid = 0
        #     scene_local_loss_weights = DEFAULT_LOSS_WEIGHTS[f'stage2_init{init_pid}']['loss_weight']
        #     scene_loss_tmp = scene_model(stage=0, loss_weights=scene_local_loss_weights)
        #     scene_loss_dict_weighted = {
        #             k: scene_loss_tmp[k] * scene_local_loss_weights[k.replace("loss", "lw")] for k in scene_loss_tmp
        #         }

        #     for key, val in scene_loss_tmp.items():
        #             message_scene += f' {key}: {val.item()}_{scene_local_loss_weights[key.replace("loss", "lw")]}_{scene_loss_dict_weighted[key].item()}'
        #     scene_loss = sum(scene_loss_dict_weighted.values()).sum()
        #     scene_loss = self.scene_loss_weight * scene_loss

        # TODO: experiments to analysis;
        message = f'ST2 joint: {joint_loss}, \
            angle_prior: {angle_prior_loss}, pen:{pen_loss}, pare_prior:{pare_pose_prior_loss}, \
            gp: {gp_support_loss}, \
            gp_contact: {gp_contact_loss}, sdf: {sdf_penetration_loss}, contact: {contact_loss}, \
            scene: {scene_loss}, depth: {depth_loss}, smooth_j2d_loss:{smooth_j2d_loss}, smooth_j3d_loss: {smooth_j3d_loss}, motion_smooth_prior: {motion_prior_loss}'
        message += ' || scene details: ' + message_scene
        logger.info(message)

        if self.loss_use_sum:
            total_loss = (joint_loss + pprior_loss + shape_loss +
                        angle_prior_loss + pen_loss + pare_pose_prior_loss +
                        jaw_prior_loss + expression_loss +
                        left_hand_prior_loss + right_hand_prior_loss
                        + gp_support_loss + gp_contact_loss +
                        sdf_penetration_loss + contact_loss + depth_loss + smooth_j2d_loss + smooth_j3d_loss + motion_prior_loss) * batch_size + scene_loss
        else:
            total_loss = (joint_loss + pprior_loss + shape_loss +
                        angle_prior_loss + pen_loss + pare_pose_prior_loss +
                        jaw_prior_loss + expression_loss +
                        left_hand_prior_loss + right_hand_prior_loss
                        + gp_support_loss + gp_contact_loss +
                        sdf_penetration_loss + contact_loss + depth_loss + smooth_j2d_loss + smooth_j3d_loss + motion_prior_loss) + scene_loss

        if 'tb_debug' in kwargs and kwargs['tb_debug']:
            debug_loss_dict = {
                'loss_joint': joint_loss,
                'loss_pprior': pprior_loss,
                'loss_shape': shape_loss,
                'loss_angle_prior': angle_prior_loss,
                'loss_pare_pose_prior': pare_pose_prior_loss,
                'loss_expression': expression_loss,
                'loss_left_hand_prior': left_hand_prior_loss,
                'loss_right_hand_prior': right_hand_prior_loss,
                'loss_gp_support_body': gp_support_loss,
                'loss_gp_contact':  gp_contact_loss,
                'loss_sdf_penetration': sdf_penetration_loss,
                'loss_contact': contact_loss,
                'loss_scene': scene_loss,
                'loss_depth': depth_loss,
                'smooth_j3d_loss': smooth_j3d_loss,
                'smooth_j2d_loss': smooth_j2d_loss,
                'motion_prior_loss': motion_prior_loss,
                'loss_total_MultiViewSMPLify': total_loss,
            }

            if self.ground_plane_support:
                debug_loss_dict['SMPLX_weight_gp_support'] = self.gp_support_loss_weight
            if self.ground_contact_support:
                debug_loss_dict['SMPLX_weight_gp_contact'] = self.gp_contact_loss_weight
            if self.sdf_penetration:
                debug_loss_dict['SMPLX_weight_sdf_penetration'] = self.sdf_penetration_loss_weight
            if self.contact:
                debug_loss_dict['SMPLX_weight_contact'] = self.contact_loss_weight
            if self.scene:
                debug_loss_dict['SMPLX_weight_scene'] = self.scene_loss_weight
            if self.ordinal_depth:
                debug_loss_dict['SMPLX_weight_ordinal_depth'] = self.ordinal_depth_loss_weight

            if self.scene and self.scene_loss_weight > 0:
                for key, value in scene_local_loss_weights.items():
                    debug_loss_dict[f'scene_weight_{key}'] = value
                for key in scene_loss_tmp.keys():
                    debug_loss_dict[f'loss_scene_{key}_wt'] = scene_loss_dict_weighted[key]
                    debug_loss_dict[f'loss_scene_{key}'] = scene_loss_tmp[key]

            if self.video_smooth:
                debug_loss_dict['smooth_2d_weight'] = self.smooth_2d_weight
                debug_loss_dict['smooth_3d_weight'] = self.smooth_3d_weight
                if self.motion_smooth_prior_flag:
                    debug_loss_dict['motion_prior_weight'] = self.motion_prior_weight

            return total_loss, debug_loss_dict
        else:
            return total_loss


import json
from flask import request, _request_ctx_stack, abort
from functools import wraps
from jose import jwt

algo = 'HS256'  # HMAC-SHA 256
secret = 'learning'
class AuthError(Exception):
    def __init__(self, error, status_code):
        self.error = error
        self.status_code = status_code

def get_auth_token():
    auth = request.headers.get('Authorization', None)
    if auth is None:
        return auth
    parts = auth.split()
    if parts[0].lower() != 'bearer':
        raise AuthError({
            'code': 'invalid_header',
            'description': 'Authorization header must start with "Bearer".'
        }, 401)

    elif len(parts) == 1:
        raise AuthError({
            'code': 'invalid_header',
            'description': 'Token not found.'
        }, 401)

    elif len(parts) > 2:
        raise AuthError({
            'code': 'invalid_header',
            'description': 'Authorization header must be bearer token.'
        }, 401)
    token = parts[1]
    return token

def verify_decode_jwt(token):
    try:
        decoded_jwt = jwt.decode(token, secret,options={'require': ['exp']}, algorithms = algo)
        name = decoded_jwt.get('name')
    except jwt.ExpiredSignatureError:
            raise AuthError({
                'code': 'token_expired',
                'description': 'Token expired.'
            }, 401)
    return name

def requires_auth_decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            token = get_auth_token()
            if token is None:
                raise AuthError({
                'code': 'authorization_header_missing',
                'description': 'Authorization header is expected.'
                }, 401)
            name = verify_decode_jwt(token)

            return f(name, *args, **kwargs)

        return wrapper

def check_log_in(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            token = get_auth_token()
            name = ''
            if token is not None:
                name = verify_decode_jwt(token)

            return f(name, *args, **kwargs)

        return wrapper


def kmpMatcher(txt,pattern):
    n=len(txt)
    m=len(pattern)
    pi=computeprefixfunction(pattern)
    q=0
    i=0
    while i < n:
        if pattern[q] == txt[i]:
            i += 1
            q += 1
        if q == m:
            print ("Found pattern at index " + str(i-q))
            q = pi[q-1]

        elif i < n and pattern[q] != txt[i]:
            if q != 0:
                q = pi[q-1]
            else:
                i += 1

def computeprefixfunction(pattern):
    m= len(pattern)
    pi=[0]*m
    pi[0]=0
    k=0

    q=1
    while q < m:
        if pattern[q]== pattern[k]:
            k += 1
            pi[q] = k
            q += 1
        else:
            if k != 0:
                k = pi[k-1]
            else:
                pi[q] = 0
                q += 1
    return pi

# txt = "ΞΔΞΔ~ΔΞΔΞΔΞΔ~"
# pat = "ΞΔΞΔΞΔ"
# kmpMatcher(txt,pat)


from src.domain.grade import Grade
from src.domain.grade_validator import GradeVal
from random import randint
import unittest
from src.repository.repository_exception import Exception
from src.services.undo_service import FunctionCall, Operation, CascadedOperation
from src.services.gnome_sort import gnomeSort, filter

class GradeSer:
    def __init__(self, grade_repo, student_service, discipline_service, undo_service):
        self.__repo = grade_repo
        self._validator = GradeVal()
        self.__studService = student_service
        self.__disService = discipline_service
        self.__undo_service = undo_service

    def add_grade_undo(self, stud_id, dis_id, gr):
        """
        adds a grade (undo/redo function)
        :param stud_id: student id
        :param dis_id: discipline id
        :param gr: grade
        :return:
        """
        grade = Grade(stud_id, dis_id, gr)
        self.__repo.add_gr(grade)

    def add_gr(self, gr):
        self.__repo.add_gr(gr)

    def add_grade(self, gr):
        """
        adds a grade
        :param gr: grade
        :return:
        """

        try:
            idd = gr.dis_id
            self.__disService.is_discipline(idd)
            ids = gr.stud_id
            self.__studService.is_student(ids)
            self._validator.validate(gr)
            delete_list = list()
            delete_list.append(gr)

            self.__repo.add_gr(gr)
            fc_undo = FunctionCall(self.__repo.delete_mult_grades, delete_list)
            fc_redo = FunctionCall(self.__repo.add_mult_grades, delete_list)
            cope = CascadedOperation()
            cope.add(Operation(fc_undo, fc_redo))
            self.__undo_service.record_operation(cope)

        except Exception as ve:
            raise ve
        return gr

    def delete_grade_undo(self, sid, did, gr):
        """
        deletes a grade (undo/redo function)
        :param sid: student id
        :param did: discipline id
        :param gr: grade
        :return:
        """
        grade = Grade(sid, did, gr)
        self.__repo.delete_gr(grade)

    def delete_gr(self, gr):
        """
        deletes a grade
        :param gr:
        :return:
        """
        self.__repo.delete_gr(gr)

    def delete_grade(self, stud_id, dis_id, gr):
        """
        deletes a grade
        :param stud_id: student id
        :param dis_id: discipline id
        :param gr: the grade that the given student has at the given discipline
        :return:
        """
        grade_list = self.__repo.show_grade()
        k = 0
        i = 0
        s = 0
        delete_list = list()
        while i < len(grade_list):
            if int(grade_list[i].stud_id) == int(stud_id) and int(grade_list[i].dis_id) == int(dis_id) \
                    and int(grade_list[i].grade) == int(gr):
                s = grade_list[i]
                delete_list.append(s)
                k = 1
            i += 1
        if k == 0:
            raise Exception("No grade to be removed")
        else:
            try:
                self.__repo.delete_gr(s)
                fc_undo = FunctionCall(self.__repo.add_mult_grades, delete_list)
                fc_redo = FunctionCall(self.__repo.delete_mult_grades, delete_list)
                cope = CascadedOperation()
                cope.add(Operation(fc_undo, fc_redo))
                self.__undo_service.record_operation(cope)

            except Exception as ve:
                raise ve
        return s

    def delete_studgrade(self, stud_id):
        """
        used for deleting the grades of a student that was removed from the repository
        :param stud_id: the student that was removed
        :return:
        """
        deleted_list = list()
        grade_list = self.__repo.show_grade()
        i = 0
        l = 0
        while i < len(grade_list):
            if int(grade_list[i].stud_id) == int(stud_id):
                s = grade_list[i]
                deleted_list.append(s)
                l = 1
            i += 1
        if l > 0:
            try:
                self.__repo.delete_mult_grades(deleted_list)
                fc_undo = FunctionCall(self.__repo.add_mult_grades, deleted_list)
                fc_redo = FunctionCall(self.__repo.delete_mult_grades, deleted_list)
                cope = CascadedOperation()
                cope.add(Operation(fc_undo, fc_redo))

                fname = self.__studService.search_fn_stud_id(stud_id)
                sname = self.__studService.search_sn_stud_id(stud_id)
                fc_undo = FunctionCall(self.__studService.add_student_undo, stud_id, fname, sname)
                fc_redo = FunctionCall(self.__studService.delete_student_id, stud_id)
                cope.add(Operation(fc_undo, fc_redo))

                self.__undo_service.record_operation(cope)
            except Exception as ve:
                raise ve

    def delete_disgrade(self, dis_id):
        """
        used for deleting the graded of all students for a discipline that was removed from the repository
        :param dis_id: the discipline that was removed
        :return:
        """
        deleted_list = list()
        grade_list = self.__repo.show_grade()
        l = 0
        i = 0
        while i < len(grade_list):
            if int(grade_list[i].dis_id) == int(dis_id):
                s = grade_list[i]
                deleted_list.append(s)
                l = 1
            i += 1
        if l > 0:
            try:
                self.__repo.delete_mult_grades(deleted_list)
                fc_undo = FunctionCall(self.__repo.add_mult_grades, deleted_list)
                fc_redo = FunctionCall(self.__repo.delete_mult_grades, deleted_list)
                cope = CascadedOperation()
                cope.add(Operation(fc_undo, fc_redo))

                name = self.__disService.search_n_dis_id(dis_id)
                fc_undo = FunctionCall(self.__disService.add_dis_undo, dis_id, name)
                fc_redo = FunctionCall(self.__disService.delete_discipline_id, dis_id)
                cope.add(Operation(fc_undo, fc_redo))

                self.__undo_service.record_operation(cope)
            except Exception as ve:
                raise ve

    def modify_grade(self, stud_id, dis_id, o, n):
        """
        modifies a grade (undo/redo function)
        :param stud_id: student id
        :param dis_id: discipline id
        :param o: old grade
        :param n: new grade
        :return:
        """
        self.__repo.mod_grade(stud_id, dis_id, o, n)

    def mod_grade(self, stud_id, dis_id, o, n):
        """
        modifies a grade
        :param stud_id: student id
        :param dis_id: discipline id
        :param o: old grade
        :param n: new grade
        :return:
        """
        grade_list = self.__repo.show_grade()
        k = 0
        i = 0
        while i < len(grade_list):
            if int(grade_list[i].stud_id) == int(stud_id) and int(grade_list[i].dis_id) == int(dis_id) \
                    and int(grade_list[i].grade) == int(o):

                k = 1
            i += 1
        if k == 0:
            raise Exception("No grade to be modified")
        else:
            try:
                self.__repo.mod_grade(stud_id, dis_id, o, n)
                fc_undo = FunctionCall(self.modify_grade, stud_id, dis_id, n, o)
                fc_redo = FunctionCall(self.modify_grade, stud_id, dis_id, o, n)
                cope = CascadedOperation()
                cope.add(Operation(fc_undo, fc_redo))
                self.__undo_service.record_operation(cope)

            except Exception as ve:
                raise ve

    def generate_grades(self):
        """
        generates grades for each student at each discipline
        :return:
        """
        stud_list, length_stud = self.__studService.show_stud_len()
        dis_list, length_dis = self.__disService.show_dis_len()
        i = 0

        while i < length_stud:
            j = 0
            si = stud_list[i].id
            while j < length_dis:

                di = dis_list[j].id
                gr = randint(1, 10)
                grade = Grade(si, di, gr)
                self.__repo.add_gr(grade)
                j += 1
            i += 1

    def show_grade(self):
        """

        :return: the list of grades
        """
        return self.__repo.show_grade()

    def sort_gr(self):
        grade_list = self.__repo.show_grade()
        sorted_list = list()
        k = 0
        for s in grade_list:
            k = 1
            sorted_list.append([s.stud_id, s.dis_id, s.grade])
        if k == 0:
            raise Exception("Grade list is empty")
        else:
            sorted_list = gnomeSort(sorted_list, len(sorted_list), 1)
            return gnomeSort(sorted_list, len(sorted_list), 0)

    def average_grades_stud(self):
        student_list, length_s = self.__studService.show_stud_len()
        discipline_list, length_d = self.__disService.show_dis_len()
        gr_list = self.sort_gr()
        failing_list = list()
        i = 0
        while i < length_s:
            sid = student_list[i].id
            j = 0
            while j < length_d:
                did = discipline_list[j].id
                su = int(0)
                k = int(0)
                l = 0
                while l < len(gr_list):
                    if int(gr_list[l][0]) == int(sid) and int(gr_list[l][1]) == int(did):
                        su += int(gr_list[l][2])
                        k += 1
                    l += 1
                if k != 0:
                    av = su / k
                    failing_list.append([student_list[i].id, student_list[i].fname, student_list[i].sname,
                                             discipline_list[j].id, discipline_list[j].name, av])
                j += 1
            i += 1
        return failing_list

    def students_failing(self):
        s = []
        tempArray = []
        tempArray = filter(tempArray, self.average_grades_stud())
        for i in range(0, len(tempArray)):
            if tempArray[i][5] < 5:
                s.append([tempArray[i][0], tempArray[i][1], tempArray[i][2], tempArray[i][3], tempArray[i][4]])  # we put in the list the student
        return s

    def average_students(self):
        student_list, length_s = self.__studService.show_stud_len()
        discipline_list, length_d = self.__disService.show_dis_len()
        grade_list = self.sort_gr()
        best_list = list()
        i = 0
        while i < length_s:
            sid = student_list[i].id
            su = int(0)
            k = int(0)
            j = 0
            while j < length_d:
                did = discipline_list[j].id
                l = 0
                while l < len(grade_list):

                    if int(grade_list[l][0]) == int(sid) and int(grade_list[l][1]) == int(did):
                        su += int(grade_list[l][2])
                        k += 1
                    l += 1
                j += 1
            if k != 0:
                av = su / k
                best_list.append([student_list[i].id, student_list[i].fname, student_list[i].sname, av])
            i += 1
        return best_list

    def best_students(self):
        s = []
        tempArray = []
        tempArray = filter(tempArray, self.average_students())
        for i in range(0, len(tempArray)):
            if tempArray[i][3] > 5:
                s.append([tempArray[i][0], tempArray[i][1], tempArray[i][2], tempArray[i][3]])  # we put in the list the student
        return gnomeSort(s, len(s), 3)

    def dis_average(self):
        discipline_list, length_d = self.__disService.show_dis_len()
        grade_list = self.__repo.show_grade()
        dis_average = list()
        i = 0
        while i < length_d:
            did = discipline_list[i].id
            su = 0
            k = 0
            l = 0
            while l < len(grade_list):
                if int(grade_list[l].dis_id) == int(did):
                    su += int(grade_list[l].grade)
                    k += 1
                l += 1
            if k != 0:
                av = su / k
                dis_average.append([discipline_list[i].id, discipline_list[i].name, av])
            i += 1
        return dis_average

    def discipline_average(self):
        s = []
        tempArray = []
        tempArray = filter(tempArray, self.dis_average())
        for i in range(0, len(tempArray)):
             s.append([tempArray[i][0], tempArray[i][1], tempArray[i][2]])  # we put in the list the student
        return gnomeSort(s, len(s), 22)

class GradeServTest(unittest.TestCase):
    def setup(self, grade_repo, student_service, discipline_service, undo_service):
        """
        Runs before any of the tests
        Used to set up the class so that tests can be run

        :return: None
        """
        self._grepo = grade_repo
        self._srepo = student_service
        self._drepo = discipline_service
        self._ser = GradeSer(grade_repo, student_service, discipline_service, undo_service)
        self._grade = Grade(258, 1978, 5)
        self._repo.add_grade(self._grade)

    def test_repo_elem(self):
        # test add
        self.assertEqual(len(self._repo), 21)
        self._ser.add_grade(Grade(425, 2589, 8))
        self._ser.add_grade(Grade(559, 2597, 2))
        self.assertEqual(len(self._repo), 23)

        # test delete
        last_grade = self._repo[len(self._repo)-1]
        stud_id_lg = last_grade.stud_id
        dis_id_lg = last_grade.dis_id
        grade_last_grade = last_grade.grade
        self._ser.delete_grade(stud_id_lg, dis_id_lg, grade_last_grade)
        self.assertEqual(len(self._repo), 7)

        # test update
        last_grade = self._repo[len(self._repo)-1]
        stud_id_lg = last_grade.stud_id
        dis_id_lg = last_grade.dis_id
        grade_last_grade = last_grade.grade
        self._repo.mod_grade(stud_id_lg, dis_id_lg, grade_last_grade, 9)
        last_grade = self._repo[len(self._repo) - 1]
        grade_last_grade = last_grade.grade
        self.assertEqual(grade_last_grade, 9)

    def tearDown(self) -> None:
        """
        Runs after all the tests have completed
        Used to close the test environment (clase files, DB connections, deallocate memory)

        :return: None
        """
        self._repo = None

def filter(tempArray,currentArray):
    for e in currentArray:
        tempArray.append(e)
    return tempArray


""" summarize a biff alert """

__all__ = ["summarize"]

def plural(num, single="", many="s"):
    """ plural """
    return single if num == 1 else many

def sane_fname(mbox):
    """ sanitise the mailbox name for notification """
    ret = str(mbox)
    if "." in ret:
        ret = ret.split(".")[-1]
    return ret

def summarize(diff):
    """ summarize """
    fhavenew = sorted(sane_fname(f) for f in diff.keys())
    mcount = sum(len(u) for u in diff.values())
    if len(fhavenew) == 1:
        fhavenew = fhavenew[0]
        summary = "folder %s has %s new message%s" % (fhavenew,
                                                      mcount,
                                                      plural(mcount))
    else:
        fhavenew = ", ".join(fhavenew[:-1]) + " & " + fhavenew[-1]
        summary = "folders %s have %s new message%s" % (fhavenew,
                                                        mcount,
                                                        plural(mcount))
    return summary


import sys
import math
from collections import deque

Towers = deque()

number_of_steps = 0

sys.setrecursionlimit(3090)

def initialize(n, K) :
    for i in range(K) :
        X = deque()
        if (i == 0) :
            for j in range(n) :
                X.append(j+1)
        Towers.append(X)

def is_everything_legal(K) :
    result = True
    for i in range(K) :
        for j in range(len(Towers[i])) :
            for k in range(j,len(Towers[i])) :
                if (Towers[i][k] < Towers[i][j]) :
                    result = False
    return(result)

def move_top_disk(source, dest, K):
    global number_of_steps
    number_of_steps = number_of_steps + 1
    x = Towers[source].popleft()
    Towers[dest].appendleft(x)
    if (True == is_everything_legal(K)):
        y = " (Legal)"
    else:
        y = " (Illegal)"

    print("Move disk " + str(x) + " from Peg " + str(source + 1) + " to Peg " + str(dest + 1) + y)

def move_using_k_pegs(number_of_disks, source, dest, intermediates, K):
    if number_of_disks == 1:
        move_top_disk (source, dest, K)
    else:
        if K-2 > 1:
            k = math.floor(number_of_disks/2)
        else:
            k = number_of_disks - 1
        move_using_k_pegs(k, source, intermediates[0], intermediates[1:K-2] + [dest], K)
        move_using_k_pegs(number_of_disks-k, source, dest, intermediates[1:K-2], K-1)
        move_using_k_pegs(k, intermediates[0], dest, [source] + intermediates[1:K-2], K)

def print_peg_state(m) :
    global number_of_steps
    print ("-----------------------------")
    print ("State of Peg " + str(m+1) + " (Top to Bottom): " + str(Towers[m]))
    print ("Number of Steps = " + str(number_of_steps))
    print ("-----------------------------")

def main():
    if len(sys.argv[1:]) != 2:
        print('Run using "py hanoi.py N k"')
        return
    n = int(sys.argv[1])
    K = int(sys.argv[2])
    initialize(n, K)
    print_peg_state(0)
    move_using_k_pegs(n,0,K-1,[i for i in range(1,K-1)], K)
    print_peg_state(K-1)

if __name__== "__main__":
    main()


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

"""Storage Model.

This is Storage Model of Kagisys
"""

import os

class StorageModel():
    """StorageModel for Kagisys."""

    def __init__(self):
        """Set file name."""
        self.file_name = "kagisys.toggle"

        """Move directry"""
        path = "/home/pi/project/kagisys_logic/"
        os.chdir(path)

    def update_file(self, data):
        """Update file to set data."""
        file_ = open(self.file_name, 'w')
        file_.write(data)
        file_.close()