import multiprocessing as mp
import numpy as np
import sys
import torch
import openai
import asyncio
import jsonlines
import random
import soundfile as sf
import os
from pathlib import Path
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
from pypinyin import lazy_pinyin, Style
from listen_chats import listen
from Wav2Lip.inference import sync_lip, load_model
from NeMo.nemo.collections.tts.models import HifiGanModel, FastPitchModel
from transformers import AutoTokenizer, AutoModelForMaskedLM

class AiStreamer:
    def __init__(self, api_key, args):
        self.api_key = api_key
        self.args = args
        self.wav2lip_model = None
        self.tokenizer = None
        self.bert_model = None
        self.vocoder_model_pt = None
        self.spec_gen_model = None
        self.frontend = None
        self.am_predictor = None
        self.voc_predictor = None
        self.content_id = 0  # 当前音视频文件生成后的名称（编号），根据编号进行播放

        # 初始化预设video路径
        self.not_talking_videos_source_path = os.path.join(os.path.join(self.args.video_source, self.args.streamer),
                                                           'not_talking_source')
        self.talking_videos_source_path = os.path.join(os.path.join(self.args.video_source, self.args.streamer),
                                                       'talking_source')
        self.sync_result_path = os.path.join(os.path.join(self.args.video_source, self.args.streamer),
                                             'sync_result')

        # 建立输入的通信队列
        self.normal_chats = mp.Queue()  # 普通弹幕队列
        self.sc_chats = mp.Queue()  # sc留言队列
        self.questions = mp.Queue()  # 结果队列,格式为(question: result_avi path})

    def listen_chats(self):
        """实时监听直播间的普通弹幕和付费SC留言"""
        asyncio.run(listen(self.args.room_id, self.normal_chats, self.sc_chats))

    def read_jsonl(self):
        """
        读取jsonl文件内容
        :returns
            contents: [list]
        """
        jsonl_name = self.args.streamer + '.jsonl'
        jsonl_path = os.path.join(self.args.text_input, jsonl_name)  # jsonl文件的相对路径
        with open(jsonl_path, 'r+', encoding='utf8') as f:
            contents = []
            for items in jsonlines.Reader(f):
                contents.append(items)
        return contents

    def write_jsonl(self, question):
        """写入jsonl文件"""
        contents = self.read_jsonl()
        contents[-1]["content"] = question  # 写入新问题
        jsonl_name = self.args.streamer + '.jsonl'
        jsonl_path = os.path.join(self.args.text_input, jsonl_name)  # jsonl文件的相对路径
        with open(jsonl_path, 'w+', encoding='utf8') as f:
            for content in contents:
                f.write(str(content).replace('\'', '\"') + '\n')

    def get_inputs_from_typing(self, question):
        """替代音频输入，将问题写入jsonl文件中"""
        self.write_jsonl(question)

    def transcribe_audio(self):
        """对指定音频进行语音识别"""
        audio_file_path = os.path.join(os.path.join(self.args.audio_input, self.args.streamer), 'audio_input.m4a')
        audio_file = open(audio_file_path, 'rb')
        transcript = openai.Audio.transcribe("whisper-1", audio_file)
        sentences = transcript['text'].split(',')
        sentences[0] = self.args.streamer  # 因为识别不准所以统一替换成指定的streamer名字
        transcript = ','.join(sentences)
        return transcript

    def generate_text(self):
        """从Inputs路径下的对应jsonl文件中读取用户的提问, 根据不同模型调用不同API并返回答案"""
        # openai.api_key = self.api_key
        # # 区分3.5模型和其他老模型，因为接口调用方式不一样
        # if self.args.model_name == 'gpt-3.5-turbo':
        #     massages = self.read_jsonl()
        #     result = openai.ChatCompletion.create(
        #         model=self.args.model_name,
        #         messages=massages,
        #         temperature=0.4,
        #         top_p=1,
        #         n=1,
        #         presence_penalty=0.2,
        #         frequency_penalty=0.2
        #     )
        #     answer = result['choices'][0]['message']['content']
        #     print(f'回答：{answer}')
        answer = "对啊，好像我还能没看到上面中国主播啊或者网红啊去过对马岛，我可能也算是第一个去对马岛的了吧，到时候深度解密一下对马岛到底什么样儿啊，也没去过对马岛第一次去挺紧张的"
        return answer

    @ staticmethod
    def find_model(model_dir):
        """寻找model_dir下后缀在check_list的文件的文件"""
        path = ""
        check_list = ['pt', 'pth']
        for file in os.listdir(model_dir):
            suffix = file.split('.')[-1]
            if suffix in check_list:
                path = model_dir / file
                print(path)
        return path

    def text_preprocess(self, raw_text):
        punc_e2c = {',': '，',
                    '.': '。',
                    '?': '？',
                    '、': '，'}
        raw_text="".join([punc_e2c[i] if i in punc_e2c else i for i in raw_text])  # 按照punc_e2c对应关系转换中英文标点符号
        text = raw_text.replace(" ","").lower()

        with torch.no_grad():
            inputs = self.tokenizer("".join(text), return_tensors='pt')
            for i in inputs:
                inputs[i] = inputs[i].to(self.args.device)
            res = self.bert_model(**inputs, output_hidden_states=True)
            res = torch.cat(res['hidden_states'][-3:-2], -1)[0].cpu().numpy()

        initials = lazy_pinyin(raw_text, neutral_tone_with_five=False, style=Style.INITIALS, strict=False)
        finals = lazy_pinyin(raw_text, neutral_tone_with_five=False, style=Style.FINALS_TONE3)

        _vecs = []
        _text = []
        for _o in zip(zip(initials, finals), list(raw_text), res[1:-1]):
            _o, _c, _vec = _o
            if _o[0] != _o[1] and _o[0] != '':
                _text.extend(['@'+i for i in _o])
                _vecs.extend([_vec]*2)
            elif _o[0] != _o[1] and _o[0] == '':
                _text.append('@'+_o[1])
                _vecs.append(_vec)
            else:
                _text.extend(list(_o[0]))
                _vecs.extend([_vec]*len(_o[0]))

        _vecs = np.stack([res[0]] + _vecs + [res[-1]])
        bert = torch.tensor(_vecs.T[None]).to(self.args.device)
        phoneme = " ".join(_text)

        return bert, phoneme

    def generate_audio(self, input_text):
        """paddlespeech本地推理"""
        output_dir = Path(self.args.audio_output)
        output_dir.mkdir(parents=True, exist_ok=True)

        # 开始推理
        bert_feats, phoneme = self.text_preprocess(raw_text=input_text)
        with torch.no_grad():
            parsed = self.spec_gen_model.parse(str_input=phoneme, normalize=False)
            res = self.spec_gen_model.generate_spectrogram(
                tokens=parsed, pace=1,
                bert_feats=bert_feats,
            )
            spectrogram = res
            audio = self.vocoder_model_pt.convert_spectrogram_to_audio(spec=spectrogram)

        spectrogram = spectrogram.to('cpu').numpy()[0]
        audio = audio.to('cpu').numpy()

        # 保存文件
        wav_id = str(self.content_id) + ".wav"
        sf.write(output_dir / wav_id, audio, samplerate=22050)
        print(f'TTS数据生成完毕...')

    def generate_video(self):
        # 随机挑选一个视频进行合成
        talking_videos_source_list = self.load_video(self.talking_videos_source_path)
        face_index = random.randrange(len(talking_videos_source_list))
        face = talking_videos_source_list[face_index]
        sync_lip(ckpt=self.wav2lip_model,
                 face=face,
                 audio_input=os.path.join(self.args.audio_output, str(self.content_id) + '.wav'),
                 outfile=os.path.join(self.sync_result_path, str(self.content_id) + '.avi'))
        print(f'video数据生成完毕...')

    @staticmethod
    def load_video(video_path):
        """创建进程队列保存VideoCapture对象"""
        video_list = []
        for file_name in os.listdir(video_path):
            video = os.path.join(video_path, file_name)
            video_list.append(video)  # 先把所有文件的cap对象都读取到列表内
        return video_list

    def load_next_video(self):
        """给出主窗口下一个待播放的视频地址"""
        not_talking_video_list = self.load_video(self.not_talking_videos_source_path)
        next_video_index = random.randrange(len(not_talking_video_list))
        next_video = not_talking_video_list[next_video_index]
        return next_video

    def generate_answer(self, question):
        """从Inputs路径下的对应jsonl文件中读取用户的提问, 根据不同模型调用不同API并返回答案"""
        # 修改问题格式
        uname, msg = question[0], question[1]
        processed_question = uname + '问：' + msg

        # 生成回答
        self.content_id += 1  # 确认编号
        self.get_inputs_from_typing(question=processed_question)  # 将问题写入jsonl中的message模板
        text_answer = self.generate_text()
        self.generate_audio(input_text=text_answer)
        self.generate_video()
        self.questions.put((processed_question, os.path.join(self.sync_result_path, str(self.content_id) + '.avi')))  # 队列存入回答

    def processing_chats(self):
        """预加载模型并监听输入队列"""
        # 预加载lip_sync model
        if self.wav2lip_model is None:
            self.wav2lip_model = load_model(self.args.wav2lip_model)

        # 预加载TTS模型
        if self.tokenizer is None:
            self.tokenizer = AutoTokenizer.from_pretrained("hfl/chinese-roberta-wwm-ext-large")
        if self.bert_model is None:
            self.bert_model = AutoModelForMaskedLM.from_pretrained("hfl/chinese-roberta-wwm-ext-large").to(self.args.device).eval()
        if self.vocoder_model_pt is None:
            self.vocoder_model_pt = HifiGanModel.load_from_checkpoint(checkpoint_path=self.args.hifigan).to(self.args.device).eval()
        if self.spec_gen_model is None:
            self.spec_gen_model = FastPitchModel.load_from_checkpoint(checkpoint_path=self.args.fs2).to(self.args.device).eval()

        # 主循环
        while True:
            if not self.sc_chats.empty():  # 优先读取sc弹幕的留言
                sc_question = self.sc_chats.get()
                print(f'SC留言:{sc_question}')
                self.generate_answer(question=sc_question)
            if not self.normal_chats.empty() and self.sc_chats.empty():  # 如果sc留言队列为空则开始处理普通弹幕
                normal_question = self.normal_chats.get()
                print(f'普通留言:{normal_question}')
                self.generate_answer(question=normal_question)

    def start_stream(self):
        """使用进程启动各个模块对消息列表进行监听"""
        # 启动 listen_chats 进程
        listen_chats_process = mp.Process(target=self.listen_chats)
        listen_chats_process.start()

        # 启动 processing_chats 进程
        processing_chats_process = mp.Process(target=self.processing_chats)
        processing_chats_process.start()


import configparser
import json
import os

class Configure:
	def __init__(self, config_path):
		self.config_path = config_path
		self.config = configparser.ConfigParser()

		if not os.path.isfile(self.config_path):
			self.config['General'] = {
				'from': 'English',
				'to': 'Russian',
				'translator': 'Google',
				'recognitor': 'easyocr'
			}
			self.config['Font'] = {
				'font': 'Calibri',
				'font_size': 9
			}
			self.config['Shortcuts'] = {
				'select_area': 'alt+z',
				'repeat_area': 'alt+x'
			}

			self.config['Output'] = {
				'window': '1',
				'console': '0',
				'clipboard': '0',
				'original': '0',
				'start_on_new_line': '0',
				'ts2st_server': '0',
				'ts2st_server_ip': ''
			}
			self.config['Change_list'] = {
				'json': '{}'
			}

			self.write()

		self.config.read(self.config_path, encoding='utf-8')

	def read(self, section, key):
		return self.config[section][key]

	def update_dictionary(self, section, key, dic):
		_json = json.dumps(dic, ensure_ascii=False)
		self.update(section, key, _json)

	def read_dictionary(self, section, key):
		return json.loads(self.read(section, key))

	def	update(self, section, key, arg):
		self.config[section][key] = arg
		self.write()

	def write(self):
		with open(self.config_path, 'w+', encoding='utf-8') as configfile:
			self.config.write(configfile)

if __name__ == '__main__':
	config = Configure('./config.ini')


from django.conf import settings
from django.db.models.signals import post_save
from django.utils.text import slugify
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.db.models import Sum
from django.urls import reverse
from datetime import date

class User(AbstractUser):
    is_taker = models.BooleanField(default=True)
    is_master = models.BooleanField(default=False)
    is_project_manager = models.BooleanField(default=False)
    email = models.EmailField('email address', max_length=254, unique=True)

    def __str__(self):
        return self.username

class Profile(models.Model):
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    name = models.CharField(
        max_length=255, help_text="Your full name")
    image = models.ImageField(
        default='profile_default.jpg', upload_to='profile_pics')
    slug = models.SlugField(max_length=30, unique=True)
    about = models.TextField(default="A brief description about me")
    joined = models.DateTimeField("Date Joined", auto_now_add=True)
    wallet_address = models.CharField(
        max_length=35, help_text="Pi wallet address")
    telegram_id = models.CharField(max_length=100, null=True, blank=True, default="username",
                                   help_text="please input your correct telegram username to connect with friends, fellow quiz takers, and quiz masters")

    def __str__(self):
        return self.user.get_username()

class Payment(models.Model):
    transaction_id = models.CharField(max_length=50)
    user = models.ForeignKey(settings.AUTH_USER_MODEL,
                             on_delete=models.SET_NULL, blank=True, null=True)
    amount = models.FloatField()
    timestamp = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.user.username

def post_user_created_signal(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)

post_save.connect(post_user_created_signal, sender=User)


import schemas
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from db.database import get_db
from db import models
import uuid

app = APIRouter()

# To rate a user
@app.post('/user_review/', tags=["ratings"])
def create_user_review(user_review: schemas.user_rate, db: Session = Depends(get_db)):
    StarRating = db.query(models.User_Review).filter(models.User_Review.reviewer_id == user_review.reviewer_id).first()
    if StarRating is None:
        new_rating = models.User_Review(
            id=uuid.uuid4(),
            reviewee_id=user_review.reviewee_id,
            reviewer_id=user_review.reviewer_id,
            rate=user_review.rate
        )
        db.add(new_rating)

        db.commit()

        StarRating = db.query(models.User_Review).filter(
            models.User_Review.reviewer_id == user_review.reviewer_id).first()

        return {"response": StarRating}
    else:
        return {"response": "you are aleady rated"}

# To get average rate of a user
@app.post('/average_user_rating', tags=["ratings"])
def average_user_review(review: schemas.user_rate, db: Session = Depends(get_db)):
    ratings = db.query(models.User_Review.rate).filter(models.User_Review.reviewee_id == review.reviewee_id).all()
    if ratings:
        total_user_rate = [sum(rate) for rate in ratings]
        total_count_rate = [len(rate) for rate in ratings]
        average = 0 if len(total_user_rate) == 0 else (float(sum(total_user_rate)) / len(total_count_rate))
        return {"average": average, "response": ratings}
    else:
        return {"response": "Invalid id"}


#!/usr/bin/env python
# coding: utf-8

# In[2]:

# Importing required libraries for this Project.

import pandas as pd
import numpy as np
import seaborn as sns

import matplotlib.pyplot as plt
import matplotlib.mlab as mlab
import matplotlib
plt.style.use('ggplot')
from matplotlib.pyplot import figure

get_ipython().run_line_magic('matplotlib', 'inline')
matplotlib.rcParams['figure.figsize'] = (12,8)

pd.options.mode.chained_assignment = None

# Now need to read the data file from system

df = pd.read_csv(r'D:\movies2.csv')

# In[42]:

# Now lets look at the data
df

# In[46]:

# Serching for Missing data
for col in df.columns:
    pct_missing=np.mean(df[col].isnull())
    print(col,pct_missing)

# In[48]:

# Data Types of the column

df.dtypes

# In[57]:

# Creating another row of  year  as 'Correctyear'. we can see some of the year's from release date and year are no matching

df['correctyear']=df['released'].astype(str).str[6:10]
df

# In[64]:

pd.set_option('display.max_rows',None)
df.sort_values(by='gross' ,ascending=False)

# In[65]:

# Drop any duplictes

df.drop_duplicates()

# In[36]:

# Plot Bugget vs gross using seabord
plt.scatter(x=df['budget'], y=df['gross'], alpha=0.5)
plt.title('Budget vs Gross Earnings')
plt.xlabel('Gross Earnings')
plt.ylabel('Budget for Film')
plt.show()

sns.regplot(x="gross", y="budget",data=df, line_kws={"color":"blue"})

# In[13]:

# Plot Score vs gross using seabord
sns.regplot(x="score", y="gross", data=df , line_kws={"color":"black"})

# In[14]:

# Correlation Matrix between all numeric columns

df.corr()
# default  correlation method is pearson

# In[18]:

# Visualization corelation

correlation_matrix = df.corr()
sns.heatmap(correlation_matrix, annot = True)
plt.title("Correlation Feature for Numeric Features")
plt.xlabel("Movie features")
plt.ylabel("Movie features")
plt.show()

# ### High Corelation between Gross and Budget (0.71)

# In[25]:

df_numerized=df
for col_name in df_numerized.columns:
    if(df_numerized[col_name].dtype =='object'):
        df_numerized[col_name] = df_numerized[col_name].astype('category')
        df_numerized[col_name]=df_numerized[col_name].cat.codes

df_numerized

# In[27]:

df_numerized.corr()

# In[28]:

correlation_matrix = df_numerized.corr()

sns.heatmap(correlation_matrix, annot = True)

plt.title("Correlation matrix for Movies")

plt.xlabel("Movie features")

plt.ylabel("Movie features")

plt.show()

# In[44]:

pd.set_option('display.max_rows',None)
correlation_mat=df_numerized.corr()
corr_pairs=correlation_mat.unstack()
corr_pairs

# In[43]:

pd.set_option('display.max_rows',None)
sorted_pairs=corr_pairs.sort_values()
sorted_pairs

# In[42]:

pd.set_option('display.max_rows',None)
high_corr=sorted_pairs[(sorted_pairs)>.05]
high_corr

# ###  Votes and Budget have the higest corellation to gross earnings

# In[4]:

genre_gross=df.groupby(['genre'])['gross'].sum()
genre_gross=genre_gross.to_frame()
genre_gross.reset_index(inplace=True)

# In[5]:

fig = plt.figure(figsize=(18,8))
plt.bar(genre_gross['genre'],genre_gross['gross'])
plt.title("Genre vs Gross",size=30)
plt.xlabel("Genres",size=15)
plt.ylabel("Amount",size=15)
plt.show()

# In[6]:

sns.pairplot(df,diag_kind='hist')

# In[7]:

for col in df.columns:
    if df[col].dtype !="object":
        sns.distplot(df[col],bins=20)
        plt.show()

# In[ ]:

# In[ ]:

# In[ ]:

# In[ ]:

# In[ ]:

# In[ ]:

# In[ ]:

# In[ ]:

# In[ ]:

# In[ ]:

# In[ ]:

# In[ ]:

# In[ ]:

# In[ ]:


import pint
import numpy as np

def belt_drive():
    """
    Cálculo de transmisión a correa.

    Ecuaciones y modelos tomados de
    """
    units = pint.UnitRegistry()

    # Cálculo de torque mínimo de motor
    alpha = np.arctan(1.0/5.0)  # Inclinación máxima de rampas (Ley 24.314)
    Pmax = 8 * units.kgf  # Peso nominal
    D = 9.6 * units.cm  # Diámetro de rueda
    Cmin = Pmax * np.sin(alpha) * D / 2

    # Cálculo de velocidad nominal mínima de motor
    vmin = 1 * units.m / units.s
    Nmin = (vmin / (D / 2)).to(units.rpm)

    # Caracteristicas del motorreductor DC MR08D 24v 24:1
    # (con motor DC Mobuchi RS-555SH-2670)
    N = 266 * units.rpm  # Velocidad nominal
    C = 6.8 * units.kgf * units.cm  # Torque a máxima eficiencia
    assert 2 * C > Cmin
    assert N > Nmin

    sf = 1.5  # Factor de servicio nominal
    C_peak = (C * sf).to(units.N * units.m)
    # Se utiliza correa GT3 3 mm de pitch, 6 mm de ancho
    p = 3 * units.mm  # Pitch de correa
    n1 = n2 = 20  # Dientes por polea
    pd = n1 * p / np.pi  # Delta angular

    d_left = 100 * units.mm
    d_right = 80 * units.mm

    W = 6 * units.mm
    h = 2.41 * units.mm - 1.14 * units.mm
    C_rated = 0.95 * 1.26 * units.N * units.m
    assert C_rated > C_peak  # Verificación de torque

    # Cálculo de tensión de correa
    T = 2 * (
        0.812 * C_peak.to(units.lbf * units.inch) / pd.to(units.inch) +
        0.077 * units.lbf * units.minute**2 / units.ft**2 * ((pd * N).to(units.ft / units.minute)/1000)**2
    ).to(units.N)

    # Cálculo de dientes y ángulo de contacto
    EA = 30000 * units.lbf * (0.82 * W / (1 * units.inch))
    ε = (T / EA).to('dimensionless')

    n_left = np.floor((1 - 2 * ε) * ((n1 + n2)/2 + 2 * d_left/p))
    L_left = n_left * p
    n_right = np.floor((1 - 2 * ε) * ((n1 + n2)/2 + 2 * d_right/p))
    L_right = n_right * p

    print('\n'.join('{} = {}'.format(name, value)
                    for name, value in locals().items()
                    if isinstance(value, units.Quantity)))

if __name__ == '__main__':
    belt_drive()


"""
OpenGL Triangle
This is the base for 3D.  It is made up of three Vertexes that
can contain up to four attributes each.
Position, required, a Vector3
Normal, calculated, a Vector3
TexCoord, optional, a Vector2
Color, optional, a Vector3
"""

import math

from .matrix import Matrix
from .vector import Vector3

class Triangle:
    """
    parameters
        list[Vector3]
            positions
        list[Vector3]
            colors
        list[Vector2]
            texcoords
    """

    __slots__ = ["__colors", "__normals", "__positions", "__texcoords"]

    def __init__(self, positions, colors=None, texcoords=None):

        # Position
        if (
            isinstance(positions, list) and len(positions) == 3 and
            all(type(p).__name__ == "Vector" for p in positions)
        ):
            self.__positions = positions
        else:
            raise ValueError("Positions must be a List of Vectors.")

        # Colors
        if colors is not None:
            if (
                isinstance(colors, list) and len(colors) == 3 and
                all(type(c).__name__ == "Vector" for c in colors)
            ):
                self.__colors = colors
            else:
                raise ValueError("Colors must be a List of Vectors.")
        else:
            self.__colors = None

        # TexCoords
        if texcoords is not None:
            if (
                isinstance(texcoords, list) and len(texcoords) == 3 and
                all(type(t).__name__ == "Vector" for t in texcoords)
            ):
                self.__texcoords = texcoords
            else:
                raise ValueError("TexCoords must be a List of Vectors.")
        else:
            self.__texcoords = None

        # Calculate Normals
        """
        So for a triangle p1, p2, p3, if the vector U = p2 - p1
        and the vector V = p3 - p1 then the normal N = U X V and
        can be calculated by:
        Nx = UyVz - UzVy
        Ny = UzVx - UxVz
        Nz = UxVy - UyVx
        """
        position_U = self.__positions[1] - self.__positions[0]
        position_V = self.__positions[2] - self.__positions[0]
        self.__normals = Vector3(
            (position_U.Y * position_V.Z) - (position_U.Z * position_V.Y),
            (position_U.Z * position_V.X) - (position_U.X * position_V.Z),
            (position_U.X * position_V.Y) - (position_U.Y * position_V.X),
        )

    def __repr__(self):
        """
        returns
            __str__
        """
        return self.__str__()

    def __str__(self):
        """
        returns
            string representation of our triangle.
        """
        return str(self.get_vertex_data())

    def __eq__(self, other):
        """
        parameters
            Triangle
        returns
            bool of whether both triangles have the same vertexes.
        """
        if not type(other).__name__ == "Triangle":
            return False
        return all(other.has_position(p) for p in self.__positions)

    def __mul__(self, other):
        """
        parameters
            Matrix
        returns
            Triangle
        """
        # Multiplying against a Matrix.
        if type(other).__name__ == "Matrix":
            return Triangle(
                [
                    Vector3(
                        *(other * p.get_matrix()).get_as_list()
                    ) for p in self.__positions
                ],
                self.__colors,
                self.__texcoords,
            )
        return self

    def get_colors(self):
        """
        returns
            List<Vector3> colors
        """
        return self.__colors

    def get_normals(self):
        """
        returns
            List<Vector3> normals
        """
        return self.__normals

    def get_offset(self, offset):
        """
        parameters
            Vector3
        returns
            Triangle
        """
        if type(offset).__name__ == "Vector":
            return Triangle(
                [p + offset for p in self.__positions],
                self.__colors,
                self.__texcoords
            )
        return self

    def get_positions(self):
        """
        returns
            List<Vector3> positions
        """
        return self.__positions

    def get_rotation(self, yaw=0.0, pitch=0.0, roll=0.0):
        """
        parameters
            float
                yaw, or z
            float
                pitch, or y
            float
                roll, or z
        returns
            Triangle
        """

        if (
            not isinstance(yaw, (float, int)) or
            not isinstance(pitch, (float, int)) or
            not isinstance(roll, (float, int))
        ):
            ValueError("Yaw/Pitch/Roll must be a Float or Int")

        # Convert degrees to radians
        pitch_radians = math.radians(pitch)
        roll_radians = math.radians(roll)
        yaw_radians = math.radians(yaw)

        # Pitch, or Y
        pitch_matrix = Matrix(
            3, 3,
            [
                math.cos(pitch_radians), 0.0, math.sin(pitch_radians),
                0.0, 1.0, 0.0,
                -math.sin(pitch_radians), 0.0, math.cos(pitch_radians),
            ]
        )

        # Roll, or X
        roll_matrix = Matrix(
            3, 3,
            [
                1.0, 0.0, 0.0,
                0.0, math.cos(roll_radians), -math.sin(roll_radians),
                0.0, math.sin(roll_radians), math.cos(roll_radians),
            ]
        )

        # Yaw, or Z
        yaw_matrix = Matrix(
            3, 3,
            [
                math.cos(yaw_radians), -math.sin(yaw_radians), 0.0,
                math.sin(yaw_radians), math.cos(yaw_radians), 0.0,
                0.0, 0.0, 1.0,
            ]
        )

        # Order is Yaw * Pitch * Roll
        rotation_matrix = yaw_matrix * pitch_matrix * roll_matrix

        # Return a new Triangle
        return self * rotation_matrix

    def get_scaled(self, scale=1.0):
        """
        """
        """
        parameters
            Int, or Float
        returns
            Triangle
        """
        if isinstance(scale, (int, float)):
            return self * scale
        return self

    def get_texcoords(self):
        """
        returns
            List<Vector2> texcoords
        """
        return self.__texcoords

    def get_transformed(
        self,
        offset=Vector3(0.0, 0.0, 0.0),
        yaw=0.0, pitch=0.0, roll=0.0,
        scale=1.0,
    ):
        """
        parameters
            Vector3
                offset
            float
                yaw, or z
            float
                pitch, or y
            float
                roll, or x
        returns
            Triangle
        """
        return self.get_scaled(
                scale
            ).get_rotation(
                yaw, pitch, roll
            ).get_offset(
                offset
            )

    def get_vertex_data(
        self, positions=True, normals=True,
        colors=True, texcoords=True,
        offset=Vector3(0.0, 0.0, 0.0),
        yaw=0.0, pitch=0.0, roll=0.0
    ):
        """
        parameters
            4 optional bools; positions, normals, colors, texcoords.
                These tell the getter to return these values or not.
            Vector3
                offset
            float
                yaw, or z
            float
                pitch, or y
            float
                roll, or x
        returns
            [[float x 6]] x 3 vertexes of this triangle.
        """

        # Get a Transformed Triangle
        transformed_triangle = self.get_transformed(
            offset, yaw, pitch, roll
        )

        vertex_data = []
        for i in range(0, 3):
            vertex = []
            if positions:
                vertex.extend(
                    transformed_triangle.get_positions()[i].get_tuple()
                )
            if normals:
                vertex.extend(
                    transformed_triangle.get_normals().get_tuple()
                )
            if colors and self.__colors is not None:
                vertex.extend(
                    transformed_triangle.get_colors()[i].get_tuple()
                )
            if texcoords and self.__texcoords is not None:
                vertex.extend(
                    transformed_triangle.get_texcoords()[i].get_tuple()
                )
            vertex_data.append(vertex)
        return vertex_data
        """
        return [
            [*p.get_tuple(), *self.__normals.get_tuple()]
            for p in self.__positions
        ]
        """

    def has_position(self, position):
        """
        parameters
            Vector3
        returns
            bool of whether the given Vector3 matches one of ours.
        """
        if not type(position).__name__ == "Vector":
            return False
        return position in self.__positions


from observation_insight.app.services.models_BERT import predict_scheme_BERT
from observation_insight.models.predict_model import predict_scheme, get_schemes_names, schemes_to_names
from observation_insight.features.build_features import build_features
from observation_insight.app.core.config import args, BOTTOM_PREDICTION_COUNT, TOP_PREDICTION_COUNT
from observation_insight.models.Models import load_label_encoder, load_classifier, load_encoder
import spacy

class Prediction():

    nlp = spacy.load("da_core_news_sm")

    def __init__(self, encoder_name, model_name):
        self.encoder = encoder_name
        self.model_name = model_name

    def load_all_models(self, args):
        encoder_name = args["encoder_name"]
        encoder = load_encoder(args)
        if encoder_name == 'danishBERT':
            label_encoder = None
            label_encoder_model = None
        else:
            label_encoder,label_encoder_model = load_label_encoder(args)
        classifier = load_classifier(args)
        return encoder, label_encoder, label_encoder_model, classifier

    def preprocessing(self, input_text, encoder, nlp):
        data_features = build_features(args, data=input_text, encoder = encoder, nlp = self.nlp)
        return data_features

    def predict_obs_scheme(self, data_features, model, label_encoder, label_encoder_model):
        """
        # Prediction of observation schemes based on free text documentation

        ## Params
        * data_features (embeddings)
        * model (classifier)
        * label_encoder
        * label_encoder_model

        ## Returns:
        - **results** (str): Predicted observation scheme name
        - **predicted_prob_ordered** (float): Probability of predicted observation scheme
        """
        encoder_name = args["encoder_name"]
        if encoder_name == 'danishBERT':
            results, predicted_prob_ordered = predict_scheme_BERT(model,data_features)
            return results, predicted_prob_ordered
        else:
            predicted_schemes_ordered, predicted_prob_ordered = predict_scheme(args, data_features = data_features, classifier = model)
            results = get_schemes_names(args, predicted_schemes_ordered, label_encoder, label_encoder_model)
            return results, predicted_prob_ordered

    def get_results(self, result_scheme, result_prob):
        res = []
        for i in range(len(result_scheme)):
            res.append({"name": result_scheme[i], "probability": float("{:.6f}".format(result_prob[0][i]))})

        results = {
            'result': res
            }

        return results

    def medicin_fix(self, results):
        """Letting medicinadministration and medicindispensering appear together

        Args:
            results (dict): Dictionary of prediction results

        Returns:
            results_dict (dict): Dictionary of prediction results with the added functionality
        """
        top_location = TOP_PREDICTION_COUNT-1
        bottom_location = BOTTOM_PREDICTION_COUNT-1
        order = results['result']
        schemes = []
        for element in order:
            schemes.append(element['name'])

        disp_pos = schemes.index('Medicindispensering')
        adm_pos = schemes.index('Medicinadministration ')

        if adm_pos < disp_pos:
            if adm_pos == top_location or adm_pos == bottom_location:
                order.insert(adm_pos-1, order.pop(adm_pos))
                adm_pos = adm_pos-1
                order[adm_pos]['probability'] =  order[adm_pos+1]['probability']+0.000001
            order.insert(adm_pos+1, order.pop(disp_pos))
            order[adm_pos+1]['probability'] = order[adm_pos]['probability']
        elif disp_pos < adm_pos:
            if disp_pos == top_location or disp_pos == bottom_location:
                order.insert(disp_pos-1, order.pop(disp_pos))
                disp_pos = disp_pos-1
                order[disp_pos]['probability'] =  order[disp_pos+1]['probability']+0.000001
            order.insert(disp_pos+1, order.pop(adm_pos))
            order[disp_pos+1]['probability'] = order[disp_pos]['probability']

        results_dict = {'result': order}

        return results_dict


from django.conf.urls.defaults import patterns, include, url
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.http import HttpResponse

from settings import MEDIA_ROOT

from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',

    (r'^chart/', include('apps.chart.urls', namespace='chart', app_name='chart')),
    (r'^dig/', include('apps.dig.urls', namespace='dig', app_name='dig')),
    (r'^origin/', include('apps.origin.urls', namespace='origin', app_name='origin')),
	(r'^user/', include('apps.user.urls', namespace='user', app_name='user')),
	(r'^business/', include('apps.business.urls', namespace='business', app_name='business')),
    (r'^$', 'django.contrib.auth.views.login', {'template_name':'registration/login.html'}),
    (r'^login/$', 'django.contrib.auth.views.login', {'template_name':'registration/login.html'}),
    (r'^logout/$', 'django.contrib.auth.views.logout_then_login'),
    (r'^static/(?P<path>.*)$', 'django.views.static.serve', {'document_root': MEDIA_ROOT }),
    (r'^media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': MEDIA_ROOT }),
    (r'^robots\.txt$', lambda r: HttpResponse("User-agent: *\nDisallow: /", mimetype="text/plain")),
)

urlpatterns += staticfiles_urlpatterns()


#!/usr/bin/env python3
from setuptools import setup, find_packages
import sys
from pathlib import Path

MODULE_NAME = 'bathroom_checker'
APP_NAME = 'bathroom-checker'

desktop_script = """
[Desktop Entry]
Name={APP_NAME}
Type=Application
Exec=/usr/local/bin/{APP_NAME}
StartupNotify=false
Terminal=false
""".format(**vars())

if sys.platform == "linux":
    try:
        with open("/usr/share/applications/{APP_NAME}.desktop".format(**vars()), "w") as f:
            f.write(desktop_script)
    except Exception:
        print("Enable to create .desktop script. ¿Are you root?")

setup(
    python_requires='>=3.4',
    name=APP_NAME,
    description="Tray icon for mrmilu's bathroom-monitor",
    author='Alex Left',
    author_email='aizquierdo@mrmilu.com',
    url='',
    version='0.2',
    entry_points = {
        'console_scripts': ['{APP_NAME}={MODULE_NAME}.{MODULE_NAME}:main'.format(**vars())],
    },
    packages=[MODULE_NAME],
    package_data={MODULE_NAME: ['images/*']},
    include_package_data= True,
    license='GPL-v3',
    long_description=open('README.md').read(),
    install_requires=["pystray", "requests", "pillow", "notify2"]
)


from collections import Counter
from typing import List

class Solution:
    def numPairsDivisibleBy60(self, time: List[int]) -> int:
        counter = Counter()
        answer = 0
        for i in range(len(time)):
            time[i] %= 60

            if 60 - time[i] in counter:
                answer += counter[60 - time[i]]

            counter[time[i]] += 1

        return answer

# Runtime: 252 ms, faster than 37.99% of Python3 online submissions for Pairs of Songs With Total Durations Divisible by 60.
# Memory Usage: 17.4 MB, less than 39.64% of Python3 online submissions for Pairs of Songs With Total Durations Divisible by 60.


"""
https://leetcode.com/problems/count-nice-pairs-in-an-array/
"""

from collections import Counter

class Solution:
    def countNicePairs(self, nums: list[int]) -> int:
        """
        nums[i] + revs[j] = nums[j] + revs[i] ==>
        nums[i] - revs[i] = nums[j] - revs[j] ==>
        Then we just need to find how many pairs having the same difference.
        """
        pairs = 0
        cnt = Counter()
        for x in nums:
            y = int(str(x)[::-1])
            pairs += cnt[x - y]
            cnt[x - y] += 1

        return pairs % (10 ** 9 + 7)

print(Solution().countNicePairs([42, 11, 1, 97]))


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

import decimal
from functools import reduce
import json
from typing import Any, Dict, List, Optional, Tuple, Union

from django.db import connection

from catmaid.error import ClientError

class StateMatchingError(ClientError):
    """Indicates that a state check wasn't successful"""
    def __init__(self, message, state):
        super().__init__(message)
        self.unmatched_state = state

    def __str__(self) -> str:
        return "{}: {}".format(self.args[0],
            str(self.unmatched_state) or "(no details found)")

class SQL:
    was_edited = """
        SELECT 1 FROM location t
        WHERE t.id = %s
        AND t.edition_time >=(%s::timestamptz - '1 ms'::interval)
        AND t.edition_time < (%s::timestamptz + '1 ms'::interval)
        LIMIT 1
    """
    is_parent = """
        SELECT 1 FROM treenode t
        WHERE t.parent_id = %s AND t.id = %s
    """
    is_child = """
        SELECT 1 FROM treenode t
        WHERE t.id = %s AND t.parent_id = %s
    """
    is_root = """
        SELECT 1 FROM treenode t
        WHERE t.parent_id IS NULL AND t.id = %s
    """
    all_children = """
        SELECT CASE WHEN sub.c=0 THEN 1 ELSE 0 END FROM (
            SELECT COUNT(*) FROM treenode t %s WHERE t.parent_id = %s %s
        ) sub(c)
    """
    all_links = """
        SELECT CASE WHEN sub.c=0 THEN 1 ELSE 0 END FROM (
            SELECT COUNT(*) FROM treenode_connector l %s WHERE l.treenode_id = %s %s
        ) sub(c)
    """
    all_links_c = """
        SELECT CASE WHEN sub.c=0 THEN 1 ELSE 0 END FROM (
            SELECT COUNT(*) FROM treenode_connector l %s WHERE l.connector_id = %s %s
        ) sub(c)
    """

    @staticmethod
    def edited(table):
        return f"""
            SELECT 1 FROM {table} t
            WHERE t.id = %s
            AND t.edition_time >=(%s::timestamptz - '1 ms'::interval)
            AND t.edition_time < (%s::timestamptz + '1 ms'::interval)
        """

class StateCheck:
    """A simple wraper arround state check SQL and parameters for it"""

    def __init__(self, sql, params):
        if not isinstance(sql, str):
            raise ValueError("No SQL string")
        self.sql = sql
        self.params = params if type(params) in (list, tuple) else (params,)

    def __str__(self) -> str:
        return f"SQL: {self.sql} Parameters: {self.params}"

def make_all_children_query(child_ids, node_id) -> StateCheck:
    if child_ids:
        table_sql, table_args = list_to_table(child_ids, 1)
        args = table_args
        child_query = f" LEFT JOIN {table_sql} p(id) ON t.id = p.id "
        constraints = "AND p.id IS NULL"
        args.append(node_id)
        return StateCheck(SQL.all_children % (child_query, "%s", constraints), args)
    else:
        return StateCheck(SQL.all_children % ("", "%s", ""), [node_id])

def make_all_links_query(link_ids, node_id, is_connector=False) -> StateCheck:
    template = SQL.all_links_c if is_connector else SQL.all_links
    if link_ids:
        table_sql, table_args = list_to_table(link_ids, 1)
        args = table_args
        link_query = f" LEFT JOIN {table_sql} p(id) ON l.id = p.id "
        constraints = "AND p.id IS NULL"
        args.append(node_id)
        return StateCheck(template % (link_query, "%s", constraints), args)
    else:
        return StateCheck(template % ("", "%s", ""), [node_id])

def list_to_table(input_list, n=1) -> Tuple[str, Any]:
    if n == 1:
        args:List[Tuple] = [(e,) for e in input_list]
    elif n == 2:
        args = [(e[0], e[1]) for e in input_list]
    else:
        raise ValueError("Invalid n parameter for list_to_table")
    if not args: # empty input_list or invalid n
        raise ValueError("Could't parse list argument in state check")

    records_list_template = ','.join(['%s'] * len(args))
    return (f"(VALUES {records_list_template})", args)

def has_only_truthy_values(element, n=2) -> bool:
    return n == len(element) and all(element)

def parse_state(state):
    """Expect a JSON string and returned the parsed object."""
    if not state:
        raise ValueError("No state provided")

    if isinstance(state, str):
        state = json.loads(state, parse_float=decimal.Decimal)

    def check_ref(name, ref):
        if type(ref) not in (list, tuple) or len(ref) != 2:
            raise ValueError(f"Invalid state provided, {name} is not a list of two elements")

    def parse_id(name, id):
        try:
            return int(id)
        except (ValueError, TypeError):
            raise ValueError(f"Invalid state provided, couldn't parse {name} id: {id}")

    # Make sure child, parent and link ids are integers
    parsed_state_type = type(state)
    if parsed_state_type == dict:
        parent = state.get('parent')
        if parent:
            check_ref('parent', parent)
            # Allow parent to be None, as an alternative way to say there is no
            # parent.
            parent[0] = None if not parent[0] else parse_id('parent', parent[0])
        children = state.get('children')
        if children:
            if type(children) not in (list, tuple):
                raise ValueError("Invald state provided, 'children' is not a list")
            for c in children:
                check_ref("child", c)
                c[0] = parse_id("child", c[0])
        links = state.get('links')
        if links:
            if type(links) not in (list, tuple):
                raise ValueError("Invald state provided, 'links' is not a list")
            for link in links:
                check_ref("link", link)
                link[0] = parse_id("link", link[0])
    elif parsed_state_type == list:
        for n in state:
            check_ref('node', n)
            n[0] = parse_id('node', n[0])

    return state

def collect_state_checks(node_id, state, cursor, node=False,
        parent_edittime=False, is_parent=False, children=False,
        links=False, c_links=False, multinode=False) -> List[StateCheck]:
    """Collect state checks for a single node, but don't execute them.

    If <children> is a list of node IDs, only these nodes will be checked if
    they are valid children. If <children> is the boolean True, a state check is
    added that tests if the state provided children represent *all* children.
    """
    state_checks:List = []

    if node:
        if 'edition_time' not in state:
            raise ValueError("No valid state provided, missing edition time")
        node = [node_id, state['edition_time']]

        # Make sure the node itself is valid
        state_checks = [StateCheck(SQL.was_edited, (node[0], node[1], node[1]))]
    else:
        node = [node_id]

    if parent_edittime or is_parent:
        parent = state.get('parent')
        if not parent:
            parent_id = None
        elif 2 != len(parent):
            raise ValueError("No valid state provided, invalid parent")
        else:
            parent_id = parent[0]

        if parent_id and -1 != parent_id and not parent[1]:
            raise ValueError("No valid state provided, invalid parent")

        # Collect qurey components, startwith parent relation
        if parent_id and -1 != parent_id:
            if is_parent:
                if parent_id == node_id:
                    raise ValueError(f"No valid state provided, parent is same as node ({parent_id})")
                state_checks.append(StateCheck(SQL.is_parent, (parent_id, node_id)))
            state_checks.append(StateCheck(SQL.was_edited,
                (parent_id, parent[1], parent[1])))
        else:
            state_checks.append(StateCheck(SQL.is_root, (node_id,)))

    if children:
        child_nodes = state.get('children')
        if not isinstance(child_nodes, (list, tuple)):
            raise ValueError("No valid state provided, can't find list 'children'")
        if not all(has_only_truthy_values(e) for e in child_nodes):
            raise ValueError("No valid state provided, invalid children")

        if type(children) == bool:
            state_checks.append(make_all_children_query(
                [int(c[0]) for c in child_nodes], node_id))
        state_checks.extend(StateCheck(SQL.was_edited, (c[0], c[1], c[1])) for c in child_nodes)
        state_checks.extend(StateCheck(SQL.is_child, (c[0], node_id)) for c in child_nodes)

    if links:
        links = state.get('links')
        if not isinstance(links, (list, tuple)):
            raise ValueError("No valid state provided, can't find list 'links'")
        if not all(has_only_truthy_values(e) for e in links):
            raise ValueError("No valid state provided, invalid links")

        state_checks.append(make_all_links_query(
            [int(link[0]) for link in links], node_id))
        state_checks.extend(StateCheck(SQL.edited('treenode_connector'),
            (link[0], link[1], link[1])) for link in links)

    if c_links:
        c_links = state.get('c_links')
        if not isinstance(c_links, (list, tuple)):
            raise ValueError("No valid state provided, can't find list 'c_links'")
        if not all(has_only_truthy_values(e) for e in c_links):
            raise ValueError("No valid state provided, invalid links")

        state_checks.append(make_all_links_query(
            [int(link[0]) for link in c_links], node_id, True))
        state_checks.extend(StateCheck(SQL.edited('treenode_connector'),
            (link[0], link[1], link[1])) for link in c_links)

    return state_checks

def validate_state(node_ids, state, node=False, is_parent=False,
        parent_edittime=False, children=False, links=False, c_links=False,
        multinode=False, neighborhood=False, lock=True, cursor=None) -> None:
    """Validate a local state relative to a given node. What tests are performed
    depends on the mode flags set.

    Modes are hierarchical: neighborhood implies node, is_parent,
    parent_edittime, children and links. A edge implies is_parent,
    parent_edittime and children. The special mode "multinode" voids others and
    expects a list of two-element-lists containing node IDs and edition times,
    which will be checked.

    Expect state to be a dictionary of of the following form, can be provided
    as a JSON string. Only entry for set flags need to be present:
    {
      parent: (<id>, <edition_time>),
      children: ((<child_id>, <child_edition_time>), ...),
      links: ((<connector_id>, <connector_edition_time>, <relation_id>), ...)
    }
    """
    state = parse_state(state)

    # Make sure input nodes are iterable
    if type(node_ids) not in (list, tuple):
        node_ids = (node_ids,)

    # Neighborhood implies node and parent checks
    node = node or neighborhood
    is_parent = is_parent or neighborhood
    parent_edittime = parent_edittime or neighborhood
    children = children or neighborhood
    links = links or neighborhood

    # Collect state checks and test them, if state checks are not disabled
    if not is_disabled(state):
        cursor = cursor or connection.cursor()
        if multinode:
            node_id_set = set(node_ids)
            unseen = set(node_ids)
            for node_state in state:
                node_id = node_state[0]
                if node_id not in node_id_set:
                    raise ValueError(f"Couldn't find node in state: {node_id}")
                unseen.remove(node_id)
            if len(unseen) > 0:
                raise ValueError("Couldn't find state info on node(s) {}".format(
                    ", ".join(str(n) for n in unseen)))
            state_checks = []
            for node_state in state:
                state_checks.append(StateCheck(SQL.was_edited,
                    (node_state[0], node_state[1], node_state[1])))
            check_state(state, state_checks, cursor)
        else:
            check_sets = [collect_state_checks(n, state, cursor, node=node,
                    is_parent=is_parent, parent_edittime=parent_edittime,
                    multinode=multinode, children=children, links=links,
                    c_links=c_links) for n in node_ids]
            # Flatten list of per-node state checks into one list of checks
            state_checks = reduce(lambda x, y: x + y, check_sets)
            check_state(state, state_checks, cursor)

    # Acquire lock on treenode
    if lock:
        cursor = cursor or connection.cursor()
        lock_nodes(node_ids, cursor)

def lock_node(node_id, cursor) -> None:
    cursor.execute("""
        SELECT id FROM treenode WHERE id=%s FOR UPDATE
    """, (node_id,))
    result = cursor.fetchall()

def lock_nodes(node_ids, cursor) -> None:
    if node_ids:
        node_template = ",".join(("%s",) * len(node_ids))
        cursor.execute(f"""
            SELECT id FROM treenode WHERE id IN ({node_template}) FOR UPDATE
        """, node_ids)
    else:
        raise ValueError("No nodes to lock")

def is_disabled(state) -> bool:
    return state and type(state) == dict and state.get('nocheck') is True

def make_nocheck_state(parsed=False):
    """Get a state representation that causes skipping of actual state checks.

    If "parsed" is True, a parsed representation will be returned, otherwise a
    regular JSON representation is used."""
    state:Union[str, Dict] = {'nocheck': True}
    if not parsed:
        state = json.dumps(state)
    return state

def check_state(state, state_checks, cursor) -> None:
    """Raise an error if state checks can't be passed."""
    # Skip actual tests if state checking is disabled in state
    if is_disabled(state):
        return

    sql_checks = [sc.sql for sc in state_checks]
    args:List = []
    for sc in state_checks:
        args.extend(p for p in sc.params)

    if sql_checks:
        cursor.execute("(" + ") INTERSECT (".join(sql_checks) + ")", args)
    else:
        cursor.execute("SELECT 1")
    state_check_results = cursor.fetchall()

    # Expect results to have a length of the number of checks made and that
    # each result equals one.
    if not state_check_results or 1 != len(state_check_results) or 1 != state_check_results[0][0]:
        raise StateMatchingError("The provided state differs from the database state", state)


# Add some right click options

# stdlib imports
from copy import copy
import math

# Local imports
from ..utils.misc import get_root_node, clone_node
from ..ModelExporter.utils import get_children

# Blender imports
import bmesh
import bpy
from bpy.types import Menu, Operator
from mathutils import Matrix

CONE_ROTATION_MAT = Matrix.Rotation(math.radians(90), 4, 'X')

class AddReferenceNode(Operator):
    """Add a new NMS reference node"""
    bl_idname = "nmsdk.add_reference_node"
    bl_label = "Reference Node"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        selected_objs = getattr(context, "selected_objects", None)
        if not selected_objs:
            self.report({'ERROR_INVALID_INPUT'},
                        'Please select an object to add a child node to')
            return {'FINISHED'}
        # Create a new empty reference node.
        for obj in selected_objs:
            empty_mesh = bpy.data.meshes.new('ref')
            empty_obj = bpy.data.objects.new('ref', empty_mesh)
            empty_obj.NMSNode_props.node_types = 'Reference'
            empty_obj.matrix_world = Matrix()
            empty_obj.parent = obj
            empty_obj.rotation_mode = 'QUATERNION'
            bpy.context.scene.collection.objects.link(empty_obj)

        return {'FINISHED'}

class AddLocatorNode(Operator):
    """Add a new NMS reference node"""
    bl_idname = "nmsdk.add_locator_node"
    bl_label = "Locator Node"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        selected_objs = getattr(context, "selected_objects", None)
        if not selected_objs:
            self.report({'ERROR_INVALID_INPUT'},
                        'Please select an object to add a child node to')
            return {'FINISHED'}
        # Create a new empty reference node.
        for obj in selected_objs:
            empty_mesh = bpy.data.meshes.new('loc')
            empty_obj = bpy.data.objects.new('loc', empty_mesh)
            empty_obj.NMSNode_props.node_types = 'Locator'
            empty_obj.matrix_world = Matrix()
            empty_obj.parent = obj
            empty_obj.rotation_mode = 'QUATERNION'
            bpy.context.scene.collection.objects.link(empty_obj)

        return {'FINISHED'}

class AddBoxCollisionNode(Operator):
    """Add a new NMS box collision node"""
    bl_idname = "nmsdk.add_box_collision_node"
    bl_label = "Box collision node"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        selected_objs = getattr(context, "selected_objects", None)
        if not selected_objs:
            self.report({'ERROR_INVALID_INPUT'},
                        'Please select an object to add a child node to')
            return {'FINISHED'}
        # Create a new empty reference node.
        for obj in selected_objs:
            # Create a new cube for collisions
            mesh = bpy.data.meshes.new('cube')
            bm = bmesh.new()
            bmesh.ops.create_cube(bm, size=1.0)
            bm.to_mesh(mesh)
            bm.free()
            cube = bpy.data.objects.new('cube', mesh)
            cube.NMSNode_props.node_types = 'Collision'
            cube.NMSCollision_props.collision_types = 'Box'
            cube.matrix_world = Matrix()
            cube.parent = obj
            bpy.context.scene.collection.objects.link(cube)
            cube.rotation_mode = 'QUATERNION'

        return {'FINISHED'}

class AddSphereCollisionNode(Operator):
    """Add a new NMS sphere collision node"""
    bl_idname = "nmsdk.add_sphere_collision_node"
    bl_label = "Sphere collision node"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        selected_objs = getattr(context, "selected_objects", None)
        if not selected_objs:
            self.report({'ERROR_INVALID_INPUT'},
                        'Please select an object to add a child node to')
            return {'FINISHED'}
        # Create a new empty reference node.
        for obj in selected_objs:
            # Create a new sphere for collisions
            mesh = bpy.data.meshes.new('sphere')
            bm = bmesh.new()
            bmesh.ops.create_icosphere(bm, subdivisions=4, radius=0.5)
            bm.to_mesh(mesh)
            bm.free()
            sphere = bpy.data.objects.new('sphere', mesh)
            sphere.NMSNode_props.node_types = 'Collision'
            sphere.NMSCollision_props.collision_types = 'Sphere'
            sphere.matrix_world = Matrix()
            sphere.parent = obj
            bpy.context.scene.collection.objects.link(sphere)
            sphere.rotation_mode = 'QUATERNION'

        return {'FINISHED'}

class AddCylinderCollisionNode(Operator):
    """Add a new NMS cylinder collision node"""
    bl_idname = "nmsdk.add_cylinder_collision_node"
    bl_label = "Cylinder collision node"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        selected_objs = getattr(context, "selected_objects", None)
        if not selected_objs:
            self.report({'ERROR_INVALID_INPUT'},
                        'Please select an object to add a child node to')
            return {'FINISHED'}
        # Create a new empty reference node.
        for obj in selected_objs:
            # Create a new cylinder for collisions
            mesh = bpy.data.meshes.new('cylinder')
            bm = bmesh.new()
            bmesh.ops.create_cone(bm, cap_ends=True, cap_tris=True,
                                  radius1=0.5, radius2=0.5, depth=1.0,
                                  segments=20, matrix=CONE_ROTATION_MAT)
            bm.to_mesh(mesh)
            bm.free()
            cylinder = bpy.data.objects.new('cylinder', mesh)
            cylinder.NMSNode_props.node_types = 'Collision'
            cylinder.NMSCollision_props.collision_types = 'Cylinder'
            cylinder.matrix_world = Matrix()
            cylinder.parent = obj
            bpy.context.scene.collection.objects.link(cylinder)
            cylinder.rotation_mode = 'QUATERNION'

        return {'FINISHED'}

class CloneNodes(Operator):
    """Clone the selcted node(s)"""
    bl_idname = "nmsdk.clone_nodes"
    bl_label = "Clone node(s)"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        selected_objs = getattr(context, "selected_objects", None)
        if not selected_objs:
            self.report({'ERROR_INVALID_INPUT'},
                        'Please select an object to clone')
            return {'FINISHED'}
        # Make a copy of each of the selected nodes.
        for obj in selected_objs:
            clone_node(obj)
        return {'FINISHED'}

class CloneNodesRecursively(Operator):
    """Clone the selcted node(s) and all attached children"""
    bl_idname = "nmsdk.clone_nodes_recursively"
    bl_label = "Clone node(s) recursively"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        selected_objs = getattr(context, "selected_objects", None)
        if not selected_objs:
            self.report({'ERROR_INVALID_INPUT'},
                        'Please select an object to clone')
            return {'FINISHED'}
        # Make a copy of each of the selected nodes.
        for obj in selected_objs:
            clone_node(obj, True)
        return {'FINISHED'}

class NMSDK_OT_move_to_parent(Operator):
    """Set the selected object(s) to be a child node of the selected parent"""
    bl_idname = "nmsdk.add_to_parent"
    bl_label = "Set object(s) as child of selected parent"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        selected_objs = getattr(context, "selected_objects", [])
        parent_obj = getattr(context, "active_object", None)

        # First, make sure we have, a) enough objects selected, and b) the
        # correct set of objects to iterate over so that it doesn't include the
        # parent object.
        if len(selected_objs) < 2:
            self.report({'ERROR_INVALID_INPUT'},
                        'Please select 2 or more objects')
            return {'FINISHED'}
        child_objs = set(selected_objs) - {parent_obj}
        root_obj = get_root_node(parent_obj)
        for obj in child_objs:
            # There are two possibilities for moving an object under a parent.
            # 1. It can already be inside the imported scene, in which case we
            # don't want to apply any kind of transforms, but simply set the
            # parent and local transform.
            # 2. The object exists outside of the scene, so we need to do a few
            # extra things detailed within the code block.
            if get_root_node(obj) == root_obj:
                # Copy the local transform.
                trans = copy(obj.matrix_local)
                # set the parent.
                obj.parent = parent_obj
                # Set the local transform as before
                obj.matrix_local = trans
                del trans
            else:
                trans = copy(obj.matrix_world)
                bpy.ops.object.parent_set()
                obj.matrix_world = trans
                del trans

                # Go over all the children nodes, and if any don't have data
                # then they are empty. We'll set these as Locators.
                # Also check the obj itself while we are here...
                for child in [obj] + get_children(obj):
                    if not child.data:
                        child.NMSNode_props.node_types = 'Locator'

        return {'FINISHED'}

class NMSDK_MT_add_NMS_Scenenodes(Menu):
    bl_label = "Add new Scene Node objects"
    bl_idname = "NMSDK_MT_add_NMS_Scenenodes"

    def draw(self, context):
        layout = self.layout
        row = layout.row()
        row.operator('nmsdk.add_reference_node')
        row = layout.row()
        row.operator('nmsdk.add_locator_node')
        layout.separator()
        row = layout.row()
        row.operator('nmsdk.add_box_collision_node')
        row = layout.row()
        row.operator('nmsdk.add_sphere_collision_node')
        row = layout.row()
        row.operator('nmsdk.add_cylinder_collision_node')

class NMSDK_MT_clone_NMS_Scenenodes(Menu):
    bl_label = "Clone NMS Scenenode objects"
    bl_idname = "NMSDK_MT_clone_NMS_Scenenodes"

    def draw(self, context):
        layout = self.layout
        row = layout.row()
        row.operator('nmsdk.clone_nodes')
        row = layout.row()
        row.operator('nmsdk.clone_nodes_recursively')
        row = layout.row()

# Functions to modify blenders' menu items

def add_empty_root_node(self, context):
    """ Add an option to the 'add' menu in the 3D view to add an empty root
    NMS SceneNode reference for exporting. """
    layout = self.layout
    layout.separator()
    layout.operator('nmsdk.create_root_scene')

def parent_menu_func(self, context):
    layout = self.layout
    layout.separator()
    layout.operator('nmsdk.add_to_parent')

def add_obj_menu_func(self, context):
    """ Add the sub-menu to the context menu. """
    layout = self.layout
    if get_root_node(context.active_object) is not None:
        layout.separator()
        row = layout.row()
        row.menu("NMSDK_MT_add_NMS_Scenenodes")
        row = layout.row()
        row.menu("NMSDK_MT_clone_NMS_Scenenodes")

classes = (NMSDK_OT_move_to_parent,
           AddReferenceNode,
           AddBoxCollisionNode,
           AddSphereCollisionNode,
           AddCylinderCollisionNode,
           AddLocatorNode,
           NMSDK_MT_add_NMS_Scenenodes,
           NMSDK_MT_clone_NMS_Scenenodes,
           CloneNodes,
           CloneNodesRecursively)

class ContextMenus():
    @staticmethod
    def register():
        # Register classes to be used.
        for cls_ in classes:
            bpy.utils.register_class(cls_)
        # Add the functions to the menus they need to be in.
        bpy.types.VIEW3D_MT_add.append(add_empty_root_node)
        bpy.types.VIEW3D_MT_object_parent.append(parent_menu_func)
        bpy.types.VIEW3D_MT_object_context_menu.append(add_obj_menu_func)

    @staticmethod
    def unregister():
        # Unregister classes used.
        for cls_ in classes:
            bpy.utils.unregister_class(cls_)
        # Remove the functions from the menus they were in.
        bpy.types.VIEW3D_MT_add.remove(add_empty_root_node)
        bpy.types.VIEW3D_MT_object_parent.remove(parent_menu_func)
        bpy.types.VIEW3D_MT_object_context_menu.remove(add_obj_menu_func)


import pygame
from pygame.locals import Color, Rect
import random
from graphics import *
import pieces
import piece

random.seed()

class GameFunctions:
	def new_piece(self):
		"""Creates a new piece and places it at the top of the well."""
		piecesCount = len(pieces.PieceTypes.AllPieceTypes)
		next_piece_type = random.randint(0, piecesCount - 1)
		#next_piece_type = 4
		well_cols, _ = self.well_dimensions()
		left_pos = int((well_cols - 4) / 2)
		top_pos = -4
		p = piece.Piece(self, next_piece_type, (left_pos, top_pos), self.well_dimensions())
		# The piece is above the well.
		# Bring the piece down until its bottom row is inside the well.
		if (self.row_is_empty(p.matrix, 3)):
			p.move_down()
			if (self.row_is_empty(p.matrix, 2)):
				p.move_down()
				if (self.row_is_empty(p.matrix, 1)):
					p.move_down()
					if (self.row_is_empty(p.matrix, 0)):
						raise Exception("Piece matrix seems to be empty.")
		return p

	def row_is_empty(self, matrix, rownum):
		"""Determins whether a given row in a piece matrix contains only 0s."""
		for colnum in range(4):
			if (matrix[rownum][colnum] > 0):
				return False
		return True

	def col_is_empty(self, matrix, colnum):
		"""Determines whether a given columnin a piece matrix contains only 0s."""
		for rownum in range(4):
			if (matrix[rownum][colnum] > 0):
				return False
		return True

	def cell_size_in_pixels(self):
		"""Returns the number of pixels in a cell as (width, height)."""
		return (16, 16)

	def well_dimensions(self):
		"""Returns the cellular dimensions of the well."""
		return (10, 20)

	def well_size(self):
		welldw, welldh = self.well_dimensions()
		return (welldw * 16, welldh * 16)

	def well_size_in_pixels(self):
		"""Returns the pixel size of the well as (width, height)."""
		cell_size_x, cell_size_y = self.cell_size_in_pixels()
		well_dim_x, well_dim_y = self.well_dimensions()
		return (cell_size_x * well_dim_x, cell_size_y * well_dim_y)

	def well_pixel_coords(self, screen_size):
		"""Returns the x,y pixel coordinates of the upper-left corner of the well."""
		screen_width, screen_height = screen_size
		well_size_x, well_size_y = self.well_size_in_pixels()
		x = int((screen_width - well_size_x) / 2)
		y = int((screen_height - well_size_y) / 2)
		return (x, y)

	def draw_well_grid(self, screen, screen_size):
		"""This draws a happy little dot in the well at the center of each cell."""
		wellx, welly = self.well_pixel_coords(screen_size)
		cell_width, cell_height = self.cell_size_in_pixels()
		cols, rows = self.well_dimensions()
		for col in range(cols):
			for row in range(rows):
				dot_pos = (wellx + col * cell_width + int(cell_width / 2), welly + row * cell_height + int(cell_height / 2))
				pygame.draw.circle(screen, Color(128, 128, 128, 255), dot_pos, 3)

	def draw_well_matrix(self, screen, screen_size, well_matrix):
		"""This draws the existing set pieces in the well.  It draws a set brick for each cell
		in the well matrix which contains a 1."""
		wellx, welly = self.well_pixel_coords(screen_size)
		cell_width, cell_height = self.cell_size_in_pixels()
		cols, rows = self.well_dimensions()
		for row in range(rows):
			for col in range(cols):
				cell = well_matrix[row][col]
				if (cell >= 1):
					brick_pos = (wellx + col * cell_width, welly + row * cell_height)
					screen.blit(img_well_brick, brick_pos)

	def draw_piece_matrix(self, screen, screen_size, piece):
		"""This draws the current piece on the screen.  It draws a brick for each cell
		in the piece matrix which contains a 1."""
		posx, posy = piece.position
		for row in range(4):
			for col in range(4):
				cellx, celly = self.cell_position_to_pixels(screen_size, (posx, posy))
				if (GameData.SHOW_MATRIX_SHADOW):
					pygame.draw.line(screen, Color(255, 0, 0, 255), (col * 16 + cellx, row * 16 + celly), ((col+1) * 16 + cellx, (row + 1) * 16 + celly))
				if (posy + row >= 0):
					if (piece.matrix[row][col] == 1):
						screen.blit(img_piece_brick, (col * 16 + cellx, row * 16 + celly))

	def pause_for(self, milliseconds):
		"""Pauses execution for the given time period.  Used for animation sequences and banners."""
		start_ticks = pygame.time.get_ticks()
		while True:
			pygame.time.Clock().tick(1000/30)
			if (pygame.time.get_ticks() > start_ticks + milliseconds):
				break

	def draw_clearings(self, screen, screen_size, well_matrix, crunch_sound):
		"""Draws crunched bricks in each line to be cleared and plays a crunching sound once for each row."""
		wellx, welly = self.well_pixel_coords(screen_size)
		cell_width, cell_height = self.cell_size_in_pixels()
		lines = self.get_lines_to_clear(well_matrix)
		line_count = len(lines)
		ww, wh = self.well_dimensions()
		for line_num in range(line_count):
			for col in range(ww):
				brick_pos = (wellx + col * cell_width, welly + lines[line_num] * cell_height)
				screen.blit(img_crunch_brick, brick_pos)
			pygame.display.update()
			crunch_sound.play()
			self.pause_for(500)

	def draw_well(self, screen, screen_size):
		border_size = 5
		borderx, bordery = self.well_pixel_coords(screen_size)
		borderx = borderx - border_size
		bordery = bordery - border_size
		borderw, borderh = self.well_size_in_pixels()
		borderw = borderw + (2 * border_size)
		borderh = borderh + (2 * border_size)
		pygame.draw.rect(screen, Color(255, 255, 255, 255), Rect(borderx, bordery, borderw, borderh))

		wellx, welly = self.well_pixel_coords(screen_size)
		wellw, wellh = self.well_size_in_pixels()
		pygame.draw.rect(screen, Color(0, 0, 0, 255), Rect(wellx, welly, wellw, wellh))

	def draw_scoreboard(self, screen, screen_size, score):
		"""Draws a box with the score in it onto the screen."""
		screenw, screenh = screen_size
		wellx, welly = self.well_pixel_coords(screen_size)
		wellw, wellh = self.well_size()
		border_size = 5
		padding = 20
		height = 50

		left = wellx + wellw + padding
		right = screenw - padding
		top = welly
		#bottom = top + height
		width = right - left + 1

		pygame.draw.rect(screen, Color(255, 255, 255, 255), Rect(left - border_size, top - border_size, width + border_size * 2, height + border_size * 2))
		pygame.draw.rect(screen, Color(0, 0, 0, 255), Rect(left, top, width, height))

		text = str(score)
		font = pygame.font.SysFont("Lucida Console", 32, 1, 0)
		text_surface = font.render(text, False, Color(255, 255, 255, 255))
		screen.blit(text_surface, (left + (width - text_surface.get_width()) / 2, top + (height - text_surface.get_height()) / 2))

	def compact_well(self, well_matrix):
		"""Bring the contents of the wall down into any recently cleared lines."""
		lines = self.get_lines_to_clear(well_matrix)
		ww, wh = self.well_dimensions()

		# For each cleared line...
		for clear_line_num in lines:
			# Copy each line of the matrix above this one, overwriting the line below it.
			# Don't do it for line 0 because there is no line above it.
			row_num = clear_line_num
			while (row_num >= 1):
				line_above = well_matrix[row_num - 1]

				for col_num in range(ww):
					well_matrix[row_num][col_num] = line_above[col_num]

				row_num = row_num - 1

		# For line 0, we just clear it explicitly.
		for col_num in range(ww):
			well_matrix[0][col_num] = 0

	def create_empty_matrix(self, rows, cols):
		"""Creates a matrix representing a new empty well."""
		result = []
		for row in range(rows):
			this_row = []
			result.append(this_row)
			for col in range(cols):
				this_row.append(0)
		return result

	def new_well(self):
		"""Creates a matrix representing a new empty well."""
		cols, rows = self.well_dimensions()
		return self.create_empty_matrix(rows, cols)

	def set_piece(self, piece, well_matrix):
		pass

	def copy_matrix(self, source):
		"""Create a duplicate of a matrix.  This is used to create a new piece from the pieces templates."""
		result = []
		for row_num in range(len(source)):
			old_row = source[row_num]
			new_row = []
			for col_num in range(len(old_row)):
				old_cell = old_row[col_num]
				new_row.append(old_cell)
			result.append(new_row)
		return result

	def impose_matrix(self, source, target, source_pos):
		"""writes a matrix onto another matrix at the given cell position.  The result is a matrix addition function.
		So if the same cell in both matrixes contain 1, the output cell will contain 2 (1+1). """

		x_offset, y_offset = source_pos
		source_h = len(source)
		source_w = len(source[0])
		target_h = len(target)
		target_w = len(target[0])

		# Copy the target matrix to a temp matrix.
		new_blank_well = self.copy_matrix(target)

		# Impose the piece to the temp matrix
		for source_row_num in range(source_h):
			for source_col_num in range(source_w):
				x, y = (x_offset + source_col_num, y_offset + source_row_num)
				if (x >= 0) and (x <= target_w - 1) and (y >= 0) and (y <= target_h - 1):
					source_cell_value = source[source_row_num][source_col_num]
					target_cell_value = target[y][x]
					new_cell_value = source_cell_value + target_cell_value
					new_blank_well[y][x] = new_cell_value

		# Copy the temp matrix back over the target matrix to send it back out.
		for row_num in range(target_h):
			for col_num in range(target_w):
				target[row_num][col_num] = new_blank_well[row_num][col_num]

	def has_collisions(self, matrix):
		"""Determines if there were any collisions in the result of two merged matrices."""
		matrix_height = len(matrix)
		matrix_width = len(matrix[0])
		for row in range(matrix_height):
			for col in range(matrix_width):
				if (matrix[row][col] > 1):
					return True
		return False

	def line_is_full(self, row):
		"""Indicates if the specified row is completely full of bricks and should be cleared."""
		w = len(row)
		for col in range(w):
			if (row[col] == 0):
				return False
		return True

	def has_lines_to_clear(self, matrix):
		"""Indicates whether any lines are full and need to be cleared."""
		return len(self.get_lines_to_clear(matrix)) > 0

	def get_lines_to_clear(self, matrix):
		"""Returns a list of lines which are full and need to be cleared."""
		result = []
		h = len(matrix)
		for row in range(h):
			if self.line_is_full(matrix[row]):
				result.append(row)
		return result

	def has_hit_bottom(self, piece):
		"""Determines whether the piece has encountered the bottom of the well."""
		x, y = piece.position
		matrix = piece.matrix
		ww, wh = self.well_dimensions()
		wx, wy = (ww - 1, wh - 1)
		result = False
		for rownum in range(4):
			ry = y + rownum
			if (ry >= wy):
				if (not self.row_is_empty(matrix, rownum)):
					result = True
		return result

	def cell_position_to_pixels(self, screen_size, cell_position_colrow):
		"""Convert a cell position within the well to screen x,y coordinates."""
		cell_col, cell_row = cell_position_colrow
		well_pixel_x, well_pixel_y = self.well_pixel_coords(screen_size)
		cell_size_width, cell_size_height = self.cell_size_in_pixels()
		x = well_pixel_x + cell_col * cell_size_width
		y = well_pixel_y + cell_row * cell_size_height
		return (x, y)

	def print_matrix(self, matrix, caption):
		"""Displays the values in the given matrix in the text console.  This is useful for debugging."""
		h = len(matrix)
		w = len(matrix[0])
		print(caption)
		for rownum in range(h):
			print("\t", matrix[rownum])

	def show_game_over(self, screen, screen_size):
		pygame.mixer.music.stop()
		text = "Game Over"
		font = pygame.font.SysFont("Courier New", 32, 1, 0)
		text_surface = font.render(text, False, Color(255, 255, 0, 255))
		surface = pygame.Surface( (text_surface.get_width() + 80, text_surface.get_height() + 80) )

		surface.fill(Color(255, 0, 0, 255))

		w, h = screen_size
		screen.blit(surface, ( ((w - surface.get_width()) / 2), ((h - surface.get_height()) / 2) ) )
		screen.blit(text_surface, ( ((w - text_surface.get_width()) / 2), ((h - text_surface.get_height()) / 2) ) )
		pygame.display.update()
		self.pause_for(5000)

	def piece_will_set(self, piece, well_matrix):
		will_set = False
		(x, y) = piece.position
		hit_bottom = self.has_hit_bottom(piece)
		if (hit_bottom):
			will_set = True
		else:
			# Otherwise, if it is going to collide on the next drop, it sets.
			next_position = (x, y + 1)
			collision_matrix = self.copy_matrix(well_matrix)
			self.impose_matrix(piece.matrix, collision_matrix, next_position)
			will_collide = self.has_collisions(collision_matrix)
			if (will_collide):
				will_set = True
		return will_set


#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""
Additional hypothesis-testing routines to supplement
``scipy.stats`` and ``statsmodels``.
"""
from __future__ import absolute_import
from __future__ import with_statement
from __future__ import division
from __future__ import nested_scopes
from __future__ import generators
from __future__ import unicode_literals
from __future__ import print_function

import statsmodels
import numpy as np
from numpy import random
from neurotools.util.array import find
import scipy.stats
from typing import NamedTuple

from neurotools.stats.information import betapr
def two_tailed_sampled_test(delta):
    '''
    Directly test whether a population ``delta``
    is above or below zero more than chance.
    This is for use
    with bootstrap/shuffle tests when stronger assumptions
    may be inaccurate or risk false-positives.
    '''
    delta = np.float32(delta).ravel()
    # This is very under-powered?
    if len(delta)<100:
        raise RuntimeError(
            'You probably don\'t want to use this '
            'with fewer than 100 samples.')
    k0 = sum(delta<0)
    k1 = sum(delta>0)
    if max(k0,k1)<5:
        raise RuntimeError(
            'There probably aren\'t enough samples')
    pr1gtr0 = betapr(k0,len(delta))
    pr0gtr1 = betapr(sum(delta>0),len(delta))
    pvalue  = 1-(1-min(pr1gtr0, pr0gtr1))**2
    return pvalue

class ZTestResult(NamedTuple):
    z: float
    pvalue: float
def ztest_from_moments(μ1,s1,μ2,s2):
    '''
    Calculate z-test given moments from two samples.
    '''
    Δ = μ1 - μ2
    S = np.sqrt(s1**2 + s2**2)
    z = Δ/S
    pvalue = 2*scipy.stats.norm.sf(abs(z))
    return ZTestResult(z,pvalue)

import neurotools.stats
class WeightedTtestResult(NamedTuple):
    t:float
    pvalue:float
    dof:float
    alternative:str
    sem:float
    mu:float
    s:float
def weighted_ttest_1samp(
    x,
    w,
    alternative='two-sided'):
    '''
    Test if mean of independent samples ``x``
    with weights ``w`` is different from zero using
    a to-tailed one-sample t-test.
    '''
    n   = np.sum(w) # Effective sample size
    dof = n-1       # Degrees of freedom
    μ,σ = neurotools.stats.weighted_avg_and_std(x,w)
    s   = σ*np.sqrt(n/(n-1)) # sample s.d.
    sem = s/np.sqrt(n)       # standard error
    t = μ/sem                # score
    if alternative=='two-sided':
        pvalue = scipy.stats.t(dof).sf(np.abs(t))*2
    if alternative=='greater':
        pvalue = scipy.stats.t(dof).sf(t)
    if alternative=='less':
        pvalue = scipy.stats.t(dof).cdf(t)
    return WeightedTtestResult(t,pvalue,dof,alternative,sem,μ,s)

def beta_propotion_test(
    a1,b1,
    a2,b2,
    npts = 1000,
    eps  = 0.5):
    '''
    (experimental)

    Use a Beta distribution model to determine whether
    two propotions are significantly different.

    Parameters
    ----------
    a1: positive int
        Number of items in category 0, group 1
    b1: positive int
        Number of items in category 1, group 1
    a2: positive int
        Number of items in category 0, group 2
    b2: positive int
        Number of items in category 1, group 2

    Returns
    -------
    mudelta: float
        When positive: The probability of being in the
        second category is larger for the second group,
        compared to the first.
    p: float
        Two-tailed p-value for significant diffierence
        in rates between the groups.
    '''
    from scipy.stats import beta

    # Model count data using beta distribution
    d1 = beta(eps+a1, eps+b1)
    d2 = beta(eps+a2, eps+b2)

    # Integrate to get distr. of differences
    ll = np.linspace(0,1,npts+1)
    pdelta = np.convolve(d1.pdf(ll), d2.pdf(ll)[::-1])
    pdelta /= np.sum(pdelta)

    # average difference
    l2 = np.linspace(-1,1,2*npts+1)
    mudelta = l2@pdelta

    # P value
    if mudelta>0:
        p = np.sum(pdelta[l2<0])*2
    else:
        p = np.sum(pdelta[l2>0])*2
    return mudelta,p

def cohen_d(x,y):
    '''
    Calculate Cohen's d effect-size summary for independent
    samples from two unpaired populations.
    '''
    nx = len(x)
    ny = len(y)
    dof = nx + ny - 2
    vx = np.nanvar(x, ddof=1)
    vy = np.nanvar(y, ddof=1)
    v  = ((nx-1)*vx + (ny-1)*vy) / dof
    d = (np.nanmean(x) - np.nanmean(y)) / np.sqrt(v)
    # Cohen, Jacob (1988).
    # Sawilowsky, S (2009)
    bins = [-np.inf,0.01,0.20,0.50,0.80,1.20,2.0,np.inf]
    sizenames = ['Very small',
        'Small',
        'Medium',
        'Large',
        'Very large',
        'Huge']
    i = np.digitize(abs(d),bins,right=True)-1
    return d, sizenames[i]


import logging
import re
from collections import defaultdict
from urllib.parse import urljoin

import requests_cache
from tqdm import tqdm

from configs import configure_argument_parser, configure_logging
from constants import (
    BASE_DIR,
    DOWNLOADS_DIR,
    MAIN_DOC_URL,
    MAIN_PEP_URL,
    WHATS_NEW_URL,
    DOWNLOAD_URL,
    EXPECTED_STATUS,
    CONNECTION_ERROR_MESSAGE,
    VERSION_ERROR_MESSAGE,
    DOWNLOAD_COMPLITE,
    PATTERN,
    UNEXPECTED_PEP_STATUS,
    COMMANDS_ARGUMENTS,
    PARSER_START_MESSAGE,
    PARSER_FINISH_MESSAGE,
    PROGRAM_ERROR_MESSAGE
)
from outputs import control_output
from utils import find_tag, get_soup

def whats_new(session):
    logs = []
    results = [('Ссылка на статью', 'Заголовок', 'Редактор, Автор')]
    for section in tqdm(
        get_soup(
            session, WHATS_NEW_URL
            ).select(
            '#what-s-new-in-python div.toctree-wrapper li.toctree-l1'
            )
    ):
        version_link = urljoin(WHATS_NEW_URL, section.select('a')[0]['href'])
        try:
            soup = get_soup(session, version_link)
            results.append(
                (
                    version_link,
                    find_tag(soup, 'h1').text,
                    find_tag(soup, 'dl').text.replace('\n', ' '),
                )
            )
        except ConnectionError:
            logs.append(CONNECTION_ERROR_MESSAGE.format(url=version_link))
    for log in logs:
        logging.info(log)
    return results

def latest_versions(session):
    for ul in get_soup(
        session, MAIN_DOC_URL
        ).select(
            'div.sphinxsidebarwrapper ul'
    ):
        if 'All versions' in ul.text:
            a_tags = ul.find_all('a')
            break
    else:
        raise RuntimeError(VERSION_ERROR_MESSAGE)
    results = [('Ссылка на документацию', 'Версия', 'Статус')]

    for a_tag in a_tags:
        text_match = re.search(PATTERN, a_tag.text)
        if text_match is not None:
            version, status = text_match.groups()
        else:
            version, status = a_tag.text, ''
        results.append(
            (a_tag['href'], version, status)
        )

    return results

def download(session):
    archive_url = urljoin(
        DOWNLOAD_URL,
        get_soup(
            session, DOWNLOAD_URL
            ).select_one(
                'table.docutils td > a[href$="pdf-a4.zip"]'
            )['href']
    )
    filename = archive_url.split('/')[-1]
    downloads_dir = BASE_DIR / DOWNLOADS_DIR
    downloads_dir.mkdir(exist_ok=True)
    archive_path = downloads_dir / filename
    response = session.get(archive_url)
    with open(archive_path, 'wb') as file:
        file.write(response.content)
    logging.info(DOWNLOAD_COMPLITE.format(archive_path=archive_path))

def pep(session):
    results_count = defaultdict(int)
    logs = []
    for tr_tag in tqdm(
        get_soup(
            session, MAIN_PEP_URL
            ).select(
                '#numerical-index tbody tr')
    ):
        try:
            status_pep = find_tag(tr_tag, 'td').text[1:]
            expected_status = EXPECTED_STATUS.get(status_pep, [])
            pep_link = urljoin(MAIN_PEP_URL, find_tag(tr_tag, 'a')['href'])
            description = find_tag(
                get_soup(session, pep_link),
                'dl', attrs={'class': 'rfc2822 field-list simple'})
            td = description.find(string='Status')
            status = td.find_parent().find_next_sibling().text
            if status not in expected_status:
                logs.append(
                    UNEXPECTED_PEP_STATUS.format(
                        pep_link=pep_link,
                        status_pep=status_pep,
                        expected_status=expected_status,
                    )
                )
            results_count[status] += 1
        except ConnectionError:
            logs.append(CONNECTION_ERROR_MESSAGE.format(url=pep_link))
    for log in logs:
        logging.info(log)
    return [
        ('Статус', 'Количество'),
        *results_count.items(),
        ('Всего', sum(results_count.values()))
    ]

MODE_TO_FUNCTION = {
    'whats-new': whats_new,
    'latest-versions': latest_versions,
    'download': download,
    'pep': pep,
}

def main():
    configure_logging()
    logging.info(PARSER_START_MESSAGE)
    arg_parser = configure_argument_parser(MODE_TO_FUNCTION.keys())
    args = arg_parser.parse_args()
    logging.info(COMMANDS_ARGUMENTS.format(args=args))
    try:
        session = requests_cache.CachedSession()
        if args.clear_cache:
            session.cache.clear()
        parser_mode = args.mode
        results = MODE_TO_FUNCTION[parser_mode](session)

        if results is not None:
            control_output(results, args)
    except Exception as error:
        logging.exception(
            msg=PROGRAM_ERROR_MESSAGE.format(error=error),
            stack_info=True
        )
    logging.info(PARSER_FINISH_MESSAGE)

if __name__ == '__main__':
    main()


import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d, Axes3D

mu = 398600 #[km^3/s^2]
I_vec = np.array([1.0, 0.0, 0.0])
J_vec = np.array([0.0, 1.0, 0.0])
K_vec = np.array([0.0, 0.0, 1.0])

def mag(a):
    return np.linalg.norm(a)

def state_to_orbital_elements(pos, vel):
  h = np.cross(pos,vel)
  h_mag = mag(h)
  i = np.arccos(h[2]/h_mag)
  e_vec = np.cross(vel, h) / mu - pos / mag(pos)
  e = mag(e_vec)

  N = np.cross(K_vec, h)

  small_omega = np.arctan2(np.dot(h, np.cross(N, e_vec)), h_mag*np.dot(e_vec, N))
  big_omega = np.arctan2( np.dot(N, J_vec), np.dot(N, I_vec) )
  true_anomaly = np.arctan2(np.dot(h, np.cross(e_vec, pos)), h_mag*np.dot(e_vec, pos) )

  specific_energy = 0.5 * mag(vel)**2 - (mu / mag(pos))
  a = -mu / (2*specific_energy)

  print("Q2: ORBITAL ELEMENTS FROM STATE:")
  print("Pos = ", pos,   "Vel = ", vel)
  print("=============================")
  print("a \t=", a)
  print("e \t=", e)
  print("i \t=", i)
  print("Omega \t=", big_omega)
  print("w \t=", small_omega)
  print("nu \t=", true_anomaly) #true anomaly
  print("h \t=", h, "\n\n") #specific angular momentum

  return a, e, i, big_omega, small_omega, true_anomaly, h_mag

def orbital_elements_to_state(a, e, i, big_omega, small_omega, true_anom):
    N = np.array([np.cos(big_omega), np.sin(big_omega), 0.0])
    N = N / mag(N)

    h_norm = np.array([np.sin(i)*np.sin(big_omega), -np.sin(i)*np.cos(big_omega), np.cos(i)])
    N_t = np.cross(h_norm, N)
    r_mag = a*(1 - e**2) / (1 + e*np.cos(true_anom))

    radial_vec = np.cos(true_anom + small_omega)*N + np.sin(true_anom + small_omega) * N_t
    perp_vec = -np.sin(true_anom + small_omega)*N + np.cos(true_anom + small_omega) * N_t

    h_mag = np.sqrt(mu*a*(1 - e**2))

    r = r_mag*radial_vec
    v = mu/h_mag * ( (1+e*np.cos(true_anom))*perp_vec + e*np.sin(true_anom) * radial_vec )

    print("Q3: STATE VECTORS FROM ORBITAL ELEMENTS:")
    print("a = ", a, "  e = ", e, "  i = ", i)
    print("small omega = ", small_omega, "  big omega = ", big_omega)
    print("true anomaly = ", true_anom)
    print("====================================")
    print("posiiton = ", r)
    print("velocity = ", v)
    print("h \t = ", h_norm*h_mag, "\n")

def plot_3d_orbit(a, e, i, big_omega, small_omega, true_anom, h_mag):
    EARTH_RAD = 6378
    fig = plt.figure()
    ax = Axes3D(fig)

    N = 1000
    orbit_states = np.zeros([N,3])

    for i in range(N):
        true_anomaly = 2*np.pi*i / N

        r_sat = h_mag**2 / ((mu) * (1 + e* np.cos(true_anomaly)))
        #print(r_sat)
        orbit_states[i, 0] = r_sat*np.cos(true_anomaly)
        orbit_states[i, 1] = r_sat*np.sin(true_anomaly)
        orbit_states[i,2] = 0.0
        #print(h_mag, orbit_states[i,:])

    u, v = np.mgrid[0:2*np.pi:20j, 0:np.pi:10j]
    x = EARTH_RAD*np.cos(u)*np.sin(v)
    y = EARTH_RAD*np.sin(u)*np.sin(v)
    z = EARTH_RAD*np.cos(v)
    #ax.plot3D(orbit_states[:,0], orbit_states[:,1], orbit_states[:,2], color='purple')
    #ax.plot_wireframe(x, y, z, label='Earth', color='r', zorder=0.3)
    #ax.plot_surface(x, y, z, rstride=1, cstride=1, cmap='winter', edgecolor='none')
    ax.plot3D(orbit_states[:,0], orbit_states[:,1], orbit_states[:,2], 'purple')
    ax.scatter([0], [0], [0], color="g", s=EARTH_RAD)

    #plt.legend()

    plt.grid()
    plt.show()

position = np.array([-2465, 6040, 3413])    #[km]
velocity = np.array([1.727, 3.893, -5.883]) #[km/sec]
a, b, c, d, e, f, g = state_to_orbital_elements(position, velocity)
orbital_elements_to_state(a, b, c, d, e, f)

#plot_3d_orbit(a, b, c, d, e, f, g)

#plot in pyplot and find where orbit intersects with planet projection


import MDAnalysis as mda
from subprocess import call
from tqdm import tqdm

def Dcd2Xtc(psf, dcd):
    psffile = psf
    dcdfile = dcd
    xtcfile = dcdfile[0:-4] + '.xtc'
    u = mda.Universe(psffile, dcdfile)
    with mda.Writer(xtcfile, u.atoms.n_atoms)as w:
        for ts in tqdm(u.trajectory,
                       desc=f'{xtcfile}', leave=False):
            w.write(u)

def RemoveDcd(dcd):
    args = f'rm -rf {dcd}'.split()
    call(args)

if __name__ == '__main__':
    Dcd2Xtc()


import logging
import pandas as pd
from pathlib import Path

# Create logger
logger = logging.getLogger(__name__)

def get_logfc_matrix(paths):
    """
    Combines contrast files and returns it as a matrix with a multi-level
    index.
    """

    # Output format
    #                      basemean, log2foldchange, lfcse, stat, pvalue, padj,
    #   treatment,   gene,
    # treatment_1, gene_1,   mean_1,           fc_1,  se_1, st_1,    p_1, pa_1,
    #              gene_2,   mean_2,           fc_2,  se_2, st_2,    p_2, pa_2,
    # treatment_2, gene_1,   mean_3,           fc_3,  se_3, st_3,    p_3, pa_3,
    #              gene_2,   mean_4,           fc_4,  se_4, st_4,    p_4, pa_4,

    concat_df = None

    for path in paths:
        logger.debug(f"Processing '{path.name}'")
        treatment_name = path.name.rsplit(".", 1)[0]

        df = pd.read_csv(path, sep=",")
        df = format_logfc_df(df, treatment_name)

        if concat_df is None:
            concat_df = df.copy()

        else:
            concat_df = pd.concat([concat_df, df])

    return concat_df

def format_logfc_df(df, treatment_name):
    """
    Formats logfc dataframe before concatenation.
    """

    # Clean column names, insert treatment name, and set multi-index
    df.columns = df.columns.str.lower()
    df.insert(0, "treatment", treatment_name)
    df.columns.values[1] = "gene"
    df.set_index(["treatment", "gene"], inplace=True)

    # Catch non-unique gene index
    try:
        if not df.index.is_unique:
            msg = (
                f"'{treatment_name}' has a non-unique index which may "
                "cause downstream errors. Please check that its gene list is "
                "unique"
            )
            raise IndexError(msg)

    except IndexError as error:
        logger.exception(error)
        raise

    return df


import logging
import coloredlogs

def init_log(logfile, loglevel):
    format_string = '%(asctime)s [%(process)d] [%(levelname)s] (%(name)s:%(funcName)s) %(message)s'
    time_string = "%y-%m-%d %H:%M:%S"
    console_formatter = coloredlogs.ColoredFormatter(format_string, time_string)
    console_handler = logging.StreamHandler()
    console_handler.setFormatter(console_formatter)
    handlers = [console_handler]

    time_formatter = logging.Formatter(format_string, time_string)
    time_handler = logging.FileHandler(f'{logfile}')
    time_handler.setFormatter(time_formatter)
    handlers += [time_handler]

    # steal uvicorn logger
    logging.getLogger("uvicorn").handlers.clear()
    access_log = logging.getLogger("uvicorn.access")
    access_log.handlers.clear()
    access_log.addHandler(console_handler)
    access_log.addHandler(time_handler)

    # steal gunicorn logger
    logging.getLogger("gunicorn").handlers.clear()
    access_log = logging.getLogger("gunicorn.access")
    access_log.handlers.clear()
    access_log.addHandler(console_handler)
    access_log.addHandler(time_handler)

    # current level
    loglevel = logging.getLevelName(loglevel)
    if type(loglevel) != int:
        loglevel = logging.DEBUG

    # load config
    logging.basicConfig(
        level=loglevel,
        handlers=handlers,
    )


import requests

url = 'https://rickandmortyapi.com/api/'
endpointCharacter = 'character/'

characterList = [] #List to append character info

########## FETCH DATA FUNCTION ##########

def fetchData(url, listName): #Function parameters --> url: API url, listName: list to append the results
    print('Fetching data...\n') #Status control message

    response = requests.get(url).json() #Response from API

    if not response.get('error'): #If everything is correct calling the API
        if response.get('results'): #If 'results' exist means the response from the API is all character's list
            for item in response.get('results'):
                listName.append({ #Apped data to the list
                    'id': item.get('id'),
                    'name': item.get('name'),
                    'status': item.get('status'),
                    'species': item.get('species'),
                    'type': item.get('type'),
                    'gender': item.get('gender'),
                    'origin': item.get('origin').get('name'),
                    'location': item.get('location').get('name')
                })

            if response.get('info').get('next'): #As the API respond is paginated, if there is another page
                fetchData(response.get('info').get('next'), listName) #Call fetchData again with new function parameters --> new page url, and same list name to apped the data

        else: #If 'results' does not exist means the response from the API is a specific character info
            characterList.append({ #Apped data to the list
                'id': response.get('id'),
                'name': response.get('name'),
                'status': response.get('status'),
                'species': response.get('species'),
                'type': response.get('type'),
                'gender': response.get('gender'),
                'origin': response.get('origin').get('name'),
                'location': response.get('location').get('name')
            })

    else: #If something goes wrong vaalling the API
        print('\n' + response.get('error') + ', please try again' + '\n') #Print the error message

########## LIST ALL CHARACTERS FUNCTION ##########

def listAllCharacters():
    fetchData(url + endpointCharacter, characterList) #Call API fetchData with --> API url with characters endpoint, and characterList to apped the data

    return characterList

########## SHOW CHARACTER INFO FUNCTION ##########

def showCharacterInfo(id):
    fetchData(url + endpointCharacter + id, characterList) #Call API fetchData with --> API url with the character id, and characterList to apped the data

    return characterList