#!/usr/bin/env python3
# -*- coding: utf-8 -*-

__author__ = 'Owen_Study/owen_study@126.com'
# Create date: 17-7-18 下午10:43

import shelve
import const, publicparameters, ordermanage, common
import priceupdate, ormmysql
import time
'''对查找到的COIN进行交易，买入或者卖出'''

'''交易结构'''
class OrderItem(object):
    def __init__(self, market, coin):

        # 预测价格的信息
        self.priceitem = None
        # market
        self.market = market
        # coin
        # 可以传入mk_type,默认为btc
        if len(coin.split('_')) == 2:
            self.coin_pair = coin
            self.coin = coin.split('_')[0]
        else:
            self.coin_pair=coin+'_btc'
            self.coin = coin
        # 买入价格
        self.buy_price = None
        # 买入金额，默认为10RMB
        self.buy_amount = None
        # 买入order_id
        self.buy_order_id = None

        # 买入时间
        self.buy_date = None
        # 买入UNIT
        self.buy_units = None
        # 买入状态, Open, Closed
        self.buy_status = None

        # 卖出相关
        # 卖出的order_id
        self.sell_order_id = None
        # 卖出价格
        self.sell_price = None
        # 卖出UNITS
        self.sell_units = None
        # 卖出金额
        self.sell_amount = None
        # 卖出日期
        self.sell_date = None
        # 卖出状态
        self.sell_status = None
    '''生成表的动态类，用来自动生成ORM的模型'''
    def gen_table_class(self):

        create_table_sql = 'CREATE TABLE IF NOT EXISTS t_coin_trans (\n'+\
                    'trans_id INT(11) NOT NULL AUTO_INCREMENT,\n'
        table_class= 'orderitemTable = Table(\n'+ '"t_coin_trans", metadata,\n'+ \
                     "Column('trans_id', INT(11), primary_key=True),\n"
        for name, value in vars(self).items():
            # Column('trans_id', Integer, primary_key=True),
            table_class = table_class + "Column('{0}', VARCHAR(100)),\n".format(name)
            create_table_sql = create_table_sql + "'{0}' VARCHAR(100),\n".format(name)
        create_table_sql = create_table_sql[:len(create_table_sql)-2]+'\n)'
        table_class = table_class[:len(table_class)-2] +'\n)'
        print(table_class)
        print(create_table_sql)
        return table_class
    '''转换成字符保存'''
    # TODO
    def __repr__(self):
        order_detail_format = '{0}|{1}|{2}|{3}|{4}|{5}|{6}|{7}|{8}|{9}|{10}|{11}|{12}|{13}|\n'
        if self.sell_amount is not None:
            profit_amount = round(self.sell_amount - self.buy_amount,10)
        else:
            profit_amount = ''
        return order_detail_format.format(self.market.rjust(8),self.coin.rjust(6), profit_amount, self.buy_order_id,self.buy_date,\
                                          self.buy_status,self.buy_price,self.buy_amount,self.buy_units,self.sell_order_id,\
                                          self.sell_date,self.sell_status,self.sell_amount,self.sell_units)
    # 打印出来字符
    def __str__(self):
        return self.__repr__()

'''aex trans item，网站的交易历史记录'''
class AEXTransItem(object):
    def __init__(self, transitem ):
        self.id=transitem.get('id')
        self.coin=transitem.get('coinname')
        self.price=transitem.get('price')
        self.volume=transitem.get('volume')
        self.btcvolume=float(self.price) * float(self.volume)
        self.buyer_id=transitem.get('buyer_id')
        self.seller_id=transitem.get('seller_id')
        self.trans_time=transitem.get('time')

'''交易类'''
class CoinTrans(object):
    def __init__(self, market):
        # 订单状态
        const.ORDER_STATUS_OPEN = 'open'
        const.ORDER_STATUS_CLOSED = 'closed'
        const.ORDER_STATUS_CANCEL = 'cancelled'
        const.TRANS_TYPE_BUY='buy'
        const.TRANS_TYPE_SELL='sell'
        # 取消订单状态
        const.CANCEL_STATUS_SUCC = 'success'
        const.CANCEL_STATUS_FAIL= 'fail'

        # 最大的交易订单数，
        self.__max_open_order_pool = publicparameters.MAX_OPEN_ORDER_POOL
        # 每次交易的金额
        self.__trans_amount_per_trans = publicparameters.TRANS_AMOUNT_PER_ORDER
        # 止盈比例
        self.__sell_profit_rate = publicparameters.SELL_PROFIT_RATE
        # 市场的交易处理器
        self.order_market = ordermanage.OrderManage(market)
        # 市场
        self.market = market

        pass
    '''卖出条件检查'''
    def sell_check(self):
        open_order_list = ormmysql.openorderlist()
        for curroderitem in open_order_list:
            # The transaction of selling is in progress then check next
            if curroderitem.sell_status ==const.ORDER_STATUS_OPEN:
                continue
            pricebuffer = priceupdate.PriceBuffer(self.market, save_log_flag=False)
            # 获取当前的价格

            newpriceitem = pricebuffer.getpriceitem(self.market, curroderitem.coin+'_btc')
            if newpriceitem is not None and curroderitem.buy_status == const.ORDER_STATUS_CLOSED:
                # 测试，卖单提前生成好，等待直接成效
                rounding_num_unit = publicparameters.rounding_unit(curroderitem.coin)
                rounding_num_price = publicparameters.rounding_price(curroderitem.coin)
                default_sell_price = round(curroderitem.buy_price*(1+publicparameters.SELL_PROFIT_RATE),rounding_num_price)
                # trans_status = self.coin_trans(self.market, 'sell', default_sell_price, curroderitem.priceitem)
                # if newpriceitem.buy_price >= curroderitem.buy_price*(1+publicparameters.SELL_PROFIT_RATE):
                #     # 执行实际的卖出操作
                #     trans_status = self.coin_trans(self.market, 'sell', newpriceitem.buy_price, curroderitem.priceitem)
                #
                #     if trans_status is True:
                #         pass
                        # print('{0}:已经成功卖出,价格:{1}, coin: {2}, 盈利百分比: {3}'.format(common.get_curr_time_str(), newpriceitem.buy_price,\
                        #                                                         curroderitem.coin, publicparameters.SELL_PROFIT_RATE))

    '''查询已经存在的orderitem, 并返回'''
    def get_order_item(self, priceitem):
        orderitem_result = None
        order_list = ormmysql.openorderlist()
        for item in order_list:
            if item.priceitem.pricedate == priceitem.pricedate and item.priceitem.coin == priceitem.coin\
                and item.priceitem.buy_price == priceitem.buy_price:
                orderitem_result = item
                break
        return orderitem_result

    '''更新订单列表中状态'''
    def update_order_status(self):
        order_list = ormmysql.openorderlist()
        for orderitem in order_list:
            if orderitem.buy_status == const.ORDER_STATUS_OPEN:
                order_status = self.order_market.getOrderStatus(orderitem.buy_order_id, orderitem.priceitem.coin)
                # 处理状态取值时错误的情况
                if order_status is None:
                    continue
                orderitem.buy_status = order_status
                ormmysql.updateorder(orderitem)
            if orderitem.sell_status == const.ORDER_STATUS_OPEN:
                order_status = self.order_market.getOrderStatus(orderitem.sell_order_id, orderitem.priceitem.coin)
                # 处理状态取值时错误的情况
                if order_status is None:
                    continue
                orderitem.sell_status = order_status
                ormmysql.updateorder(orderitem)
                if order_status == const.ORDER_STATUS_CLOSED:
                    orderitem.sell_date = common.get_curr_time_str()
                    ormmysql.updateorder(orderitem)
                    # 把交易记录从交易表转移到LOG表
                    ormmysql.delorder(orderitem)
                    profit_amount = round(orderitem.sell_amount - orderitem.buy_amount,10)
                    print('{0}:[{1}]{smile}已经成功交易,盈利{2}！ BuyPrice:{3}, SellPrice:{4}, ProfiteRate:{5}'.format(\
                            common.get_curr_time_str(), orderitem.priceitem.coin, profit_amount, orderitem.buy_price, orderitem.sell_price,\
                            publicparameters.SELL_PROFIT_RATE, smile='^_^ '*5))

                    # the sell status is closed to move log table
            # 这里会导致出问题，出现上面更新后又再次删除的情况
            # if orderitem.sell_status == const.ORDER_STATUS_CLOSED:
            #     # 把交易记录从交易表转移到LOG表
            #     ormmysql.delorder(orderitem)
        pass

    '''取消超时买入订单，买入挂单超过指定的时间则取消'''
    # TODO 这个功能还需要完善
    def cancle_ot_buy_order(self, duration):
        open_order_list = ormmysql.openorderlist()
        curr_time = common.CommonFunction.get_curr_date()
        for curr_order_item in open_order_list:
            diff = curr_time - common.CommonFunction.strtotime(curr_order_item.buy_date)
            diffseconds = diff.seconds
            # Only cancel the buy status =open
            if diffseconds >duration and curr_order_item.buy_status == const.ORDER_STATUS_OPEN:
                cancel_status = self.order_market.cancelOrder(curr_order_item.buy_order_id, curr_order_item.coin)
                if cancel_status == 'success':
                    curr_order_item.buy_status=const.ORDER_STATUS_CANCEL
                    # update the buy status
                    ormmysql.updateorder(curr_order_item)
                    # move the records to log table
                    ormmysql.delorder(curr_order_item)
                    pass

    # '''保存订单到数据库文件中以便查询'''
    # def save_order(self, orderitem, ordertype = 'new'):
    #     # 保存新的和更新的订单到文件中
    #     open_trans_file_db = shelve.open(self.open_trans_file_db_name, writeback=True)
    #     ordernum = len(open_trans_file_db.items()) + 1
    #     ordername = 'order{0}'.format(ordernum)
    #     if ordertype == 'new':
    #         # 直接增加一条记录
    #         open_trans_file_db[ordername] = orderitem
    #     #     查找到对应的记录并更新
    #     elif ordertype == 'update':
    #         for order in open_trans_file_db.items():
    #             curritem = order[1]
    #             if curritem.coin == orderitem.coin and curritem.buy_date == orderitem.buy_date:
    #                 open_trans_file_db[order[0]] = orderitem
    #                 break
    #     open_trans_file_db.close()
    #
    #     pass
    #

    # 判断是不是满足买入或者卖出条件
    def check_trans_indi(self, coin=None):
        # 总共的OPEN交易订单
        total_open_count = ormmysql.openordercount()
        if total_open_count >= publicparameters.MAX_OPEN_ORDER_POOL:
            return False
        if coin is not None:
            coin_rate = self.get_coin_rate_in_open_orders(coin)
            # 大于单个COIN在总的OPEN数量中允许的最大比例
            if coin_rate > publicparameters.COIN_MAX_RATE_IN_OPEN_ORDERS:
                return False
        return True
        pass
    # coin percentage out of total allow open orders,
    def get_coin_rate_in_open_orders(self, coin):
        open_order_list = ormmysql.openorderlist()
        # total_count = ormmysql.openordercount()
        # 用POOL的最大值来检查单个币种的比例
        total_count = publicparameters.MAX_OPEN_ORDER_POOL
        coin_count = 0
        for open_order in open_order_list:
            if open_order.coin == coin:
                coin_count = coin_count + 1
        if total_count == 0:
            rate =0
        else:
            rate = round(coin_count/total_count,2)
        return rate

    # 交易测试
    def test_coin_trans(self):
        pricebuffer = priceupdate.PriceBuffer('btc38', save_log_flag=False)
        priceitem = pricebuffer.getpriceitem('btc38', 'doge_btc')
        # 循环检查OPEN订单是不是满足卖出条件

        orderstatus1 = self.coin_trans( 'btc38', 'buy', 0.01224, priceitem)
        # orderstatus2 = self.coin_trans( 'btc38', 'sell', 0.01345, priceitem)
        # print('order status 1: {0}, 2: {1}'.format(orderstatus1, orderstatus2))
        runtimes = 0
        while (True):
            self.update_order_status()
            pricebuffer = priceupdate.PriceBuffer('btc38', save_log_flag=False)
            newpriceitem = pricebuffer.getpriceitem('btc38', 'doge_btc')
            order_list = ormmysql.openorderlist()
            for openorderitem in order_list:
                if openorderitem.buy_status == const.ORDER_STATUS_CLOSED:
                    # 满足卖出条件后提交卖出订单
                    if newpriceitem.buy_price>openorderitem.buy_price*(1+self.__sell_profit_rate) and openorderitem.sell_status ==const.ORDER_STATUS_OPEN:
                        self.coin_trans('btc38', 'sell', newpriceitem.buy_price, openorderitem.priceitem)
                        print('满足卖出条件，提交了定单{0}, 卖出价格:{1}'.format(openorderitem.sell_order_id, newpriceitem.buy_price))
            runtimes = runtimes + 1
            time.sleep(5)
            print('has run %d times'%runtimes)
            pass

        # 更新订单的状态
        self.update_order_status()

    '''交易信息
    @market ==btc38, bter
    @trans_type   =='sell', 'buy'
    @trans_price    交易价格
    @price_item     预测价格时的信息，用来进行对比
    '''
    def coin_trans(self, market, trans_type, trans_price, price_item):
        coin = price_item.coin
        coin_pair = coin+'_btc'

        # 判断是不是满足交易的条件，不满足则退出不进行交易
        if trans_type == const.TRANS_TYPE_BUY:
            if self.check_trans_indi(coin) is False:
                print('{0}:不符合买入条件，可能是超过买入数量上限或者比例上限'.format(coin))
                return False

        # 对价格和交易单位进行rounding，否则有可能造成调用接口失败
        rounding_price = publicparameters.rounding_price(coin)
        rounding_unit = publicparameters.rounding_unit(coin)
        # 买入时的价格
        buy_price = price_item.buy_price
        # 交易UNITS
        trans_units = round(self.__trans_amount_per_trans / buy_price, rounding_unit)
        # 第一次交易卖出时的UNIT和买入UNIT会有一个0.5%的误差
        newtrans_units = trans_units
        # 对交易价格进行ROUNDING处理
        trans_price_rounding = round(trans_price, rounding_price)
        # 有些价格较贵，金额太小出现UNIT为0的情况，不需要再提交订单
        if trans_units < 0.00001:
            return False
        # 当前交易的对象
        if trans_type == const.TRANS_TYPE_BUY:
            orderitem = OrderItem(market, coin)
        elif trans_type == const.TRANS_TYPE_SELL:
            # 卖出时查找已经存在的priceitem，并更新相应的状态
            orderitem = self.get_order_item(price_item)
            if orderitem is None:
                return False
            else:
                trans_units = round(orderitem.buy_units, rounding_unit)
            # 卖出订单时检查买入订单的状态，如果没有买入成功则停止卖出，返回失败
            if orderitem.buy_status != const.ORDER_STATUS_CLOSED:
                # print('买入订单还没有成交，卖出取消!')
                return False
        # 交易的order_market
        order_market = self.order_market

        # 提交订单
        if trans_type == const.TRANS_TYPE_SELL:
            bal = order_market.getMyBalance(coin)
            # 可能出现余额不足的情况
            if bal > trans_units:
                trans_order = order_market.submitOrder(coin_pair, trans_type, trans_price_rounding, trans_units)
            # 处理第一次买入出现扣除手续费后卖出时余额不足的情况
            elif trans_units*0.99<bal:
                newtrans_units = round(trans_units*0.99,rounding_unit)
                trans_order = order_market.submitOrder(coin_pair, trans_type, trans_price_rounding, newtrans_units)
            # 余额不足的取消卖出交易
            else:
                orderitem.sell_status = const.ORDER_STATUS_CANCEL
                ormmysql.updateorder(orderitem)
                ormmysql.delorder(orderitem)
                print('{0}:余额不足，已经取消订单'.format(coin))
                return False
        else:
            trans_order = order_market.submitOrder(coin_pair, trans_type, trans_price_rounding, trans_units)
        #     trans_units)
        # # 取得返回订单的信息
        order_id = trans_order.order_id
        if order_id== -1111111:
            order_status = 'fail'
            return False
        else:
            order_status = order_market.getOrderStatus(order_id, coin)
            # 如果状态为None，说明取状态有异常，对于卖出默认为OPEN，防止出现多现卖出状态设置为None，继续卖出的情况
            if order_status is None:
                order_status = const.ORDER_STATUS_OPEN
        # 保留交易时的相关信息到orderitem对象中
        if trans_type == const.TRANS_TYPE_BUY:
            orderitem.buy_order_id = order_id
            orderitem.buy_status = order_status
            orderitem.buy_price = trans_price_rounding
            orderitem.buy_amount = self.__trans_amount_per_trans
            orderitem.buy_units = trans_units
            orderitem.buy_date = common.get_curr_time_str()
            orderitem.priceitem = price_item
            # 买入时新增加一个订单，卖出时则直接更新已经存在的订单
            # 保存数据到DB
            ormmysql.saveorder(orderitem)
            print('{0}:开始[{1}]交易,交易状态:{2}:PriceDate:{3},Coin:{4}, BuyPrice:{5}'.format(common.get_curr_time_str(), trans_type, order_status,
                                                               price_item.pricedate, price_item.coin,
                                                               price_item.buy_price))
        elif trans_type == const.TRANS_TYPE_SELL:
            orderitem.sell_order_id = order_id
            orderitem.sell_status = order_status
            orderitem.sell_price = trans_price_rounding
            orderitem.sell_amount = round(trans_units * trans_price_rounding, 10)
            orderitem.sell_units = trans_units
            orderitem.sell_date = common.get_curr_time_str()
            print('{0}:开始[{1}]交易,交易状态:{2}:PriceDate:{3},Coin:{4}, BuyPrice:{5}, SellPrice:{6}, ProfiteRate:{7}'.format(common.get_curr_time_str(), trans_type, order_status,
                                                               price_item.pricedate, price_item.coin,
                                                               price_item.buy_price, orderitem.sell_price, publicparameters.SELL_PROFIT_RATE))
            # 更新到DB
            ormmysql.updateorder(orderitem)
            # remove to log table if sell status is closed
            if order_status == const.ORDER_STATUS_CLOSED:
                ormmysql.delorder(orderitem)

        return True
        pass

    # 处理未完成订单的止损操作
    def stop_lost(self, curr_pricitem):
        open_order_list = ormmysql.openorderlist()
        for open_order in open_order_list:
            # 如果当前价格和买入价格小于止损的比例，则执行先取消订单再按当前价格的直接卖出操作
            curr_price = curr_pricitem.sell_price
            if curr_price/open_order.buy_price<(1 - publicparameters.STOP_LOST_RATE) and curr_pricitem.coin == open_order.coin:
            # if curr_pricitem.coin == open_order.coin:
                status = self.order_market.cancelOrder(open_order.sell_order_id, coin_code=curr_pricitem.coin)
                # #  TEST usage
                # status = const.CANCEL_STATUS_SUCC
                if status == const.CANCEL_STATUS_FAIL:
                    pass
                elif status == const.CANCEL_STATUS_SUCC:
                    # 更新订单的状态为取消
                    # ormmysql.updateorder(open_order)
                    # 重新卖出，以当前价卖出进行止损
                    sell_status = self.coin_trans(self.market,const.TRANS_TYPE_SELL,curr_price,open_order.priceitem)
                    # #  test only
                    # sell_status = True
                    # 止损卖出成功
                    if sell_status is True:
                        print("-------:(--------订单进行了止损操作,coin:{0},操作时间:{1}".format(open_order.coin,common.get_curr_time_str()))
                        self.update_order_status()
                        return True
                        pass
                    # 止损卖出失败，继续进行循环操作进行下一次的自动卖出
                    else:
                        return False
                        pass
                    pass

    #     得到aex网站的历史交易记录并保存到数据库中
    def save_aex_trans(self, coin_pair):
        trans_list = self.order_market.getMyTradeList(coin_pair)
        # 保存历史记录到数据库表中
        for transitem in trans_list:
            aextransitem = AEXTransItem(transitem)
            ormmysql.saveaextrans(aextransitem)
        pass

if __name__ == '__main__':
    #
    # orderitem = OrderItem('btc38', 'doge')
    # orderitem.gen_table_class()
    #
    #
    trans = CoinTrans('btc38')
    # test save aex trans list
    trans.save_aex_trans('bcc_btc')
    # trans.update_order_status()
    # trans.test_coin_trans()
    #
    #
    # test stop lost function
    # pricebuffer = priceupdate.PriceBuffer('btc38', save_log_flag=False)
    # priceitem = pricebuffer.getpriceitem('btc38', 'bcc_btc')
    # trans.stop_lost(priceitem)

    # trans.cancle_ot_buy_order(10)
    #
    # trans.cancle_ot_buy_order(50)
    # trans.sell_check()
    # # time.sleep(2)
    # priceitem2 = pricebuffer.getpriceitem('btc38', 'doge_btc')
    #
    # # 循环检查OPEN订单是不是满足卖出条件
    #
    # orderstatus1 = trans.coin_trans( 'btc38', 'buy', 0.009, priceitem)
    # trans.sell_check()
    # trans.update_order_status()

    # orderstatus2 = trans2.coin_trans( 'btc38', 'buy', 0.009, priceitem2)
    # print(len(publicparameters.ORDER_LIST))
    # print(len(trans2.order_list))

    # orderstatus2 = trans.coin_trans( 'btc38', 'sell', 0.01345, priceitem)
    # print('order status 1: {0}, 2: {1}'.format(orderstatus1, orderstatus2))

    # print(len(publicparameters.ORDER_LIST))
    pass


import functools
import json

import jsonschema
import tornado.web

from .. import basehandler

class ApiBaseHandler(basehandler.BaseHandler):
    def check_xsrf_cookie(self):
        pass

    def prepare(self):
        """prepare function: it is called after initialize function"""
        if self.request.headers.get("Content-Type", "").startswith("application/json"):
            try:
                self.json_args = json.loads(self.request.body, encoding="utf-8")
            except ValueError:
                raise tornado.web.HTTPError(400, "JSON is not valid")

    def write_json_response(self, dictionary, cacheable = False):
        self.set_header("Content-Type", "application/json")
        self.set_header("Cache-Control", "public, no-transform, max-age=300" if cacheable else "no-cache")
        self.write(json.dumps(dictionary, default = lambda o: o.__dict__, encoding='utf-8'))

def validate_json(schema):
    #Decorate methods with this to validate input json.
    def decorator(method):
        @functools.wraps(method)
        def wrapper(self, *args, **kwargs):
            if self.json_args is None:
                raise tornado.web.HTTPError(403, "No JSON data received")

            try:
                jsonschema.validate(self.json_args, schema)
            except jsonschema.ValidationError as e:
                raise tornado.web.HTTPError(403, e.message)

            return method(self, *args, **kwargs)
        return wrapper
    return decorator

authenticated = basehandler.authenticated
run_asynchronous = basehandler.run_asynchronous


#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
Created on Thu Dec  6 14:42:48 2018

@author: davidstupski
"""
from __future__ import division
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
#from matplotlib import cm
#from mpl_toolkits.mplot3d import Axes3D
import pandas as pd

#Need a script that highlights times of events where thermoregulatory thresholds are broken
plt.style.use("seaborn-white")
tamb = []
rad = []
sstemp = []
#mean values of all paramaters
def heat_transfer(Tb, t, i, Ta):
    hK = 0.00424
    #Ta = 10
    alpha = .903
    a_inc = .0000365683
    #i = 550
    epsilon = .95
    sigma = 5.67*10**-8
    a_surf =.0000731367
    c = 4.31
    m = 0.04
    dTbdt = -1.0 * hK*(Tb-Ta)/(c*m) + alpha *a_inc*i/(c*m) - epsilon *sigma*a_surf*((Tb+273.15)**4-(Ta+273.15)**4)/(c*m) + ((-29.83514*Tb+1763.0566)/(1000))/c - (.0022*2.71828**(.24413*Tb))/(1000)/c
    return dTbdt
#mean - lower error value for all paramaters
def heat_transfermin(Tb, t, i, Ta):
    hK = 0.00424+.00029816
    #Ta = 10
    alpha = .903-.0081*1.96
    a_inc = .0000365683
    #i = 550
    epsilon = .95
    sigma = 5.67*10**-8
    a_surf =.0000731367
    c = 4.31
    m = 0.04
    dTbdt = -1.0 * hK*(Tb-Ta)/(c*m) + alpha *a_inc*i/(c*m) - epsilon *sigma*a_surf*((Tb+273.15)**4-(Ta+273.15)**4)/(c*m) + (((-29.83514*Tb+1763.0566-56*1.96))/(1000))/c - .90*(.0022*2.71828**(.24413*Tb)-25.81*1.96)/(1000)/c
    return dTbdt

def heat_transfermax(Tb, t, i, Ta):
    hK = 0.00424-.00029816
    #Ta = 10
    alpha = .903+.0081
    a_inc = .0000365683
    #i = 550
    epsilon = .95
    sigma = 5.67*10**-8
    a_surf =.0000731367
    c = 4.31
    m = 0.04
    dTbdt = -1.0 * hK*(Tb-Ta)/(c*m) + alpha *a_inc*i/(c*m) - epsilon *sigma*a_surf*((Tb+273.15)**4-(Ta+273.15)**4)/(c*m) + (((-29.83514*Tb+1763.0566)+56*1.96)/(1000))/c - (.0022*2.71828**(.24413*Tb)+25.81*1.96)/(1000)/c
    return dTbdt

def labheat_transfer(Tb, t, i, Ta):
    hK = 0.00217
    #Ta = 10
    alpha = .903
    a_inc = .0000365683
    #i = 550
    epsilon = .95
    sigma = 5.67*10**-8
    a_surf =.0000731367
    c = 4.31
    m = 0.04
    dTbdt = -1.0 * hK*(Tb-Ta)/(c*m) + alpha *a_inc*i/(c*m) - epsilon *sigma*a_surf*((Tb+273.15)**4-(Ta+273.15)**4)/(c*m) + ((-29.83514*Tb+1763.0566)/(1000))/c - (.0022*2.71828**(.24413*Tb))/(1000)/c
    return dTbdt
#mean - lower error value for all paramaters
def labheat_transfermin(Tb, t, i, Ta):
    hK = 0.00217+.00029816
    #Ta = 10
    alpha = .903-.0081
    a_inc = .0000365683
    #i = 550
    epsilon = .95
    sigma = 5.67*10**-8
    a_surf =.0000731367
    c = 4.31
    m = 0.04
    dTbdt = -1.0 * hK*(Tb-Ta)/(c*m) + alpha *a_inc*i/(c*m) - epsilon *sigma*a_surf*((Tb+273.15)**4-(Ta+273.15)**4)/(c*m) + (((-29.83514*Tb+1763.0566-56*1.96))/(1000))/c - .90*(.0022*2.71828**(.24413*Tb)-25.81*1.96)/(1000)/c
    return dTbdt

def labheat_transfermax(Tb, t, i, Ta):
    hK = 0.00217-.00029816
    #Ta = 10
    alpha = .903+.0081
    a_inc = .0000365683
    #i = 550
    epsilon = .95
    sigma = 5.67*10**-8
    a_surf =.0000731367
    c = 4.31
    m = 0.04
    dTbdt = -1.0 * hK*(Tb-Ta)/(c*m) + alpha *a_inc*i/(c*m) - epsilon *sigma*a_surf*((Tb+273.15)**4-(Ta+273.15)**4)/(c*m) + (((-29.83514*Tb+1763.0566)+56*1.96)/(1000))/c - (.0022*2.71828**(.24413*Tb)+25.81*1.96)/(1000)/c
    return dTbdt

def water_loss_value(Tb):
    waterloss= (.0022*2.71828**(.24413*Tb))
    return waterloss
T0 = 37.5
t = np.linspace(0,250,1000)
test_temps = [20,21,22, 23,24,25, 26,27,28, 29,30,31, 32, 33,34,35,36, 37, 38]
fin_temps_solar = []
fin_temps_solar_max= []
fin_temps_solar_min = []
fin_temps_lab = []
fin_temps_lab_max = []
fin_temps_lab_min = []

for i in test_temps:
    fin_temps_solar.append(odeint(heat_transfer, T0, t,args =(1100, i))[-1][0])
    fin_temps_solar_min.append(odeint(heat_transfermin, T0, t,args =(1100, i))[-1][0])
    fin_temps_solar_max.append(odeint(heat_transfermax, T0, t,args =(1100, i))[-1][0])

for i in test_temps:
    fin_temps_lab.append(odeint(labheat_transfer, T0, t,args =(0, i))[-1][0])
    fin_temps_lab_min.append(odeint(labheat_transfermin, T0, t,args =(0, i))[-1][0])
    fin_temps_lab_max.append(odeint(labheat_transfermax, T0, t,args =(00, i))[-1][0])

#print fin_temps_solar
#print fin_temps_solar_max
#print fin_temps_solar_min
water_vector_solar = []
water_vector_solarmax = []
water_vector_solarmin=[]
water_vector_lab = []
water_vector_labmax =[]
water_vector_labmin= []

for i in fin_temps_solar:
    x = water_loss_value(i)
    water_vector_solar.append(x)
for i in fin_temps_solar_max:
    x = water_loss_value(i)
    water_vector_solarmax.append(x)
for i in fin_temps_solar_min:
    x = water_loss_value(i)
    water_vector_solarmin.append(x)

for i in fin_temps_lab:
    x = water_loss_value(i)
    water_vector_lab.append(x)
for i in fin_temps_lab_max:
    x = water_loss_value(i)
    water_vector_labmax.append(x)
for i in fin_temps_lab_min:
    x = water_loss_value(i)
    water_vector_labmin.append(x)

new_df = pd.DataFrame()
new_df["solar"]= water_vector_solar
new_df["solar lower"]= water_vector_solarmin
new_df["solar upper"] = water_vector_solarmax
new_df["lab"]= water_vector_lab
new_df["lab upper"] = water_vector_labmax
new_df["lab lower"] = water_vector_labmin

fig, ax = plt.subplots()
ax.plot(test_temps, water_vector_solar, color = "red", label = "Insolated")
ax.plot(test_temps, water_vector_lab, color = "blue", label = "Laboratory", linestyle = "--")
ax.plot(test_temps, water_vector_solarmin, color = "red", linestyle = "")
ax.plot(test_temps, water_vector_labmin, color = "blue", linestyle = "")
ax.plot(test_temps, water_vector_solarmax, color = "red", linestyle = "")
ax.plot(test_temps, water_vector_labmax, color = "blue", linestyle = "" )
ax.fill_between(test_temps, new_df["lab upper"], new_df["lab lower"], color = "blue", alpha = '0.2')
ax.fill_between(test_temps, new_df["solar upper"], new_df["solar lower"], color = "red", alpha = '0.2')

ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
ax.legend()
ax.set_xlabel("Ambient Temperature (C)")
ax.set_ylabel("Predicted Waterloss (mJ/s/g)")
plt.savefig("/Users/davidstupski/Desktop/water_loss_fig.pdf")


# -*- coding: utf-8 -*-
import json
import time

from datetime import datetime, timedelta

from django.conf import settings
from django.core import mail

import mock

from olympia import amo
from olympia.abuse.models import AbuseReport
from olympia.access.models import Group, GroupUser
from olympia.activity.models import ActivityLog
from olympia.addons.models import (
    Addon, AddonApprovalsCounter, AddonReviewerFlags, AddonUser)
from olympia.amo.tests import (
    TestCase, addon_factory, file_factory, user_factory, version_factory)
from olympia.files.models import File, FileValidation, WebextPermission
from olympia.ratings.models import Rating
from olympia.reviewers.models import (
    AutoApprovalNotEnoughFilesError, AutoApprovalNoValidationResultError,
    AutoApprovalSummary, RereviewQueueTheme, ReviewerScore,
    ReviewerSubscription, ViewFullReviewQueue, ViewPendingQueue,
    ViewUnlistedAllList, send_notifications, set_reviewing_cache)
from olympia.users.models import UserProfile
from olympia.versions.models import Version, version_uploaded

def create_search_ext(name, version_str, addon_status, file_status,
                      channel):
    addon, created_ = Addon.objects.get_or_create(
        name__localized_string=name,
        defaults={'type': amo.ADDON_SEARCH, 'name': name})
    version, created_ = Version.objects.get_or_create(
        addon=addon, version=version_str, defaults={'channel': channel})
    File.objects.create(version=version, filename=u"%s.xpi" % name,
                        platform=amo.PLATFORM_ALL.id, status=file_status)
    # Update status *after* there are files:
    addon = Addon.objects.get(pk=addon.id)
    addon.update(status=addon_status)
    return addon

class TestQueue(TestCase):
    """Tests common attributes and coercions that each view must support."""
    __test__ = False  # this is an abstract test case

    def test_latest_version(self):
        addon = self.new_addon()
        v1 = addon.find_latest_version(self.channel)
        v1.update(created=self.days_ago(2))
        v1.all_files[0].update(status=amo.STATUS_PUBLIC)
        version_factory(addon=addon, version='2.0', created=self.days_ago(1),
                        channel=self.channel,
                        file_kw={'status': amo.STATUS_PUBLIC})
        version_factory(addon=addon, version='3.0', created=self.days_ago(0),
                        channel=self.channel,
                        file_kw={'status': amo.STATUS_AWAITING_REVIEW})
        row = self.Queue.objects.get()
        assert row.latest_version == '3.0'

    def test_addons_disabled_by_user_are_hidden(self):
        self.new_addon(version=u'0.1').update(disabled_by_user=True)
        assert list(self.Queue.objects.all()) == []

    def test_addons_disabled_by_admin_are_hidden(self):
        self.new_addon(version=u'0.1').update(status=amo.STATUS_DISABLED)
        assert list(self.Queue.objects.all()) == []

    def test_reviewed_files_are_hidden(self):
        self.new_addon(name='Unreviewed')
        addon_factory(name='Already Reviewed')
        assert sorted(q.addon_name for q in self.Queue.objects.all()) == (
            ['Unreviewed'])

    def test_search_extensions(self):
        self.new_search_ext('Search Tool', '0.1')
        row = self.Queue.objects.get()
        assert row.addon_name == u'Search Tool'
        assert row.addon_type_id == amo.ADDON_SEARCH

    def test_count_all(self):
        # Create two new addons and give each another version.
        version_factory(addon=self.new_addon(), version=u'2.0',
                        channel=self.channel)
        version_factory(addon=self.new_addon(), version=u'2.0',
                        channel=self.channel)
        assert self.Queue.objects.all().count() == 2

class TestPendingQueue(TestQueue):
    __test__ = True
    Queue = ViewPendingQueue
    channel = amo.RELEASE_CHANNEL_LISTED

    def new_addon(self, name=u'Pending', version=u'1.0'):
        """Creates an approved addon with two listed versions, one approved,
        the second awaiting review."""
        addon = addon_factory(
            name=name,
            version_kw={'version': u'0.0.1', 'channel': self.channel,
                        'created': self.days_ago(1)})
        version_factory(
            addon=addon, version=version, channel=self.channel,
            file_kw={'status': amo.STATUS_AWAITING_REVIEW,
                     'is_restart_required': False})
        return addon

    def new_search_ext(self, name, version, **kw):
        return create_search_ext(name, version,
                                 amo.STATUS_PUBLIC, amo.STATUS_AWAITING_REVIEW,
                                 channel=self.channel, **kw)

    def test_waiting_time(self):
        self.new_addon()
        Version.objects.update(created=datetime.utcnow())
        row = self.Queue.objects.all()[0]
        assert row.waiting_time_days == 0
        # Time zone will be off, hard to test this.
        assert row.waiting_time_hours is not None

    def test_flags_needs_admin_code_review(self):
        AddonReviewerFlags.objects.create(
            addon=self.new_addon(), needs_admin_code_review=True)

        queue = self.Queue.objects.get()
        assert queue.flags == [
            ('needs-admin-code-review', 'Needs Admin Code Review')]

    def test_flags_info_request(self):
        AddonReviewerFlags.objects.create(
            addon=self.new_addon(),
            pending_info_request=datetime.now() + timedelta(days=6))
        queue = self.Queue.objects.get()
        assert queue.flags == [('info', 'More Information Requested')]

    def test_flags_jetpack(self):
        self.new_addon().find_latest_version(self.channel).all_files[0].update(
            jetpack_version='1.8')

        queue = self.Queue.objects.get()
        assert queue.flags == [('jetpack', 'Jetpack Add-on')]

    def test_flags_is_restart_required(self):
        self.new_addon().find_latest_version(self.channel).all_files[0].update(
            is_restart_required=True)

        queue = self.Queue.objects.get()
        assert queue.flags == [('is_restart_required', 'Requires Restart')]

    def test_flags_sources_provided(self):
        self.new_addon().find_latest_version(self.channel).update(
            source='/some/source/file')

        queue = self.Queue.objects.get()
        assert queue.flags == [('sources-provided', 'Sources provided')]

    def test_flags_webextension(self):
        self.new_addon().find_latest_version(self.channel).all_files[0].update(
            is_webextension=True)

        queue = self.Queue.objects.get()
        assert queue.flags == [('webextension', 'WebExtension')]

    def test_no_flags(self):
        self.new_addon()

        queue = self.Queue.objects.get()
        assert queue.flags == []

class TestFullReviewQueue(TestQueue):
    __test__ = True
    Queue = ViewFullReviewQueue
    channel = amo.RELEASE_CHANNEL_LISTED

    def new_addon(self, name=u'Nominated', version=u'1.0',
                  addon_status=amo.STATUS_NOMINATED,
                  file_status=amo.STATUS_AWAITING_REVIEW):
        addon = addon_factory(
            name=name, status=addon_status,
            version_kw={'version': version, 'channel': self.channel},
            file_kw={'status': file_status})
        return addon

    def new_search_ext(self, name, version, **kw):
        return create_search_ext(name, version,
                                 amo.STATUS_NOMINATED,
                                 amo.STATUS_AWAITING_REVIEW,
                                 channel=self.channel, **kw)

    def test_waiting_time(self):
        self.new_addon()
        Version.objects.update(nomination=datetime.utcnow())
        row = self.Queue.objects.all()[0]
        assert row.waiting_time_days == 0
        # Time zone will be off, hard to test this.
        assert row.waiting_time_hours is not None

class TestUnlistedAllList(TestCase):
    Queue = ViewUnlistedAllList
    channel = amo.RELEASE_CHANNEL_UNLISTED
    fixtures = ['base/users']

    def new_addon(self, name=u'Unlisted', version=u'1.0',
                  addon_status=amo.STATUS_NULL,
                  file_status=amo.STATUS_PUBLIC):
        addon = addon_factory(
            name=name, status=addon_status,
            version_kw={'version': version, 'channel': self.channel},
            file_kw={'status': file_status})
        return addon

    def test_all_addons_are_in_q(self):
        self.new_addon('Public', addon_status=amo.STATUS_PUBLIC,
                       file_status=amo.STATUS_PUBLIC)
        self.new_addon('Nominated', addon_status=amo.STATUS_NOMINATED,
                       file_status=amo.STATUS_AWAITING_REVIEW)
        self.new_addon('Deleted', addon_status=amo.STATUS_PUBLIC,
                       file_status=amo.STATUS_PUBLIC).delete()
        assert sorted(q.addon_name for q in self.Queue.objects.all()) == (
            ['Deleted', 'Nominated', 'Public'])

    def test_authors(self):
        addon = self.new_addon()
        bert = user_factory(username='bert')
        ernie = user_factory(username='ernie')
        AddonUser.objects.create(addon=addon, user=bert)
        AddonUser.objects.create(addon=addon, user=ernie)
        row = self.Queue.objects.all()[0]
        self.assertSetEqual(set(row.authors),
                            {(ernie.id, 'ernie'), (bert.id, 'bert')})

    def test_last_reviewed_version(self):
        today = datetime.today().date()
        addon = self.new_addon(version='1.0')
        v2 = version_factory(addon=addon, version='2.0', channel=self.channel)
        log = ActivityLog.create(amo.LOG.APPROVE_VERSION, v2, v2.addon,
                                 user=UserProfile.objects.get(pk=999))
        version_factory(addon=addon, version='3.0', channel=self.channel)
        row = self.Queue.objects.all()[0]
        assert row.review_date == today
        assert row.review_version_num == '2.0'
        assert row.review_log_id == log.id

    def test_no_developer_actions(self):
        addon = self.new_addon(version='1.0')
        ActivityLog.create(amo.LOG.ADD_VERSION, addon.latest_unlisted_version,
                           addon, user=UserProfile.objects.get(pk=999))
        row = self.Queue.objects.all()[0]
        assert row.review_version_num is None

        ver2 = version_factory(version='2.0', addon=addon,
                               channel=self.channel)
        ActivityLog.create(amo.LOG.APPROVE_VERSION, ver2, addon,
                           user=UserProfile.objects.get(pk=999))
        row = self.Queue.objects.all()[0]
        assert row.review_version_num == '2.0'

        ver3 = version_factory(version='3.0', addon=addon,
                               channel=self.channel)
        ActivityLog.create(amo.LOG.EDIT_VERSION, ver3, addon,
                           user=UserProfile.objects.get(pk=999))
        row = self.Queue.objects.all()[0]
        # v2.0 is still the last reviewed version.
        assert row.review_version_num == '2.0'

    def test_no_automatic_reviews(self):
        ver = self.new_addon(
            name='addon789', version='1.0').latest_unlisted_version
        ActivityLog.create(
            amo.LOG.APPROVE_VERSION, ver, ver.addon,
            user=UserProfile.objects.get(pk=settings.TASK_USER_ID))
        row = self.Queue.objects.all()[0]
        assert row.review_version_num is None

    def test_latest_version(self):
        addon = addon_factory(
            version_kw={'version': u'0.1', 'channel': self.channel,
                        'created': self.days_ago(2)},
            file_kw={'created': self.days_ago(2)})
        version_factory(
            addon=addon, version=u'0.2', channel=self.channel,
            created=self.days_ago(1), file_kw={'created': self.days_ago(1)})
        version_factory(
            addon=addon, version=u'0.3', channel=self.channel)
        row = self.Queue.objects.get()
        assert row.latest_version == '0.3'

    def test_addons_disabled_by_user_are_hidden(self):
        self.new_addon().update(disabled_by_user=True)
        assert list(self.Queue.objects.all()) == []

    def test_addons_disabled_by_admin_are_hidden(self):
        self.new_addon(version=u'0.1').update(status=amo.STATUS_DISABLED)
        assert list(self.Queue.objects.all()) == []

    def test_count_all(self):
        addon1 = self.new_addon()
        version_factory(addon=addon1, version=u'0.2')
        addon2 = self.new_addon()
        version_factory(addon=addon2, version=u'0.2')
        assert self.Queue.objects.all().count() == 2

    def test_mixed_listed(self):
        unlisted_listed = addon_factory(
            status=amo.STATUS_NULL, name=u'UnlistedListed',
            version_kw={'version': u'0.1',
                        'channel': amo.RELEASE_CHANNEL_UNLISTED},
            file_kw={'status': amo.STATUS_PUBLIC})
        version_factory(addon=unlisted_listed, version=u'0.2',
                        channel=amo.RELEASE_CHANNEL_LISTED,
                        file_kw={'status': amo.STATUS_PUBLIC})

        listed_unlisted = addon_factory(
            status=amo.STATUS_NULL, name=u'ListedUnlisted',
            version_kw={'version': u'0.1',
                        'channel': amo.RELEASE_CHANNEL_LISTED},
            file_kw={'status': amo.STATUS_PUBLIC})
        version_factory(addon=listed_unlisted, version=u'0.2',
                        channel=amo.RELEASE_CHANNEL_UNLISTED,
                        file_kw={'status': amo.STATUS_PUBLIC})

        just_unlisted = addon_factory(
            status=amo.STATUS_NULL, name=u'JustUnlisted',
            version_kw={'version': u'0.1',
                        'channel': amo.RELEASE_CHANNEL_UNLISTED},
            file_kw={'status': amo.STATUS_PUBLIC})
        version_factory(addon=just_unlisted, version=u'0.2',
                        channel=amo.RELEASE_CHANNEL_UNLISTED,
                        file_kw={'status': amo.STATUS_PUBLIC})

        just_listed = addon_factory(
            status=amo.STATUS_NULL, name=u'JustListed',
            version_kw={'version': u'0.1',
                        'channel': amo.RELEASE_CHANNEL_LISTED},
            file_kw={'status': amo.STATUS_PUBLIC})
        version_factory(addon=just_listed, version=u'0.2',
                        channel=amo.RELEASE_CHANNEL_LISTED,
                        file_kw={'status': amo.STATUS_PUBLIC})

        assert self.Queue.objects.all().count() == 3
        assert [addon.addon_name for addon in self.Queue.objects.all()] == [
            'UnlistedListed', 'ListedUnlisted', 'JustUnlisted']
        assert ([addon.latest_version for addon in self.Queue.objects.all()] ==
                ['0.1', '0.2', '0.2'])

class TestReviewerSubscription(TestCase):
    fixtures = ['base/addon_3615', 'base/users']

    def setUp(self):
        super(TestReviewerSubscription, self).setUp()
        self.addon = Addon.objects.get(pk=3615)
        self.version = self.addon.current_version
        self.user_one = UserProfile.objects.get(pk=55021)
        self.user_two = UserProfile.objects.get(pk=999)
        self.reviewer_group = Group.objects.create(
            name='Reviewers: Legacy', rules='Addons:Review')
        GroupUser.objects.create(
            group=self.reviewer_group, user=self.user_one)
        self.post_reviewer_group = Group.objects.create(
            name='Reviewers: Add-ons', rules='Addons:PostReview')
        GroupUser.objects.create(
            group=self.post_reviewer_group, user=self.user_two)
        ReviewerSubscription.objects.create(
            addon=self.addon, user=self.user_one)
        ReviewerSubscription.objects.create(
            addon=self.addon, user=self.user_two)

    def test_email(self):
        es = ReviewerSubscription.objects.get(user=self.user_one)
        es.send_notification(self.version)
        assert len(mail.outbox) == 1
        assert mail.outbox[0].to == [u'del@icio.us']
        assert mail.outbox[0].subject == (
            'Mozilla Add-ons: Delicious Bookmarks Updated')

    def test_notifications(self):
        send_notifications(sender=self.version)
        assert len(mail.outbox) == 2
        emails = sorted([o.to for o in mail.outbox])
        assert emails == [[u'del@icio.us'], [u'regular@mozilla.com']]

    def test_notifications_setting_persists(self):
        send_notifications(Version, self.version)
        assert ReviewerSubscription.objects.count() == 2
        mail.outbox = []
        send_notifications(Version, self.version)
        assert len(mail.outbox) == 2

    def test_dont_send_notifications_unlisted(self):
        self.version.update(channel=amo.RELEASE_CHANNEL_UNLISTED)
        version_uploaded.send(sender=self.version)
        assert len(mail.outbox) == 0

    def test_signal_edit(self):
        self.version.save()
        assert len(mail.outbox) == 0

    def test_signal_create(self):
        v = Version.objects.create(addon=self.addon)
        version_uploaded.send(sender=v)
        assert len(mail.outbox) == 2
        assert mail.outbox[0].subject == (
            'Mozilla Add-ons: Delicious Bookmarks Updated')

    def test_signal_create_twice(self):
        v = Version.objects.create(addon=self.addon)
        version_uploaded.send(sender=v)
        mail.outbox = []
        v = Version.objects.create(addon=self.addon)
        version_uploaded.send(sender=v)
        assert len(mail.outbox) == 2

    def test_no_email_for_ex_reviewers(self):
        self.user_one.delete()
        # Remove user_one from reviewers.
        GroupUser.objects.get(
            group=self.reviewer_group, user=self.user_one).delete()
        send_notifications(sender=self.version)
        assert len(mail.outbox) == 1  # Only notification for user_two remains.

    def test_no_email_address_for_reviewer(self):
        self.user_one.update(email=None)
        send_notifications(sender=self.version)
        assert len(mail.outbox) == 1  # Only notification for user_two remains.

class TestReviewerScore(TestCase):
    fixtures = ['base/users']

    def setUp(self):
        super(TestReviewerScore, self).setUp()
        self.addon = amo.tests.addon_factory(status=amo.STATUS_NOMINATED)
        self.user = UserProfile.objects.get(email='reviewer@mozilla.com')

    def _give_points(self, user=None, addon=None, status=None):
        user = user or self.user
        addon = addon or self.addon
        ReviewerScore.award_points(
            user, addon, status or addon.status, version=addon.current_version)

    def check_event(self, type, status, event, **kwargs):
        self.addon.type = type
        assert ReviewerScore.get_event(self.addon, status, **kwargs) == event

    def test_events_addons(self):
        types = {
            amo.ADDON_ANY: None,
            amo.ADDON_EXTENSION: 'ADDON',
            amo.ADDON_THEME: 'XUL_THEME',
            amo.ADDON_DICT: 'DICT',
            amo.ADDON_SEARCH: 'SEARCH',
            amo.ADDON_LPAPP: 'LP',
            amo.ADDON_LPADDON: 'LP',
            amo.ADDON_PLUGIN: 'ADDON',
            amo.ADDON_API: 'ADDON',
            amo.ADDON_PERSONA: 'PERSONA',
            amo.ADDON_STATICTHEME: 'STATICTHEME',
        }
        statuses = {
            amo.STATUS_NULL: None,
            amo.STATUS_PENDING: None,
            amo.STATUS_NOMINATED: 'FULL',
            amo.STATUS_PUBLIC: 'UPDATE',
            amo.STATUS_DISABLED: None,
            amo.STATUS_DELETED: None,
            amo.STATUS_REJECTED: None,
            amo.STATUS_REVIEW_PENDING: None,
        }
        for tk, tv in types.items():
            for sk, sv in statuses.items():
                try:
                    event = getattr(amo, 'REVIEWED_%s_%s' % (tv, sv))
                except AttributeError:
                    try:
                        event = getattr(amo, 'REVIEWED_%s' % tv)
                    except AttributeError:
                        event = None
                self.check_event(tk, sk, event)

    def test_events_post_review(self):
        self.addon.update(status=amo.STATUS_PUBLIC)
        base_args = (self.addon, self.addon.status)
        # No version.
        assert ReviewerScore.get_event(
            *base_args, version=None,
            post_review=True) == amo.REVIEWED_EXTENSION_LOW_RISK
        # No autoapprovalsummary.
        assert ReviewerScore.get_event(
            *base_args, version=self.addon.current_version,
            post_review=True) == amo.REVIEWED_EXTENSION_LOW_RISK
        # Now with a summary... low risk.
        summary = AutoApprovalSummary.objects.create(
            version=self.addon.current_version, verdict=amo.AUTO_APPROVED,
            weight=-10)
        assert ReviewerScore.get_event(
            *base_args, version=self.addon.current_version,
            post_review=True) is amo.REVIEWED_EXTENSION_LOW_RISK
        # Medium risk.
        summary.update(weight=91)
        assert ReviewerScore.get_event(
            *base_args, version=self.addon.current_version,
            post_review=True) is amo.REVIEWED_EXTENSION_MEDIUM_RISK
        # High risk.
        summary.update(weight=176)
        assert ReviewerScore.get_event(
            *base_args, version=self.addon.current_version,
            post_review=True) is amo.REVIEWED_EXTENSION_HIGH_RISK
        # Highest risk.
        summary.update(weight=276)
        assert ReviewerScore.get_event(
            *base_args, version=self.addon.current_version,
            post_review=True) is amo.REVIEWED_EXTENSION_HIGHEST_RISK
        # Highest risk again.
        summary.update(weight=65535)
        assert ReviewerScore.get_event(
            *base_args, version=self.addon.current_version,
            post_review=True) is amo.REVIEWED_EXTENSION_HIGHEST_RISK
        # Content review is always the same.
        assert ReviewerScore.get_event(
            *base_args, version=self.addon.current_version, post_review=True,
            content_review=True) == amo.REVIEWED_CONTENT_REVIEW

    def test_award_points(self):
        self._give_points()
        assert ReviewerScore.objects.all()[0].score == (
            amo.REVIEWED_SCORES[amo.REVIEWED_ADDON_FULL])

    def test_award_points_with_extra_note(self):
        ReviewerScore.award_points(
            self.user, self.addon, self.addon.status, extra_note=u'ÔMG!')
        reviewer_score = ReviewerScore.objects.all()[0]
        assert reviewer_score.note_key == amo.REVIEWED_ADDON_FULL
        assert reviewer_score.score == (
            amo.REVIEWED_SCORES[amo.REVIEWED_ADDON_FULL])
        assert reviewer_score.note == u'ÔMG!'

    def test_award_points_bonus(self):
        user2 = UserProfile.objects.get(email='admin@mozilla.com')
        bonus_days = 2
        days = amo.REVIEWED_OVERDUE_LIMIT + bonus_days

        bonus_addon = addon_factory(
            status=amo.STATUS_NOMINATED,
            file_kw={'status': amo.STATUS_AWAITING_REVIEW})
        bonus_addon.current_version.update(
            nomination=(datetime.now() - timedelta(days=days, minutes=5))
        )
        self._give_points(user2, bonus_addon, amo.STATUS_NOMINATED)
        score = ReviewerScore.objects.get(user=user2)
        expected = (amo.REVIEWED_SCORES[amo.REVIEWED_ADDON_FULL] +
                    (amo.REVIEWED_OVERDUE_BONUS * bonus_days))

        assert score.score == expected

    def test_award_points_no_bonus_for_content_review(self):
        self.addon.update(status=amo.STATUS_PUBLIC)
        self.addon.current_version.update(nomination=self.days_ago(28))
        AutoApprovalSummary.objects.create(
            version=self.addon.current_version, verdict=amo.AUTO_APPROVED,
            weight=100)
        ReviewerScore.award_points(
            self.user, self.addon, self.addon.status,
            version=self.addon.current_version,
            post_review=False, content_review=True)
        score = ReviewerScore.objects.get(user=self.user)
        assert score.score == amo.REVIEWED_SCORES[amo.REVIEWED_CONTENT_REVIEW]

    def test_award_points_no_bonus_for_post_review(self):
        self.addon.update(status=amo.STATUS_PUBLIC)
        self.addon.current_version.update(nomination=self.days_ago(29))
        AutoApprovalSummary.objects.create(
            version=self.addon.current_version, verdict=amo.AUTO_APPROVED,
            weight=101)
        ReviewerScore.award_points(
            self.user, self.addon, self.addon.status,
            version=self.addon.current_version,
            post_review=True, content_review=False)
        score = ReviewerScore.objects.get(user=self.user)
        assert score.score == amo.REVIEWED_SCORES[
            amo.REVIEWED_EXTENSION_MEDIUM_RISK]
        assert score.version == self.addon.current_version

    def test_award_moderation_points(self):
        ReviewerScore.award_moderation_points(self.user, self.addon, 1)
        score = ReviewerScore.objects.all()[0]
        assert score.score == (
            amo.REVIEWED_SCORES.get(amo.REVIEWED_ADDON_REVIEW))
        assert score.note_key == amo.REVIEWED_ADDON_REVIEW
        assert not score.version

    def test_get_total(self):
        user2 = UserProfile.objects.get(email='admin@mozilla.com')
        self._give_points()
        self._give_points(status=amo.STATUS_PUBLIC)
        self._give_points(user=user2, status=amo.STATUS_NOMINATED)
        assert ReviewerScore.get_total(self.user) == (
            amo.REVIEWED_SCORES[amo.REVIEWED_ADDON_FULL] +
            amo.REVIEWED_SCORES[amo.REVIEWED_ADDON_UPDATE])
        assert ReviewerScore.get_total(user2) == (
            amo.REVIEWED_SCORES[amo.REVIEWED_ADDON_FULL])

    def test_get_recent(self):
        user2 = UserProfile.objects.get(email='admin@mozilla.com')
        self._give_points()
        time.sleep(1)  # Wait 1 sec so ordering by created is checked.
        self._give_points(status=amo.STATUS_PUBLIC)
        self._give_points(user=user2)
        scores = ReviewerScore.get_recent(self.user)
        assert len(scores) == 2
        assert scores[0].score == (
            amo.REVIEWED_SCORES[amo.REVIEWED_ADDON_UPDATE])
        assert scores[1].score == (
            amo.REVIEWED_SCORES[amo.REVIEWED_ADDON_FULL])

    def test_get_leaderboards(self):
        user2 = UserProfile.objects.get(email='persona_reviewer@mozilla.com')
        self._give_points()
        self._give_points(status=amo.STATUS_PUBLIC)
        self._give_points(user=user2, status=amo.STATUS_NOMINATED)
        leaders = ReviewerScore.get_leaderboards(self.user)
        assert leaders['user_rank'] == 1
        assert leaders['leader_near'] == []
        assert leaders['leader_top'][0]['rank'] == 1
        assert leaders['leader_top'][0]['user_id'] == self.user.id
        assert leaders['leader_top'][0]['total'] == (
            amo.REVIEWED_SCORES[amo.REVIEWED_ADDON_FULL] +
            amo.REVIEWED_SCORES[amo.REVIEWED_ADDON_UPDATE])
        assert leaders['leader_top'][1]['rank'] == 2
        assert leaders['leader_top'][1]['user_id'] == user2.id
        assert leaders['leader_top'][1]['total'] == (
            amo.REVIEWED_SCORES[amo.REVIEWED_ADDON_FULL])

        self._give_points(
            user=user2, addon=amo.tests.addon_factory(type=amo.ADDON_PERSONA))
        leaders = ReviewerScore.get_leaderboards(
            self.user, addon_type=amo.ADDON_PERSONA)
        assert len(leaders['leader_top']) == 1
        assert leaders['leader_top'][0]['user_id'] == user2.id

    def test_only_active_reviewers_in_leaderboards(self):
        user2 = UserProfile.objects.create(username='former-reviewer')
        self._give_points()
        self._give_points(status=amo.STATUS_PUBLIC)
        self._give_points(user=user2, status=amo.STATUS_NOMINATED)
        leaders = ReviewerScore.get_leaderboards(self.user)
        assert leaders['user_rank'] == 1
        assert leaders['leader_near'] == []
        assert leaders['leader_top'][0]['user_id'] == self.user.id
        assert len(leaders['leader_top']) == 1  # Only the reviewer is here.
        assert user2.id not in [l['user_id'] for l in leaders['leader_top']], (
            'Unexpected non-reviewer user found in leaderboards.')

    def test_no_admins_or_staff_in_leaderboards(self):
        user2 = UserProfile.objects.get(email='admin@mozilla.com')
        self._give_points()
        self._give_points(status=amo.STATUS_PUBLIC)
        self._give_points(user=user2, status=amo.STATUS_NOMINATED)
        leaders = ReviewerScore.get_leaderboards(self.user)
        assert leaders['user_rank'] == 1
        assert leaders['leader_near'] == []
        assert leaders['leader_top'][0]['user_id'] == self.user.id
        assert len(leaders['leader_top']) == 1  # Only the reviewer is here.
        assert user2.id not in [l['user_id'] for l in leaders['leader_top']], (
            'Unexpected admin user found in leaderboards.')

    def test_get_leaderboards_last(self):
        users = []
        for i in range(6):
            user = UserProfile.objects.create(username='user-%s' % i)
            GroupUser.objects.create(group_id=50002, user=user)
            users.append(user)
        last_user = users.pop(len(users) - 1)
        for u in users:
            self._give_points(user=u)
        # Last user gets lower points by reviewing a persona.
        addon = self.addon
        addon.type = amo.ADDON_PERSONA
        self._give_points(user=last_user, addon=addon)
        leaders = ReviewerScore.get_leaderboards(last_user)
        assert leaders['user_rank'] == 6
        assert len(leaders['leader_top']) == 3
        assert len(leaders['leader_near']) == 2

    def test_leaderboard_score_when_in_multiple_reviewer_groups(self):
        group_reviewers = Group.objects.create(
            name='Reviewers: Addons', rules='Addons:Review')
        group_content_reviewers = Group.objects.create(
            name='Reviewers: Content', rules='Addons:ContentReview')
        GroupUser.objects.create(group=group_reviewers, user=self.user)
        GroupUser.objects.create(group=group_content_reviewers, user=self.user)

        AutoApprovalSummary.objects.create(
            version=self.addon.current_version, verdict=amo.AUTO_APPROVED,
            weight=101)
        ReviewerScore.award_points(
            self.user, self.addon, self.addon.status,
            version=self.addon.current_version,
            post_review=True, content_review=False)
        assert ReviewerScore._leaderboard_list() == [(
            self.user.id, self.user.name, amo.REVIEWED_SCORES[
                amo.REVIEWED_EXTENSION_MEDIUM_RISK])]

    def test_all_users_by_score(self):
        user2 = UserProfile.objects.create(
            username='otherreviewer', email='otherreviewer@mozilla.com')
        self.grant_permission(
            user2, 'Personas:Review', name='Reviewers: Themes')
        amo.REVIEWED_LEVELS[0]['points'] = 180
        self._give_points()
        self._give_points(status=amo.STATUS_PUBLIC)
        self._give_points(user=user2, status=amo.STATUS_NOMINATED)
        users = ReviewerScore.all_users_by_score()
        assert len(users) == 2
        # First user.
        assert users[0]['total'] == 200
        assert users[0]['user_id'] == self.user.id
        assert users[0]['level'] == amo.REVIEWED_LEVELS[0]['name']
        # Second user.
        assert users[1]['total'] == 120
        assert users[1]['user_id'] == user2.id
        assert users[1]['level'] == ''

    def test_caching(self):
        self._give_points()

        with self.assertNumQueries(1):
            ReviewerScore.get_total(self.user)
        with self.assertNumQueries(0):
            ReviewerScore.get_total(self.user)

        with self.assertNumQueries(1):
            ReviewerScore.get_recent(self.user)
        with self.assertNumQueries(0):
            ReviewerScore.get_recent(self.user)

        with self.assertNumQueries(2):
            ReviewerScore.get_leaderboards(self.user)
        with self.assertNumQueries(0):
            ReviewerScore.get_leaderboards(self.user)

        with self.assertNumQueries(1):
            ReviewerScore.get_breakdown(self.user)
        with self.assertNumQueries(0):
            ReviewerScore.get_breakdown(self.user)

        # New points invalidates all caches.
        self._give_points()

        with self.assertNumQueries(1):
            ReviewerScore.get_total(self.user)
        with self.assertNumQueries(1):
            ReviewerScore.get_recent(self.user)
        with self.assertNumQueries(2):
            ReviewerScore.get_leaderboards(self.user)
        with self.assertNumQueries(1):
            ReviewerScore.get_breakdown(self.user)

class TestRereviewQueueTheme(TestCase):

    def test_manager_soft_delete_addons(self):
        """Test manager excludes soft delete add-ons."""
        # Normal RQT object.
        RereviewQueueTheme.objects.create(
            theme=addon_factory(type=amo.ADDON_PERSONA).persona, header='')

        # Deleted add-on RQT object.
        addon = addon_factory(type=amo.ADDON_PERSONA)
        RereviewQueueTheme.objects.create(theme=addon.persona, header='')
        addon.delete()

        assert RereviewQueueTheme.objects.count() == 1
        assert RereviewQueueTheme.unfiltered.count() == 2

    def test_filter_for_many_to_many(self):
        # Check https://bugzilla.mozilla.org/show_bug.cgi?id=1142035.
        addon = addon_factory(type=amo.ADDON_PERSONA)
        rqt = RereviewQueueTheme.objects.create(theme=addon.persona)
        assert addon.persona.rereviewqueuetheme_set.get() == rqt

        # Delete the addon: it shouldn't be listed anymore.
        addon.update(status=amo.STATUS_DELETED)
        assert addon.persona.rereviewqueuetheme_set.all().count() == 0

class TestAutoApprovalSummary(TestCase):
    def setUp(self):
        self.addon = addon_factory(
            average_daily_users=666, version_kw={'version': '1.0'})
        AutoApprovalSummary.objects.create(
            version=self.addon.current_version, verdict=amo.AUTO_APPROVED,
            confirmed=True)
        self.current_file_validation = FileValidation.objects.create(
            file=self.addon.current_version.all_files[0], validation=u'{}')
        self.version = version_factory(
            addon=self.addon, version='1.1', file_kw={
                'status': amo.STATUS_AWAITING_REVIEW,
                'is_webextension': True})
        self.file = self.version.all_files[0]
        self.file_validation = FileValidation.objects.create(
            file=self.version.all_files[0], validation=u'{}')
        AddonApprovalsCounter.objects.create(addon=self.addon, counter=1)

    def test_negative_weight(self):
        summary = AutoApprovalSummary.objects.create(
            version=self.version, weight=-300)
        summary = AutoApprovalSummary.objects.get(pk=summary.pk)
        assert summary.weight == -300

    def test_calculate_weight(self):
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        expected_result = {
            'abuse_reports': 0,
            'admin_code_review': 0,
            'average_daily_users': 0,
            'negative_ratings': 0,
            'reputation': 0,
            'past_rejection_history': 0,
            'uses_custom_csp': 0,
            'uses_eval_or_document_write': 0,
            'uses_implied_eval': 0,
            'uses_innerhtml': 0,
            'uses_native_messaging': 0,
            'size_of_code_changes': 0,
            'uses_remote_scripts': 0,
            'uses_unknown_minified_code': 0,
            'violates_mozilla_conditions': 0,
        }
        assert weight_info == expected_result

    def test_calculate_weight_admin_code_review(self):
        AddonReviewerFlags.objects.create(
            addon=self.addon, needs_admin_code_review=True)
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 100
        assert weight_info['admin_code_review'] == 100

    def test_calculate_weight_abuse_reports(self):
        # Extra abuse report for a different add-on, does not count.
        AbuseReport.objects.create(addon=addon_factory())

        # Extra abuse report for a different user, does not count.
        AbuseReport.objects.create(user=user_factory())

        # Extra old abuse report, does not count either.
        old_report = AbuseReport.objects.create(addon=self.addon)
        old_report.update(created=self.days_ago(43))

        # Recent abuse reports.
        AbuseReport.objects.create(addon=self.addon)
        recent_report = AbuseReport.objects.create(addon=self.addon)
        recent_report.update(created=self.days_ago(41))

        # Recent abuse report for one of the developers of the add-on.
        author = user_factory()
        AddonUser.objects.create(addon=self.addon, user=author)
        AbuseReport.objects.create(user=author)

        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 45
        assert weight_info['abuse_reports'] == 45

        # Should be capped at 100. We're already at 45, adding 4 more should
        # result in a weight of 100 instead of 105.
        for i in range(0, 4):
            AbuseReport.objects.create(addon=self.addon)
        weight_info = summary.calculate_weight()
        assert summary.weight == 100
        assert weight_info['abuse_reports'] == 100

    def test_calculate_weight_abuse_reports_use_created_from_instance(self):
        # Create an abuse report 60 days in the past. It should be ignored it
        # we were calculating from today, but use an AutoApprovalSummary
        # instance that is 20 days old, making the abuse report count.
        report = AbuseReport.objects.create(addon=self.addon)
        report.update(created=self.days_ago(60))

        summary = AutoApprovalSummary.objects.create(version=self.version)
        summary.update(created=self.days_ago(20))

        weight_info = summary.calculate_weight()
        assert summary.weight == 15
        assert weight_info['abuse_reports'] == 15

    def test_calculate_weight_negative_ratings(self):
        # Positive rating, does not count.
        Rating.objects.create(
            user=user_factory(), addon=self.addon, version=self.version,
            rating=5)

        # Negative rating, but too old, does not count.
        old_rating = Rating.objects.create(
            user=user_factory(), addon=self.addon, version=self.version,
            rating=1)
        old_rating.update(created=self.days_ago(370))

        # Negative review on a different add-on, does not count either.
        extra_addon = addon_factory()
        Rating.objects.create(
            user=user_factory(), addon=extra_addon,
            version=extra_addon.current_version, rating=1)

        # Recent negative ratings.
        ratings = [Rating(
            user=user_factory(), addon=self.addon,
            version=self.version, rating=3) for i in range(0, 49)]
        Rating.objects.bulk_create(ratings)
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 0  # Not enough negative ratings yet...
        assert weight_info['negative_ratings'] == 0

        # Create one more to get to weight == 1.
        Rating.objects.create(
            user=user_factory(), addon=self.addon, version=self.version,
            rating=2)
        weight_info = summary.calculate_weight()
        assert summary.weight == 1
        assert weight_info['negative_ratings'] == 1

        # Create 5000 more (sorry!) to make sure it's capped at 100.
        ratings = [Rating(
            user=user_factory(), addon=self.addon,
            version=self.version, rating=3) for i in range(0, 5000)]
        Rating.objects.bulk_create(ratings)

        weight_info = summary.calculate_weight()
        assert summary.weight == 100
        assert weight_info['negative_ratings'] == 100

    def test_calculate_weight_reputation(self):
        summary = AutoApprovalSummary(version=self.version)
        self.addon.update(reputation=0)
        weight_info = summary.calculate_weight()
        assert summary.weight == 0
        assert weight_info['reputation'] == 0

        self.addon.update(reputation=3)
        weight_info = summary.calculate_weight()
        assert summary.weight == -300
        assert weight_info['reputation'] == -300

        self.addon.update(reputation=1000)
        weight_info = summary.calculate_weight()
        assert summary.weight == -300
        assert weight_info['reputation'] == -300

        self.addon.update(reputation=-1000)
        weight_info = summary.calculate_weight()
        assert summary.weight == 0
        assert weight_info['reputation'] == 0

    def test_calculate_weight_average_daily_users(self):
        self.addon.update(average_daily_users=142444)
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 14
        assert weight_info['average_daily_users'] == 14

        self.addon.update(average_daily_users=1756567658)
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 100
        assert weight_info['average_daily_users'] == 100

    def test_calculate_weight_past_rejection_history(self):
        # Old rejected version, does not count.
        version_factory(
            addon=self.addon,
            file_kw={'reviewed': self.days_ago(370),
                     'status': amo.STATUS_DISABLED})

        # Version disabled by the developer, not Mozilla (original_status
        # is set to something different than STATUS_NULL), does not count.
        version_factory(
            addon=self.addon,
            file_kw={'reviewed': self.days_ago(15),
                     'status': amo.STATUS_DISABLED,
                     'original_status': amo.STATUS_PUBLIC})

        # Rejected version.
        version_factory(
            addon=self.addon,
            file_kw={'reviewed': self.days_ago(14),
                     'status': amo.STATUS_DISABLED})

        # Another rejected version, with multiple files. Only counts once.
        version_with_multiple_files = version_factory(
            addon=self.addon,
            file_kw={'reviewed': self.days_ago(13),
                     'status': amo.STATUS_DISABLED,
                     'platform': amo.PLATFORM_WIN.id})
        file_factory(
            reviewed=self.days_ago(13),
            version=version_with_multiple_files,
            status=amo.STATUS_DISABLED,
            platform=amo.PLATFORM_MAC.id)

        # Rejected version on a different add-on, does not count.
        version_factory(
            addon=addon_factory(),
            file_kw={'reviewed': self.days_ago(12),
                     'status': amo.STATUS_DISABLED})

        # Approved version, does not count.
        new_approved_version = version_factory(
            addon=self.addon,
            file_kw={'reviewed': self.days_ago(11)})
        FileValidation.objects.create(
            file=new_approved_version.all_files[0], validation=u'{}')

        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 20
        assert weight_info['past_rejection_history'] == 20

        # Should be capped at 100.
        for i in range(0, 10):
            version_factory(
                addon=self.addon,
                file_kw={'reviewed': self.days_ago(10),
                         'status': amo.STATUS_DISABLED})

        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 100
        assert weight_info['past_rejection_history'] == 100

    def test_calculate_weight_uses_eval_or_document_write(self):
        validation_data = {
            'messages': [{
                'id': ['DANGEROUS_EVAL'],
            }]
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 50
        assert weight_info['uses_eval_or_document_write'] == 50

        validation_data = {
            'messages': [{
                'id': ['NO_DOCUMENT_WRITE'],
            }]
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 50
        assert weight_info['uses_eval_or_document_write'] == 50

        # Still only 20 if both appear.
        validation_data = {
            'messages': [{
                'id': ['DANGEROUS_EVAL'],
            }, {
                'id': ['NO_DOCUMENT_WRITE'],
            }]
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 50
        assert weight_info['uses_eval_or_document_write'] == 50

    def test_calculate_weight_uses_implied_eval(self):
        validation_data = {
            'messages': [{
                'id': ['NO_IMPLIED_EVAL'],
            }]
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 5
        assert weight_info['uses_implied_eval'] == 5

    def test_calculate_weight_uses_innerhtml(self):
        validation_data = {
            'messages': [{
                'id': ['UNSAFE_VAR_ASSIGNMENT'],
            }]
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 50
        assert weight_info['uses_innerhtml'] == 50

    def test_calculate_weight_uses_innerhtml_multiple_times(self):
        validation_data = {
            'messages': [{
                'id': ['UNSAFE_VAR_ASSIGNMENT'],
            }, {
                'id': ['IGNORE_ME'],
            }, {
                'id': ['UNSAFE_VAR_ASSIGNMENT'],
            }, {
                'id': ['UNSAFE_VAR_ASSIGNMENT'],
            }]
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        # 50 base, + 10 per additional instance.
        assert summary.weight == 70
        assert weight_info['uses_innerhtml'] == 70

    def test_calculate_weight_uses_custom_csp(self):
        validation_data = {
            'messages': [{
                'id': ['MANIFEST_CSP'],
            }]
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 90
        assert weight_info['uses_custom_csp'] == 90

    def test_calculate_weight_uses_native_messaging(self):
        WebextPermission.objects.create(
            file=self.file, permissions=['nativeMessaging'])

        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 100
        assert weight_info['uses_native_messaging'] == 100

    def test_calculate_weight_uses_remote_scripts(self):
        validation_data = {
            'messages': [{
                'id': ['REMOTE_SCRIPT'],
            }]
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 100
        assert weight_info['uses_remote_scripts'] == 100

    def test_calculate_weight_violates_mozilla_conditions_of_use(self):
        validation_data = {
            'messages': [{
                'id': ['MOZILLA_COND_OF_USE'],
            }]
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 20
        assert weight_info['violates_mozilla_conditions'] == 20

    def test_calculate_weight_uses_unknown_minified_code_nothing(self):
        validation_data = {
            'metadata': {
                'unknownMinifiedFiles': []  # Empty list: no weight.
            }
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 0
        assert weight_info['uses_unknown_minified_code'] == 0

        validation_data = {
            'metadata': {
                # Missing property: no weight.
            }
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 0
        assert weight_info['uses_unknown_minified_code'] == 0

        validation_data = {
            # Missing metadata: no weight.
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 0
        assert weight_info['uses_unknown_minified_code'] == 0

    def test_calculate_weight_uses_unknown_minified_code(self):
        validation_data = {
            'metadata': {
                'unknownMinifiedFiles': ['something']
            }
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 100
        assert weight_info['uses_unknown_minified_code'] == 100

    def test_calculate_weight_uses_unknown_minified_code_multiple_times(self):
        validation_data = {
            'metadata': {
                'unknownMinifiedFiles': ['something', 'foobar', 'another']
            }
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        # 100 base, + 20 per additional instance.
        assert summary.weight == 120
        assert weight_info['uses_unknown_minified_code'] == 120

    def test_calculate_size_of_code_changes_no_current_validation(self):
        # Delete the validation for the previously confirmed version and reload
        # the version we're testing (otherwise the file validation has already
        # been loaded and is still attached to the instance...)
        self.current_file_validation.delete()
        self.version = Version.objects.get(pk=self.version.pk)
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 500
        assert weight_info['no_validation_result'] == 500

    def test_calculate_size_of_code_changes_no_new_validation(self):
        # Delete the validation for the new version and reload that version.
        # (otherwise the file validation has already been loaded and is still
        # attached to the instance...)
        self.file_validation.delete()
        self.version = Version.objects.get(pk=self.version.pk)
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 500
        assert weight_info['no_validation_result'] == 500

    def test_calculate_size_of_code_changes_no_reported_size(self):
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.calculate_size_of_code_changes() == 0
        assert summary.weight == 0
        assert weight_info['size_of_code_changes'] == 0

    def test_calculate_size_of_code_changes_no_previous_version_size(self):
        validation_data = {
            'metadata': {
                'totalScannedFileSize': 15000,
            }
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        assert summary.calculate_size_of_code_changes() == 15000
        weight_info = summary.calculate_weight()
        assert summary.weight == 3
        assert weight_info['size_of_code_changes'] == 3

    def test_calculate_size_of_code_changes(self):
        old_validation_data = {
            'metadata': {
                'totalScannedFileSize': 5000,
            }
        }
        self.current_file_validation.update(
            validation=json.dumps(old_validation_data))
        new_validation_data = {
            'metadata': {
                'totalScannedFileSize': 15000,
            }
        }
        self.file_validation.update(
            validation=json.dumps(new_validation_data))
        summary = AutoApprovalSummary(version=self.version)
        assert summary.calculate_size_of_code_changes() == 10000
        weight_info = summary.calculate_weight()
        assert summary.weight == 2
        assert weight_info['size_of_code_changes'] == 2

    def test_calculate_size_of_code_change_use_previously_confirmed(self):
        old_validation_data = {
            'metadata': {
                'totalScannedFileSize': 5000,
            }
        }
        self.current_file_validation.update(
            validation=json.dumps(old_validation_data))
        new_validation_data = {
            'metadata': {
                'totalScannedFileSize': 15000,
            }
        }
        self.file_validation.update(
            validation=json.dumps(new_validation_data))

        # Add a new current_version, unconfirmed. This version will be ignored
        # for the comparison as all we care about is the previous confirmed
        # version.
        self.addon.current_version.update(created=self.days_ago(2))
        new_version = version_factory(addon=self.addon)
        self.addon.reload()
        assert self.addon.current_version == new_version
        AutoApprovalSummary.objects.create(
            version=new_version, verdict=amo.AUTO_APPROVED)
        new_validation_data = {
            'metadata': {
                'totalScannedFileSize': 14999,
            }
        }
        FileValidation.objects.create(
            file=new_version.all_files[0],
            validation=json.dumps(new_validation_data))

        summary = AutoApprovalSummary(version=self.version)
        # Size of code changes should be 10000 and not 1, proving that it
        # compared with the old, confirmed version.
        assert summary.calculate_size_of_code_changes() == 10000
        weight_info = summary.calculate_weight()
        assert summary.weight == 2
        assert weight_info['size_of_code_changes'] == 2

    def test_calculate_size_of_code_changes_no_negative(self):
        old_validation_data = {
            'metadata': {
                'totalScannedFileSize': 20000,
            }
        }
        self.current_file_validation.update(
            validation=json.dumps(old_validation_data))
        new_validation_data = {
            'metadata': {
                'totalScannedFileSize': 5000,
            }
        }
        self.file_validation.update(
            validation=json.dumps(new_validation_data))
        summary = AutoApprovalSummary(version=self.version)
        assert summary.calculate_size_of_code_changes() == 15000
        weight_info = summary.calculate_weight()
        assert summary.weight == 3
        assert weight_info['size_of_code_changes'] == 3

    def test_calculate_size_of_code_changes_max(self):
        old_validation_data = {
            'metadata': {
                'totalScannedFileSize': 50000000,
            }
        }
        self.current_file_validation.update(
            validation=json.dumps(old_validation_data))
        new_validation_data = {
            'metadata': {
                'totalScannedFileSize': 0,
            }
        }
        self.file_validation.update(
            validation=json.dumps(new_validation_data))
        summary = AutoApprovalSummary(version=self.version)
        assert summary.calculate_size_of_code_changes() == 50000000
        weight_info = summary.calculate_weight()
        assert summary.weight == 100
        assert weight_info['size_of_code_changes'] == 100

    def test_calculate_weight_sum(self):
        validation_data = {
            'messages': [
                {'id': ['MANIFEST_CSP']},
                {'id': ['UNSAFE_VAR_ASSIGNMENT']},
                {'id': ['NO_IMPLIED_EVAL']},
                {'id': ['DANGEROUS_EVAL']},
                {'id': ['UNSAFE_VAR_ASSIGNMENT']},  # Another one.
                {'id': ['NOTHING_TO_SEE_HERE_MOVE_ON']},
            ]
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        summary = AutoApprovalSummary(version=self.version)
        weight_info = summary.calculate_weight()
        assert summary.weight == 205
        expected_result = {
            'abuse_reports': 0,
            'admin_code_review': 0,
            'average_daily_users': 0,
            'negative_ratings': 0,
            'reputation': 0,
            'past_rejection_history': 0,
            'uses_custom_csp': 90,
            'uses_eval_or_document_write': 50,
            'uses_implied_eval': 5,
            'uses_innerhtml': 60,  # There is one extra.
            'uses_native_messaging': 0,
            'size_of_code_changes': 0,
            'uses_remote_scripts': 0,
            'uses_unknown_minified_code': 0,
            'violates_mozilla_conditions': 0,
        }
        assert weight_info == expected_result

    def test_count_uses_custom_csp(self):
        assert AutoApprovalSummary.count_uses_custom_csp(self.version) == 0

        validation_data = {
            'messages': [{
                'id': ['MANIFEST_CSP'],
            }]
        }
        self.file_validation.update(validation=json.dumps(validation_data))
        assert AutoApprovalSummary.count_uses_custom_csp(self.version) == 1

    def test_count_uses_custom_csp_file_validation_missing(self):
        self.file_validation.delete()
        del self.version.all_files
        with self.assertRaises(AutoApprovalNoValidationResultError):
            AutoApprovalSummary.count_uses_custom_csp(self.version)

        # Also happens if only one file is missing validation info.
        self.file_validation = FileValidation.objects.create(
            file=self.version.all_files[0], validation=u'{}')
        del self.version.all_files
        file_factory(version=self.version, status=amo.STATUS_AWAITING_REVIEW)
        with self.assertRaises(AutoApprovalNoValidationResultError):
            AutoApprovalSummary.count_uses_custom_csp(self.version)

    def test_check_uses_native_messaging(self):
        assert (
            AutoApprovalSummary.check_uses_native_messaging(self.version) == 0)

        webext_permissions = WebextPermission.objects.create(
            file=self.file, permissions=['foobar'])
        del self.file.webext_permissions_list
        assert (
            AutoApprovalSummary.check_uses_native_messaging(self.version) == 0)

        webext_permissions.update(permissions=['nativeMessaging', 'foobar'])
        del self.file.webext_permissions_list
        assert (
            AutoApprovalSummary.check_uses_native_messaging(self.version) == 1)

    def test_check_has_auto_approval_disabled(self):
        assert AutoApprovalSummary.check_has_auto_approval_disabled(
            self.version) == 0

        flags = AddonReviewerFlags.objects.create(addon=self.addon)
        assert AutoApprovalSummary.check_has_auto_approval_disabled(
            self.version) == 0

        flags.update(auto_approval_disabled=True)
        assert AutoApprovalSummary.check_has_auto_approval_disabled(
            self.version) is True

    def test_check_is_locked(self):
        assert AutoApprovalSummary.check_is_locked(self.version) is False

        set_reviewing_cache(self.version.addon.pk, settings.TASK_USER_ID)
        assert AutoApprovalSummary.check_is_locked(self.version) is False

        set_reviewing_cache(self.version.addon.pk, settings.TASK_USER_ID + 42)
        assert AutoApprovalSummary.check_is_locked(self.version) is True

    @mock.patch.object(AutoApprovalSummary, 'calculate_weight', spec=True)
    @mock.patch.object(AutoApprovalSummary, 'calculate_verdict', spec=True)
    def test_create_summary_for_version(
            self, calculate_verdict_mock, calculate_weight_mock):
        calculate_verdict_mock.return_value = {'dummy_verdict': True}
        summary, info = AutoApprovalSummary.create_summary_for_version(
            self.version,)
        assert calculate_weight_mock.call_count == 1
        assert calculate_verdict_mock.call_count == 1
        assert calculate_verdict_mock.call_args == ({
            'dry_run': False,
        },)
        assert summary.pk
        assert summary.version == self.version
        assert info == {'dummy_verdict': True}

    @mock.patch.object(AutoApprovalSummary, 'calculate_verdict', spec=True)
    def test_create_summary_no_previously_approved_versions(
            self, calculate_verdict_mock):
        AddonApprovalsCounter.objects.all().delete()
        self.version.reload()
        calculate_verdict_mock.return_value = {'dummy_verdict': True}
        summary, info = AutoApprovalSummary.create_summary_for_version(
            self.version)
        assert summary.pk
        assert info == {'dummy_verdict': True}

    def test_create_summary_already_existing(self):
        # Create a dummy summary manually, then call the method to create a
        # real one. It should have just updated the previous instance.
        summary = AutoApprovalSummary.objects.create(
            version=self.version, is_locked=True)
        assert summary.pk
        assert summary.version == self.version
        assert summary.verdict == amo.NOT_AUTO_APPROVED

        previous_summary_pk = summary.pk

        summary, info = AutoApprovalSummary.create_summary_for_version(
            self.version)

        assert summary.pk == previous_summary_pk
        assert summary.version == self.version
        assert summary.is_locked is False
        assert summary.verdict == amo.AUTO_APPROVED
        assert info == {
            'has_auto_approval_disabled': False,
            'is_locked': False,
        }

    def test_create_summary_no_files(self):
        self.file.delete()
        del self.version.all_files
        with self.assertRaises(AutoApprovalNotEnoughFilesError):
            AutoApprovalSummary.create_summary_for_version(self.version)

    def test_calculate_verdict_failure_dry_run(self):
        summary = AutoApprovalSummary.objects.create(
            version=self.version, is_locked=True)
        info = summary.calculate_verdict(dry_run=True)
        assert info == {
            'is_locked': True,
            'has_auto_approval_disabled': False,
        }
        assert summary.verdict == amo.WOULD_NOT_HAVE_BEEN_AUTO_APPROVED

    def test_calculate_verdict_failure(self):
        summary = AutoApprovalSummary.objects.create(
            version=self.version, is_locked=True)
        info = summary.calculate_verdict()
        assert info == {
            'is_locked': True,
            'has_auto_approval_disabled': False,
        }
        assert summary.verdict == amo.NOT_AUTO_APPROVED

    def test_calculate_verdict_success(self):
        summary = AutoApprovalSummary.objects.create(version=self.version)
        info = summary.calculate_verdict()
        assert info == {
            'is_locked': False,
            'has_auto_approval_disabled': False,
        }
        assert summary.verdict == amo.AUTO_APPROVED

    def test_calculate_verdict_success_dry_run(self):
        summary = AutoApprovalSummary.objects.create(version=self.version)
        info = summary.calculate_verdict(dry_run=True)
        assert info == {
            'is_locked': False,
            'has_auto_approval_disabled': False,
        }
        assert summary.verdict == amo.WOULD_HAVE_BEEN_AUTO_APPROVED

    def test_calculate_verdict_has_auto_approval_disabled(self):
        summary = AutoApprovalSummary.objects.create(
            version=self.version, has_auto_approval_disabled=True)
        info = summary.calculate_verdict()
        assert info == {
            'is_locked': False,
            'has_auto_approval_disabled': True,
        }
        assert summary.verdict == amo.NOT_AUTO_APPROVED

    def test_verdict_info_prettifier(self):
        verdict_info = {
            'is_locked': True,
            'has_auto_approval_disabled': True,
        }
        result = list(
            AutoApprovalSummary.verdict_info_prettifier(verdict_info))
        assert result == [
            u'Has auto-approval disabled flag set.',
            u'Is locked by a reviewer.',
        ]

        result = list(AutoApprovalSummary.verdict_info_prettifier({}))
        assert result == []


import matplotlib.pyplot as plt

def plot_metrics(history, show=False):
    plt.subplot(2, 1, 1)
    plt.title('Loss')
    plt.plot(history['loss'], '-o', label='train')
    plt.plot(history['val_loss'], '-o', label='val')
    plt.xlabel('epoch')
    plt.legend(loc='upper right')
    plt.subplot(2,1,2)
    plt.title('Accuracy')
    plt.plot(history['acc'], '-o', label='train')
    plt.plot(history['val_acc'], '-o', label='val')
    plt.xlabel('epoch')
    plt.legend(loc='upper left')
    plt.savefig('metrics.png')
    plt.gcf().set_size_inches(15, 12)
    if show:
        plt.show()
    else:
        plt.close()


""" Middleware classes for the main app"""
from django.conf import settings
from django.utils.deprecation import MiddlewareMixin

class CachelessAPIMiddleware(MiddlewareMixin):
    """Add Cache-Control header to API responses"""

    def process_response(self, request, response):
        """Add a Cache-Control header to an API response"""
        if request.path.startswith(settings.CACHEABLE_ENDPOINTS):
            response["Cache-Control"] = settings.CACHEABLE_ENDPOINTS_CACHE_VALUE
        elif request.path.startswith("/api/"):
            response["Cache-Control"] = "private, no-store"
        return response


'''
This file impements cbc encryption using function from the ecb.py file.
It breaks up a string into blocks and either encrypts or decrypts it.
Author Cole Schumacher
'''

from Crypto.Cipher import XOR
import ecb
import sys
import binascii

iv = binascii.unhexlify('000102030405060708090a0b0c0d0e0f')

#encrypts an individual block by xoring it with the last blocks cipher and then encrypting
def encryptblock(lastcipher, plaintext):
    toXOR = XOR.new(lastcipher)
    if len(plaintext) != 16:
        plaintext = ecb.pad(plaintext)
    block = toXOR.encrypt(plaintext)
    return ecb.encrypt(ecb.key, block, False)

#decrypts the given blok and then xors it with the revious block
def decryptblock(lastcipher, ciphertext, unpad):
    block = ecb.decrypt(ecb.key, ciphertext, unpad)

    toXOR = XOR.new(lastcipher)
    block = toXOR.decrypt(block)
    return block

#gets the range of data in the current block
def getrange(first, last, length):
    return last, last + 16

#encryts the given data block by block
def encryptbinary(s, initializationVector):
    length = len(s)
    currentstart = 0
    currentend = 16

    if currentend > length:
        currentend = length

    ciphertext = binascii.unhexlify('')
    lastcipher = initializationVector

    #itterates of the raw data and blocks them
    while currentstart < length:

        plaintext = s[currentstart:currentend]
        currentcipher = encryptblock(lastcipher, plaintext)

        lastcipher = currentcipher

        #moves to the next block
        ciphertext += currentcipher
        currentstart, currentend = getrange(currentstart, currentend, length)

    if length % ecb.BLOCK_SIZE == 0:
        padding = ecb.pad(binascii.unhexlify(''))
        ciphertext += encryptblock(lastcipher, padding)

    return ciphertext

#decrypts the data given and unpads it if told
def decryptbinary(s, initializationVector, unpad):
    length = len(s)
    currentstart = 0
    currentend = 16

    if currentend > length:
        currentend = length

    plaintext = binascii.unhexlify('')
    lastcipher = initializationVector

    #iterates over the cipher and blocks it
    while currentstart < length:

        ciphertext = s[currentstart:currentend]

        #decrypt the current block, no reason to have it decrypted in ecb because
        #it would need to be added back in for all nonerminal blocks
        plaintext += decryptblock(lastcipher, ciphertext, False)

        #move to the next block
        lastcipher = ciphertext
        currentstart, currentend = getrange(currentstart, currentend, length)

    #removes padding from the string as a whole if requested
    if unpad:
        plaintext = ecb.unpad(plaintext)

    return plaintext

#the main function that executes all functions of the program
if __name__ == "__main__":
    myargs = ecb.getopts(sys.argv)

    if '-e' in myargs:
        plaintext = binascii.unhexlify(myargs['-e'])
        ciphertext = encryptbinary(plaintext, iv)
        print('Ciphertext: ' + binascii.hexlify(ciphertext))

    elif '-d' in myargs:
        ciphertext = binascii.unhexlify(myargs['-d'])
        plaintext = decryptbinary(ciphertext, iv, True)
        print('Plaintext: ' + binascii.hexlify(plaintext))

    elif '-s' in myargs:
        plaintext = binascii.a2b_qp(myargs['-s'])
        ciphertext = encryptbinary(plaintext, iv)
        print('Ciphertext: ' + binascii.hexlify(ciphertext))

    elif '-u' in myargs:
        ciphertext = binascii.unhexlify(myargs['-u'])
        plaintext = decryptbinary(ciphertext, iv, True)
        print('Plaintext: ' + binascii.b2a_qp(plaintext))


#!/usr/bin/env python3

from configparser import SafeConfigParser
import socket
import ssl

parser = SafeConfigParser()
parser.read('config.ini')
server_ip = parser.get('server', 'ip')
server_port = int(parser.get('server', 'port'))

certfile = parser.get('ssl','ssl_certfile')
print(certfile)

'''
ssl_sock = ssl.wrap_socket(sock,
                           ca_certs=certfile,
                           cert_reqs=ssl.CERT_REQUIRED)

ssl_sock.connect((server_ip, server_port))
'''
context = ssl.create_default_context()
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
context.verify_mode = ssl.CERT_REQUIRED

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ssl_sock = context.wrap_socket(sock)
ssl_sock.connect((server_ip, server_port))