# -*- coding: utf-8 -*-
"""
Created on Sat Apr 23 14:11:34 2016

@author: Bernardo
"""

'''Train a simple deep CNN on the CIFAR10 small images dataset.
GPU run command:
    THEANO_FLAGS=mode=FAST_RUN,device=gpu,floatX=float32 python cifar10_cnn.py
It gets down to 0.65 test logloss in 25 epochs, and down to 0.55 after 50 epochs.
(it's still underfitting at that point, though).
Note: the data was pickled with Python 2, and some encoding issues might prevent you
from loading it in Python 3. You might have to load it in Python 2,
save it in a different format, load it in Python 3 and repickle it.
'''

from __future__ import print_function
from keras.datasets import cifar10
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers.core import Dense, Dropout, Activation, Flatten
from keras.layers.convolutional import Convolution2D, MaxPooling2D
from keras.optimizers import SGD
from keras.utils import np_utils

batch_size = 32
nb_classes = 10
nb_epoch = 25 #200
data_augmentation = True

# input image dimensions
img_rows, img_cols = 32, 32
# the CIFAR10 images are RGB
img_channels = 3

# the data, shuffled and split between train and test sets
(X_train, y_train), (X_test, y_test) = cifar10.load_data()
print('X_train shape:', X_train.shape)
print(X_train.shape[0], 'train samples')
print(X_test.shape[0], 'test samples')

# convert class vectors to binary class matrices
Y_train = np_utils.to_categorical(y_train, nb_classes)
Y_test = np_utils.to_categorical(y_test, nb_classes)

#create the network
model = Sequential()

model.add(Convolution2D(32, 3, 3, border_mode='same',
                        input_shape=(img_channels, img_rows, img_cols)))
model.add(Activation('relu'))
model.add(Convolution2D(32, 3, 3))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Convolution2D(64, 3, 3, border_mode='same'))
model.add(Activation('relu'))
model.add(Convolution2D(64, 3, 3))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(nb_classes))
model.add(Activation('softmax'))

# let's train the model using SGD + momentum (how original).
sgd = SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True)
model.compile(loss='categorical_crossentropy',
              optimizer='sgd')

X_train = X_train.astype('float32')
X_test = X_test.astype('float32')
X_train /= 255
X_test /= 255

if not data_augmentation:
    print('Not using data augmentation.')
    model.fit(X_train, Y_train,
              batch_size=batch_size,
              nb_epoch=nb_epoch,
              validation_data=(X_test, Y_test),
              shuffle=True)
else:
    print('Using real-time data augmentation.')

    # this will do preprocessing and realtime data augmentation
    datagen = ImageDataGenerator(
        featurewise_center=False,  # set input mean to 0 over the dataset
        samplewise_center=False,  # set each sample mean to 0
        featurewise_std_normalization=False,  # divide inputs by std of the dataset
        samplewise_std_normalization=False,  # divide each input by its std
        zca_whitening=False,  # apply ZCA whitening
        rotation_range=0,  # randomly rotate images in the range (degrees, 0 to 180)
        width_shift_range=0.1,  # randomly shift images horizontally (fraction of total width)
        height_shift_range=0.1,  # randomly shift images vertically (fraction of total height)
        horizontal_flip=True,  # randomly flip images
        vertical_flip=False)  # randomly flip images

    # compute quantities required for featurewise normalization
    # (std, mean, and principal components if ZCA whitening is applied)
    datagen.fit(X_train)

    # fit the model on the batches generated by datagen.flow()
    model.fit_generator(datagen.flow(X_train, Y_train,
                        batch_size=batch_size),
                        samples_per_epoch=X_train.shape[0],
                        nb_epoch=nb_epoch,
                        show_accuracy=True,
                        validation_data=(X_test, Y_test))

score = model.evaluate(X_test, Y_test,
                       show_accuracy=True, verbose=0)

print('Test score:', score[0])
print('Test accuracy:', score[1])
#ver sintaxe correta para gravar arquivo png do modelo
#model.save_weights('MLP_9_2_1.h5')
#model.load_weights('MPL_9_2_1.h5')
T = 50

def evaluate(X, l=2, p=0.5):
#    N = X_train.shape[0]
    probs = []
    for i in range(T):
        probs.append(model.predict(np.array(X)))
    pred_mean = np.mean(probs, axis=0)
    pred_std = np.std(probs, axis=0)
    #tau = l**2 * (1 - p) / (2 * N * rg.l2)
    #pred_variance += tau**-1
    return pred_mean, pred_std

def plot_class(cl):
    indexes = [i for i, c in enumerate(y_test_aux) if c != cl]
    m, s = evaluate(np.delete(X_test_aux, indexes, axis=0))
#    aux = np.delete(X_test_aux, indexes, axis=0)
#    aux = aux.shape[0]
#    m = m/aux
#    s = s/aux
    plt.figure('All_Outputs_SD' + str(cl))
    plt.title('All Outputs SD #' + str(cl))
    plt.hist(s)
    plt.xlim(0, 0.4)
    plt.xlabel('Standard Deviation')
    plt.figure('All_Outputs_Mean' + str(cl))
    plt.title('All Outputs Mean #' + str(cl))
    plt.hist(m)
    plt.xlim(0, 1)
    plt.xlabel('Mean')

for i in range(10):
    plot_class(i)


import torch

import time

from lib.utils import AvgrageMeter, accuracy
from lib.config import cfg

def validate(model, test_loader, criterion, epoch, logger, attention_logger, writer):
    if cfg.ddp.local_rank == 0:
        print('evaluation ...')
    model.eval()

    top1 = AvgrageMeter()
    top5 = AvgrageMeter()
    losses = AvgrageMeter()

    step = 0
    with torch.no_grad():
        sta_time = time.time()
        for i, (data, target) in enumerate(test_loader):
            if cfg.test and cfg.disp_attention and cfg.ddp.local_rank == 0:
                attention_logger.info(f"test iter {i}")

            N = data.size(0)
            if cfg.cuda:
                data, target = data.cuda(), target.cuda()
            output = model(data)

            loss = criterion(output, target)
            prec1, prec5 = accuracy(output, target, topk=(1, 5))

            losses.update(loss.item(), N)
            top1.update(prec1.item(), N)
            top5.update(prec5.item(), N)

            step += 1
            if step % cfg.train.disp == 0 and cfg.ddp.local_rank == 0:
                logger.info("Test: Epoch {}/{}  Time: {:.3f} Loss {losses.avg:.3f} "
                            "Prec@(1,5) ({top1.avg:.1%}, {top5.avg:.1%})".format(
                    epoch, cfg.train.epoch, (time.time() - sta_time) / cfg.train.disp,
                    losses=losses, top1=top1, top5=top5))

                writer.add_scalar('Loss/vaild', losses.avg, epoch * len(test_loader) + i)
                writer.add_scalar('Accuracy/vaild', top1.avg, epoch * len(test_loader) + i)

    return top1.avg


"""
This module contains an implementation of a Vantage Point-tree (VP-tree).
Original implementation from https://github.com/RickardSjogren/vptree/blob/master/vptree.py
"""
import math
import statistics as stats

class VPTree:

    """ VP-Tree data structure for efficient nearest neighbor search.

    The VP-tree is a data structure for efficient nearest neighbor
    searching and finds the nearest neighbor in O(log n)
    complexity given a tree constructed of n data points. Construction
    complexity is O(n log n).

    Parameters
    ----------
    points : Iterable
        Construction points.
    dist_fn : Callable
        Function taking to point instances as arguments and returning
        the distance between them.
    root: None
        Do not use. Internal to node creation.
    """
    __slots__ = ["left", "right", "left_min", "left_max", "right_min", "right_max", "dist_fn", "vp"]
    def __init__(self, points, dist_fn, root=None):
        self.left = None
        self.right = None
        self.left_min = math.inf
        self.left_max = 0
        self.right_min = math.inf
        self.right_max = 0
        self.dist_fn = dist_fn

        if not len(points):
            raise ValueError('Points can not be empty.')

        # Vantage point is point furthest from parent vp.
        vp_i = 0
        self.vp = points[0]
        points = points[1:]

        if len(points) == 0:
            return

        # Choose division boundary at median of distances.
        distances = [self.dist_fn(self.vp, p) for p in points]
        median = stats.median(distances)

        left_points = []
        right_points = []
        for point, distance in zip(points, distances):
            if distance >= median:
                self.right_min = min(distance, self.right_min)
                if distance > self.right_max:
                    self.right_max = distance
                    right_points.insert(0, point) # put furthest first
                else:
                    right_points.append(point)
            else:
                self.left_min = min(distance, self.left_min)
                if distance > self.left_max:
                    self.left_max = distance
                    left_points.insert(0, point) # put furthest first
                else:
                    left_points.append(point)

        # Modified from the original implementation, removing recursive node creation as large trees cannot be constructed otherwise.
        if root is not None:
            if len(left_points) > 0:
                root.append((self, 0, left_points))

            if len(right_points) > 0:
                root.append((self, 1, right_points))
        elif root is None:
            accumulator = []

            if len(left_points) > 0:
                self.left = VPTree(points=left_points, dist_fn=self.dist_fn, root=accumulator)

            if len(right_points) > 0:
                self.right = VPTree(points=right_points, dist_fn=self.dist_fn, root=accumulator)

            while len(accumulator) > 0:
                node, direction, points = accumulator.pop()
                if direction == 0:
                    node.left = VPTree(points=points, dist_fn=self.dist_fn, root=accumulator)
                elif direction == 1:
                    node.right = VPTree(points=points, dist_fn=self.dist_fn, root=accumulator)
        else:
            raise TypeError("Invalid type applied to the root node. Pass \'None\' for the root node")

    def _is_leaf(self):
        return (self.left is None) and (self.right is None)

    def get_nearest_neighbor(self, query):
        """ Get single nearest neighbor.

        Parameters
        ----------
        query : Any
            Query point.

        Returns
        -------
        Any
            Single nearest neighbor.
        """
        return self.get_n_nearest_neighbors(query, n_neighbors=1)[0]

    def get_n_nearest_neighbors(self, query, n_neighbors):
        """ Get `n_neighbors` nearest neigbors to `query`

        Parameters
        ----------
        query : Any
            Query point.
        n_neighbors : int
            Number of neighbors to fetch.

        Returns
        -------
        list
            List of `n_neighbors` nearest neighbors.
        """
        if not isinstance(n_neighbors, int) or n_neighbors < 1:
            raise ValueError('n_neighbors must be strictly positive integer')
        neighbors = _AutoSortingList(max_size=n_neighbors)
        nodes_to_visit = [(self, 0)]

        furthest_d = math.inf

        while len(nodes_to_visit) > 0:
            node, d0 = nodes_to_visit.pop(0)
            if node is None or d0 > furthest_d:
                continue

            d = self.dist_fn(query, node.vp)
            if d < furthest_d:
                neighbors.append((d, node.vp))
                furthest_d, _ = neighbors[-1]

            if node._is_leaf():
                continue

            if node.left_min <= d <= node.left_max:
                nodes_to_visit.insert(0, (node.left, 0))
            elif node.left_min - furthest_d <= d <= node.left_max + furthest_d:
                nodes_to_visit.append((node.left,
                                       node.left_min - d if d < node.left_min
                                       else d - node.left_max))

            if node.right_min <= d <= node.right_max:
                nodes_to_visit.insert(0, (node.right, 0))
            elif node.right_min - furthest_d <= d <= node.right_max + furthest_d:
                nodes_to_visit.append((node.right,
                                       node.right_min - d if d < node.right_min
                                       else d - node.right_max))

        return list(neighbors)

    def get_all_in_range(self, query, max_distance):
        """ Find all neighbours within `max_distance`.

        Parameters
        ----------
        query : Any
            Query point.
        max_distance : float
            Threshold distance for query.

        Returns
        -------
        neighbors : list
            List of points within `max_distance`.
        """
        neighbors = list()
        nodes_to_visit = [(self, 0)]

        while len(nodes_to_visit) > 0:
            node, d0 = nodes_to_visit.pop(0)
            if node is None or d0 > max_distance:
                continue

            d = self.dist_fn(query, node.vp)
            if d < max_distance:
                neighbors.append((d, node.vp))

            if node._is_leaf():
                continue

            if node.left_min <= d <= node.left_max:
                nodes_to_visit.insert(0, (node.left, 0))
            elif node.left_min - max_distance <= d <= node.left_max + max_distance:
                nodes_to_visit.append((node.left,
                                       node.left_min - d if d < node.left_min
                                       else d - node.left_max))

            if node.right_min <= d <= node.right_max:
                nodes_to_visit.insert(0, (node.right, 0))
            elif node.right_min - max_distance <= d <= node.right_max + max_distance:
                nodes_to_visit.append((node.right,
                                       node.right_min - d if d < node.right_min
                                       else d - node.right_max))

        return neighbors

class _AutoSortingList(list):

    """ Simple auto-sorting list.

    Inefficient for large sizes since the queue is sorted at
    each push.

    Parameters
    ---------
    size : int, optional
        Max queue size.
    """

    __slots__ = ["max_size"]
    def __init__(self, max_size=None, *args):
        super(_AutoSortingList, self).__init__(*args)
        self.max_size = max_size

    def append(self, item):
        """ Append `item` and sort.

        Parameters
        ----------
        item : Any
            Input item.
        """
        super(_AutoSortingList, self).append(item)
        self.sort(key=lambda item: item[0])
        if self.max_size is not None and len(self) > self.max_size:
            self.pop()

"""
Copyright 2017 Rickard Sjögren
Copyright 2019 Luke Barr

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
"""


from glob import glob
from random import randint

import cv2 as cv
import numpy as np

def get_real_and_img_pts(
    pattern_size: tuple,
    img_name_pattern: str,
    real_square_size: float = 1.0,
    show_progress: bool = False
) -> tuple:
    """Get 3D points in real world space, 2D points in image place and the shape of the used images.
    Adaptation from: https://docs.opencv.org/master/dc/dbb/tutorial_py_calibration.html

    Args:
    - `pattern_size:tuple`: Chess pattern grid to be considerer as `(width, height)`
    - `img_name_pattern:str`: Image name pattern that contains a chessboard
    - `real_square_size:float`: Real measure from the squares into the chessboard
    - `show_progress:bool`: Control the image exhibition as they been process

    Returns:
    - (`real_pts`, `img_pts`, `img_gray.shape`, `img_names`)
    """
    # termination criteria
    criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, .0001)

    # prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
    real_pt = np.zeros((pattern_size[0] * pattern_size[1], 3), np.float32)
    real_pt[:, :2] = np.mgrid[:pattern_size[1], :pattern_size[0]].T.reshape(-1, 2)
    # real square measure
    real_pt *= real_square_size

    # 3D points and 2D points
    real_pts, img_pts = [], []
    img_gray = np.zeros((1, 1))

    img_names = glob(img_name_pattern)
    for img_name in img_names:
        img = cv.imread(img_name)
        img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)

        # Find the chess board corners
        status_ok, corners = cv.findChessboardCorners(
            img_gray, pattern_size,
            flags=cv.CALIB_CB_NORMALIZE_IMAGE + cv.CALIB_CB_ADAPTIVE_THRESH
        )

        if not status_ok:
            continue

        real_pts.append(real_pt)
        img_pts.append(corners)

        if show_progress:
            corners2 = cv.cornerSubPix(img_gray, corners, (11,11), (-1,-1), criteria)
            cv.drawChessboardCorners(img, pattern_size, corners2, True)
            cv.imshow('img', img)
            cv.waitKey(500)

    cv.destroyAllWindows()

    return real_pts, img_pts, img_gray.shape, img_names

def get_camera_matrices(real_pts: list, img_pts: list, img_shape: tuple) -> dict:
    """Get general camera matrices: intrinsic params and distortion coefficients; and
    specific camera matrices: rotation, translation and projection.

    Source: https://docs.opencv.org/master/dc/dbb/tutorial_py_calibration.html

    Args:
    - `real_pts:list`: 3D points in real world space
    - `img_pts:list`: 2D points in image place
    - `img_shape:tuple`: Used images' shape

    Returns:
    - Matrices in dict
    """
    _, intrinsics, distortion, rot_vecs, trans_vecs = cv.calibrateCamera(
        real_pts, img_pts, img_shape[::-1], None, None
    )

    pos = randint(0, len(rot_vecs) - 1)
    rotation, _ = cv.Rodrigues(rot_vecs[pos])
    translation = trans_vecs[pos]

    extrinsic = cv.hconcat((rotation, translation))
    projection = np.matmul(intrinsics, extrinsic)

    return {
        'intrinsics': intrinsics,
        'distortion': distortion,
        'specific': {
            'sorted': pos,
            'rotation': rotation,
            'translation': translation,
            'extrinsic': extrinsic,
            'projection': projection
        }
    }


from colonialismdb.education.models import MainDataEntry, EducationExpenditureType, SchoolType
from colonialismdb.common.models import SpatialAreaUnit, Currency
from colonialismdb.sources.models import Table
from colonialismdb.common.admin import BaseSubmitAdmin, BaseMergeableCategoryAdmin, BaseMainDataEntryAdmin
from django.contrib import admin

class MainDataEntryAdmin(BaseMainDataEntryAdmin) :
  fieldsets = [
      (None,
        {'fields' : ['active', 'submitted_by', 'datetime_created', ]}),

      ('Location Information',
        {'fields' : ['location', 'original_location_name', 'alternate_location_name', 'spatial_area', 'spatial_area_unit']}),

      ('Date Range',
        {'fields' : ['begin_date', 'end_date', 'circa']}),

      ('Currency',
        {'fields' : ['currency', 'currency_exchange_rate']}),

      ('Education Expenditure',
        {'fields' : ['education_expenditure', 'education_expenditure_type', 'is_total_expenditure']}),

      ('Schools',
        {'fields' : ['num_schools', 'is_total_schools', 'school_type', 'pub_priv', 'rel_sec']}),

      ('Students',
        {'fields' : ['num_students', 'student_gender', 'is_total_students']}),

      ('Teachers',
        {'fields' : ['num_teachers', 'teacher_gender', 'is_total_teachers']}),

      ('Source Information',
        {'fields' : ['source', 'page_num', 'primary_source_obj', 'primary_source_text']}),

      ('Other Information',
        {'fields' : ['remarks', ]}),
  ]

  #radio_fields = { 'value_precision' : admin.HORIZONTAL, 'individ_fam' : admin.HORIZONTAL, 'population_gender' : admin.HORIZONTAL, 'value_unit' : admin.HORIZONTAL, }

  activate_perm = 'education.activate_maindataentry'

class EducationExpenditureTypeAdmin(BaseMergeableCategoryAdmin):
    activate_perm = 'education.activate_educexpendtype'
    merge_perm = 'education.merge_educexpendtype'

class SchoolTypeAdmin(BaseMergeableCategoryAdmin):
    activate_perm = 'education.activate_schooltype'
    merge_perm = 'education.merge_schooltype'

admin.site.register(MainDataEntry, MainDataEntryAdmin)
admin.site.register(EducationExpenditureType, EducationExpenditureTypeAdmin)
admin.site.register(SchoolType, SchoolTypeAdmin)


# -*- coding: utf-8 -*-
import json
import re
import sys

import redis
import scrapy

from iqiyiCrawler.items import IqiyiTVItem

reload(sys)
sys.setdefaultencoding('utf-8')
conn = redis.Redis(host="10.4.255.129", port=6379, db=0)

class IqiyiSpider(scrapy.Spider):
    name = 'iqiyiTV'
    cache_urls = "http://cache.video.iqiyi.com/jp/vi/{tvid}/{vid}/"
    # http://mixer.video.iqiyi.com/jp/mixin/videos/1033851800
    urls = "http://mixer.video.iqiyi.com/jp/recommend/videos?referenceId={referenceId}&albumId={albumId}&channelId=2&cookieId=496db0c22d9a27a875d612af58b6e7c6&withRefer=false&area=bee&size=36&type=video&pru=&locale=&userId=&playPlatform=PC_QIYI&isSeriesDramaRcmd="
    url = "http://www.iqiyi.com/v_19rrco0ypo.html"
    jiming_url = "http://mixer.video.iqiyi.com/jp/mixin/videos/avlist?albumId={albumId}&size={size}&page=1"
    gender_url = "https://uaa.if.iqiyi.com/video_index/v2/get_user_profile?album_id={albumId}"
    tv_url = "http://mixer.video.iqiyi.com/jp/mixin/videos/{albumId}?select=user,credit,focus,star,cast,gender"

    def start_requests(self):
        yield scrapy.Request(url=self.url, callback=self.parse_index)
        # for url in self.start_urls:
        #     yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        data = response.meta["data"]
        result = response.text.strip("var tvInfoJs=")
        # print(result)
        jj = json.loads(result)
        # print(jj.get("albumQipuId"))
        data["keyword"] = jj.get("an")
        # print(jj.get("qiyiPlayStrategy"))
        data["tg"] = jj.get("tg")
        data["info"] = jj.get("info")
        data["d"] = jj.get("d")
        data["ma"] = jj.get("ma")
        # print(jj.get("nurl"))
        # print(jj.get("videoQipuId"))
        albumid = data.get("albumid")
        # print(self.gender_url.format(albumId=data.get("albumid")))
        yield scrapy.Request(url=self.gender_url.format(albumId=albumid), meta={"data": data},
                             callback=self.parse_gender, dont_filter=True)

    def parse_gender(self, response):
        data = response.meta["data"]
        albumid = data.get("albumid")
        tvid = data.get("tvid")
        result = json.loads(response.text)
        # print(albumid)
        gender = result.get("data").get("details")[0].get(albumid).get("gender")
        age = result.get("data").get("details")[0].get(albumid).get("age")
        data["gender"] = gender
        data["age"] = age
        yield scrapy.Request(url=self.tv_url.format(albumId=tvid), meta={"data": data}, callback=self.parse_score,
                             dont_filter=True, priority=1)

    def parse_score(self, response):
        data = response.meta["data"]
        result = json.loads(response.text.strip("var tvInfoJs="))
        score = result.get("score")
        playCount = result.get("playCount")
        data["score"] = score
        data["playCount"] = playCount
        item = IqiyiTVItem()
        item["url"] = data.get("url")
        item["name"] = data.get("keyword")
        item["directors"] = data.get("d")
        item["Actors"] = data.get("ma")
        item["period"] = data.get("time")
        item["description"] = data.get("info")
        item["pay"] = data.get("vip")
        item["score"] = score
        item["starTotal"] = playCount
        item["tags"] = data.get("tg")
        item["type"] = data.get("genres")
        item["region"] = data.get("region")
        item["positive"] = "正片"
        item["sex"] = data.get("gender")
        item["ages"] = data.get("age")
        item["subtitle"] = data.get("subtitle")
        item["numTotal"] = str(data.get("status"))
        # print(item["numTotal"])
        yield item

    def parse_item(self, response):
        # print(response.meta["data"])
        albumid = response.meta["data"].get("albumid")
        data_page = response.meta["data"].get("page")
        data = response.meta["data"]
        # print(data)
        yield scrapy.Request(
            url=self.jiming_url.format(albumId=albumid, size=str(data_page * 30)),
            meta={"data": data}, callback=self.parse_num, dont_filter=True, priority=1)
        # print(response.text)
        result = response.text.strip("var tvInfoJs=")
        jj = json.loads(result)
        for tt in jj.get("mixinVideos"):
            url = tt.get("url")
            # print(tt.get("url"))
            # print(tt.get("description"))
            # print(tt.get("tvId"))
            # print(tt.get("albumId"))
            # print(tt.get("crumbList")[1].get("title"))
            # print(tt.get("crumbList")[2].get("title"))
            if conn.sadd("Iqiyi:tv", url) == 1:
                yield scrapy.Request(url=url, callback=self.parse_index, dont_filter=True)

    def parse_index(self, response):
        sel = scrapy.Selector(response)
        result = response.text
        # print(result)
        albumid = re.findall("param\['albumid']\s=\s(.*?);", result)
        tvid = re.findall("param\['tvid']\s=\s(.*?);", result)
        vid = re.findall("param\['vid']\s=\s(.*?);", result)
        status = sel.xpath('//div[@class="playList-update-tip "]/p/text()').extract_first(default="")
        data_page = sel.xpath('//div[@data-tab-page="body"]').extract()
        site = sel.xpath('//div[@class="mod-juji-album-vip"]').extract()
        # print(site)
        vip = "免费"
        if len(site) == 0:
            vip = "收费"
        region = re.findall('rseat="707181_region">(.*?)', result)
        genres = re.findall('rseat="707181_genres">(.*?)', result)
        time = re.findall('rseat="707181_time"\starget="_blank">(.*?)', result)
        if len(region) == 0:
            region = ["None"]
        if len(genres) == 0:
            genres = ["None"]
        if len(time) == 0:
            time = ["None"]
        if len(albumid) == 0:
            yield scrapy.Request(url=response.url, callback=self.parse_index, dont_filter=True)
        else:
            # print(region[0])  # 地区
            # print(genres[0])  # 种类
            # print(time[0])  # 年代
            # print(len(data_page))  # 页数
            data = {"url": response.url, "status": status, "vip": str(vip), "vid": vid[0].strip("\""),
                    "tvid": tvid[0].strip("\""),
                    "albumid": albumid[0].strip("\""),
                    "region": region[0].strip("\""),
                    "genres": genres[0].strip("\""),
                    "time": time[0].strip("\""),
                    "page": len(data_page)}
            # yield scrapy.Request(
            #     url=self.jiming_url.format(albumId=str(albumid[0]).strip("\""), size=str(len(data_page) * 30)),
            #     callback=self.parse_num)
            yield scrapy.Request(
                url=self.urls.format(referenceId=str(tvid[0]).strip("\""), albumId=str(albumid[0]).strip("\"")),
                meta={"data": data}, callback=self.parse_item, dont_filter=True)

    def parse_num(self, response):
        result = response.text.strip("var tvInfoJs=")
        # print(result)
        jj = json.loads(result)
        data = response.meta["data"]
        subtitle = ""
        for i in range(len(jj.get("mixinVideos"))):
            subtitle = subtitle + "第" + str(i + 1) + "集:" + jj.get("mixinVideos")[i].get("subtitle") + ";"
        data["subtitle"] = subtitle
        tvid = response.meta["data"].get("tvid")
        vid = response.meta["data"].get("vid")
        # print(self.cache_urls.format(tvid=tvid, vid=vid))
        yield scrapy.Request(url=self.cache_urls.format(tvid=tvid, vid=vid), meta={"data": data},
                             callback=self.parse, dont_filter=True)


# Copyright (c) 2015 Satlab Aps <satlab@satlab.com>
# Released under the MIT license (See COPYING for details)

# Hameg HMP2030 Power supply control

import time
import serial

class HMP2030Exception(Exception):
    pass

class HMP2030():
    def __init__(self, port, baud=9600, timeout=1):
        self.serial = serial.Serial(port, baud, timeout=timeout)

        self.get_ident()

    def __del__(self):
        try:
            self.serial.close()
        except:
            pass

    def _write(self, data):
        self.serial.write(data)
        time.sleep(0.1)

    def get_ident(self):
        self._write('*IDN?\r\n')
        idn = self.serial.readline()

        if not 'HMP2030' in idn:
            raise HMP2030Exception('Failed to identify HMP2030 power supply')

        return idn

    def set_channel(self, channel):
        if channel < 1 or channel > 3:
            raise HMP2030Exception('Channel must be between 1 and 3')

        self._write('INST:NSEL {0}\r\n'.format(channel))

    def measure_voltage(self, channel):
        self.set_channel(channel)
        self._write('MEAS:VOLT?\r\n')
        volt = self.serial.readline()
        if len(volt) < 1:
            raise HMP2030Exception('Failed to read voltage')

        return float(volt)

    def measure_current(self, channel):
        self.set_channel(channel)
        self._write('MEAS:CURR?\r\n')
        curr = self.serial.readline()
        if len(curr) < 1:
            raise HMP2030Exception('Failed to read current')

        return float(curr)

    def measure_power(self, channel, average=1):
        total = 0
        peak = 0
        for m in range(average):
            volt = self.measure_voltage(channel)
            curr = self.measure_current(channel)
            power = volt * curr
            if power > peak:
                peak = power
            total += power

        total = total / average

        return float(total), float(peak)

    def activate_channel(self, channel, state):
        self.set_channel(channel)

        nstate = '1' if state == True else '0'
        self._write('OUTP:SEL {0}\r\n'.format(nstate))

    def activate_output(self, state):
        nstate = '1' if state == True else '0'
        self._write('OUTP:GEN {0}\r\n'.format(nstate))


# -*- coding: utf-8 -*-
# Copyright by Beyondy, 2010-2020
# All rights reserved.

from config.config import Config
from models.funds import FundProfile, MstarCategory, FundManagerHistory
from libs.utils.db import *
from libs.utils.date import *
from libs.utils.app import init_app
from libs.constants import *
from libs.logger import Logger

from optparse import OptionParser
from sqlalchemy import func
from datetime import datetime, date
import requests
import random
import json
import re
from bs4 import BeautifulSoup as BS4

def __download_mstar_code(cn_code):
	try:
		timestamp = int(datetime.now().timestamp() * 1000)
		url = "http://cn.morningstar.com/cacheapi/json/quickquery.ashx?callback=jsonp{}&q={}&limit=31&timestamp={}&usid=false&type=fund".format(timestamp, cn_code, timestamp)
		rsp = requests.get(url)
		logger.debug("download mstar code, url={}, rsp={}".format(url, rsp.text))

		txt = re.findall('(\[.*\])', rsp.text)
		if not (txt and txt[0]):
			logger.warn("download mstar code for cn_code={} failed, got rsp: {}".format(cn_code, rsp.text))
			return None

		arr = json.loads(txt[0])
		for obj in arr:
			#{'Ext': '002625', 'Key': '0P00017S27', 'Type': 'fund', 'Value': '博时安怡6个月定期开放债券'}
			codes = obj.get('Ext', '').split(',')
			for code in codes:
				if code == cn_code:
					mstar_code = obj.get('Key')
					return mstar_code
		return None
	except Exception as ex:
		logger.error('download mstar code for cn_code={} failed and got exception: {}'.format(cn_code, str(ex)))
	return None

def __download_mstar_category(cn_code, mstar_code):
	try:
		url = "http://cn.morningstar.com/handler/quicktake.ashx?command=banchmark&fcid={}&randomid={}".format(mstar_code, random.random())
		rsp = requests.get(url)
		obj = rsp.json()

		logger.debug("download mstar category for cn_code={}, mstar_code={} success: {}"\
				.format(cn_code, mstar_code, json.dumps(obj, ensure_ascii=False)))
		return obj
	except Exception as ex:
		logger.error("download mstar category for cn_code={}, mstar_code={} failed and got exception: {}".format(cn_code, mstar_code, str(ex)))
	return None

def __check_and_update_category(session, category_code, category_name):
	try:
		category = session.query(MstarCategory).filter(MstarCategory.category_code == category_code).first()
		if category:
			if category.category_name == category_name:
				return 0
			else:
				category.category_name = category_name
				category.last_update_time = datetime.now()
		else:
			now = datetime.now()
			category = MstarCategory(category_code = category_code, category_name = category_name, create_time = now, last_update_time = now)
			session.add(category)
		# add or update
		return 1
	except Exception as ex:
		logger.error("update category for code={}, name={} failed and got exception: {}".format(category_code, category_name, str(ex)))
	return -1

def __download_eastm_funds_in_(fund_id, cn_code):
	#即将认购
	#http://fund.eastmoney.com/data/FundNewIssue.aspx?t=zs&sort=jzrgq,desc&page=0,500&isbuy=2&v=0.5936050857704249
	#正在认购
	#var zsdatas={datas:[["005454","前海开源医疗健康C","前海开源基金","80280038","混合型","17/12/11～18/01/12","0.00%","曲扬","认购期","","2","前海开源基金","30198132"],
	#http://fund.eastmoney.com/data/FundNewIssue.aspx?t=zs&sort=jzrgq,desc&page=1,500&isbuy=1&v=0.28588074162167687
	#新成立基金
	#http://fund.eastmoney.com/data/FundNewIssue.aspx?t=xcln&sort=jzrgq,desc&y=&page=1,5000&isbuy=1&v=0.09038513837858586
	pass

def __download_eastm_profile(fund_id, cn_code):
	try:
		now = datetime.now()
		ts = int(now.timestamp() * 1000)
		url = "https://fundmobapi.eastmoney.com/FundMApi/FundDetailInformation.ashx?deviceid=1234&version=1.0.0&product=EFund&plat=Iphone&FCODE={}&_={}&callback=jsonp6".format(cn_code, ts)
		rsp = requests.get(url)
		if rsp.status_code != 200:
			logger.warn("%s", "download eastm profile for cn_code={} url={} failed: status-code={}, reason={}".format(cn_code, url, rsp.status_code, rsp.reason))
			return None

		jps = re.findall("\"Datas\": ({[^}]+})", rsp.text)
		obj = json.loads(jps[0]) if jps and jps[0] else {}
		if not jps or not obj:
			logger.warn("%s", "download eastm profile for cn_code={} url={} failed: obj is None. rsp-text={}".format(cn_code, url, rsp.text[:64]))
			return None

		data = {
			"short_name": obj.get("SHORTNAME"),
			"full_name": obj.get("FULLNAME"),
			"ftype": obj.get("FTYPE"),
			"inception_date": obj.get("ESTABDATE"),
			"company": obj.get("JJGS"),
			"custodian_bank": obj.get("TGYH"),
			"manager": obj.get("JJJL"),
			"benchmark": obj.get("BENCH")
		}

		logger.debug("download eastm profile for cn_code={}: {}".format(cn_code, data))
		return data
	except Exception as ex:
		logger.error("download eastm fund profile for code={} failed and got exception: {}".format(cn_code, str(ex)))
		return None

def __save_fund_managers(session, fmhs):
	for fmh in fmhs:
		old_fmhs = session.query(FundManagerHistory).filter(FundManagerHistory.fund_id == fmh.fund_id,
		                                                    FundManagerHistory.manager_id == fmh.manager_id,
		                                                    FundManagerHistory.begin_day == fmh.begin_day).all()
		if not old_fmhs:
			session.add(fmh)
	#end-for

def __download_manager_history(session, fund_id, cn_code):
	try:
		now = datetime.now()
		ts = int(now.timestamp() * 1000)
		url = "https://fundmobapi.eastmoney.com/FundMApi/FundManagerList.ashx?deviceid=app_danganye_f10" \
		        "&version=V2.1.0&product=EFund&plat=Iphone&FCODE={}&_={}&callback=Zepto{}"\
					.format(cn_code, ts, ts - 1010)
		rsp = requests.get(url)
		if rsp.status_code != 200:
			logger.warn("%s", "download eastm manager history for cn_code={} url={} failed: "\
								"status-code={}, reason={}"\
									.format(cn_code, url, rsp.status_code, rsp.reason))
			return None

		jps = re.findall("\"Datas\": (\[[^]]+\])", rsp.text)
		objs = json.loads(jps[0]) if jps and jps[0] else {}
		if not jps or not objs:
			logger.warn("%s", "download eastm manager history for cn_code={} url={} failed: obj is None. rsp-text={}".format(cn_code, url, rsp.text[:64]))
			return None

		fmhs = []
		for obj in objs:
			if not obj.get('MGRID') or not obj.get('FCODE'):
				continue
			mgr_ids = obj.get('MGRID', '').split(',')
			mgr_names = obj.get('MGRNAME', '').split(',')
			if len(mgr_ids) != len(mgr_names):
				continue

			now = datetime.now()
			for i, mgr_id in enumerate(mgr_ids):
				fmh = FundManagerHistory(fund_id=fund_id,
				                         manager_id=mgr_id,
				                         manager_name=mgr_names[i],
				                         begin_day=str2day(obj.get('FEMPDATE')),
				                         end_day=str2day(obj.get('LEMPDATE')),
				                         tenure_days=obj.get('DAYS'),
				                         tenure_return=obj.get('PENAVGROWTH'),
				                         create_time=now,
				                         last_update_time=now)
				fmhs.append(fmh)
			#end-for
		#end-for

		return fmhs
	except Exception as ex:
		logger.error("download fund({}) manager history failed and got exception: {}".format(cn_code, str(ex)))
		return None

def	__check_and_download_profile(session, fund_id, cn_code, update_mgr_history):
	try:
		update = 0
		fp = session.query(FundProfile).filter(FundProfile.cn_code == cn_code).first()

		# step 1: update from mstar
		if fp.mstar_code and fp.mstar_category_code:
			logger.debug("fund(fund_id={}, cn_code={}) has mstar info: code={}, category=({}, {})" \
					.format(fund_id, cn_code, fp.mstar_code, fp.mstar_category_code, \
						fp.mstar_category_name))
		else:
			mstar_code = __download_mstar_code(cn_code)
			if not mstar_code:
				logger.warn("failed to get mstar code for cn_code={}".format(cn_code))
				#inogre it
			else:
				obj = __download_mstar_category(cn_code, mstar_code)
				if not obj:
					logger.warn("failed to get mstar category info for cn_code={}, mstar_code={}".format(cn_code, mstar_code))
				else:
					if obj.get('CategoryId') or obj.get('CategoryName'):
						__check_and_update_category(session, obj.get('CategoryId'), obj.get('CategoryName'))
					else:
						logger.warn("no CategoryId and CategoryName found from mstar for cn_code={}, mstar_code={}".format(cn_code, mstar_code))
					#end-if

					fp.mstar_code = mstar_code
					fp.mstar_category_code = obj.get('CategoryId')
					fp.last_update_time = datetime.now()

					update += 1
				#end-if
			#end-if
		#end-if

		#step 2: update from eastm
		if fp.eastm_ftype and fp.inception_day:
			logger.debug("fund(fund_id={}, cn_code={}) has eastm info: type={}, inception_day={}"\
					.format(fund_id, cn_code, fp.eastm_ftype, fp.inception_day))
		else:
			obj2 = __download_eastm_profile(fund_id, cn_code)
			if not obj2:
				logger.warn("failed to get eastm fund data for cn_code={}".format(cn_code))
			else:
				fp.eastm_ftype = obj2.get('ftype')
				fp.inception_day = str2day(obj2.get('inception_date')) if obj2.get('inception_date') else None
				#TODO: more fields
				fp.last_update_time = datetime.now()

				update += 1
			#end-if
		#end-if

		#step 3: download fund managers history
		if update_mgr_history:
			fmhs = __download_manager_history(session, fund_id, cn_code)
			if fmhs:
				__save_fund_managers(session, fmhs)
				update += 1

		#TODO: how to explicit update?
		return 1 if (update > 0) else 0
	except Exception as ex:
		logger.error("download profile for fund_id={}, cn_code={} failed and got exception: {}".format(fund_id, cn_code, str(ex)))
	return -1

def download_fund_profile(fund_id, cn_code, update_mgr_history):
	logger.debug("fundid={}, cn-code={}".format(fund_id, cn_code))
	with ScopedSession() as session:
		try:
			__check_and_download_profile(session, fund_id, cn_code, update_mgr_history)
			session.commit()
		except Exception as ex:
			session.rollback()
			logger.error("%s", "download fund profile for ticker={} failed and got exception: {}".format(cn_code, ex))
	# end-with

def get_all_fund_codes():
	try:
		with ScopedSession() as session:
			fcs = session.query(FundProfile.fund_id, FundProfile.cn_code).all()
			return [(fc[0], fc[1]) for fc in fcs]
		#endwith
	except Exception as ex:
		logger.error("load all fund codes from db failed and got exception: {}".format(str(ex)))
		return None

if __name__ == '__main__':
	init_app()
	global logger
	logger = Logger.getLogger(__name__)

	parser = OptionParser()
	parser.add_option('-c', '--fund-code', dest='fundcode', help='just download nav for the specified funds, separated by \",\". Default is ALL.')
	parser.add_option('-m', '--update-manager-history', action='store_true', dest='update_mgr_history', help='download fund managers history')

	try:
		(options, args) = parser.parse_args()
		if options.fundcode:
			fcs = []
			for fund_code in options.fundcode.split(','):
				fcs.append((None, fund_code))
		else:
			fcs = get_all_fund_codes()

		if options.update_mgr_history is not None:
			update_mgr_history = True
		else:
			update_mgr_history = False

		for fc in fcs:
			fund_id, cn_code = fc
			download_fund_profile(fund_id, cn_code, update_mgr_history)
		#endfor
		exit(0)
	except Exception as ex:
		logger.error("main loop (download profile) failed and got exception: {}".format(str(ex)))
		exit(1)
#end-if-main


import asyncio
import collections
import functools
import math
import random
import time
from enum import Enum
from io import BytesIO

import aiohttp
import typing

import discord
from PIL import ImageFont, ImageDraw, ImageOps, Image
from discord.ext import commands, tasks
from jishaku.functools import executor_function

from .classes import other
from .classes.converters import AmountConverter
from .utils import fuzzy, get_language

class SlotsEmojis(Enum):
    CACTUS = "\U0001f335"
    GEM = "\U0001f48e"
    HEART = "\U00002764"
    SPARKLING_HEART = "\U0001f496"
    STAR = "\U00002b50"
    ROSE = "\U0001f339"

class CatError(commands.CommandError):
    def __init__(self, type_):
        self.type = type_

class CatIsDead(CatError):
    def __init__(self):
        super().__init__('dead')

class MemberDoesNotHaveCat(CatError):
    def __init__(self):
        super().__init__('no_cat')

class DefaultCat:
    """Default representation for cats"""
    def __init__(self, bot, cat):
        self.bot = bot
        self.cat = cat

    def __getattr__(self, attr):
        if attr == "owner":
            return self._owner
        if attr == "stamina":
            attr = "sta"
        if attr == "health":
            attr = "hp"

        return self.cat[attr]

    @property
    def _owner(self):
        user = self.bot.get_user(self.cat['owner_id'])
        return user

    async def buy(self, item):
        try:
            cost = COST[item]
        except KeyError:
            cost = WEAPONS_PRICE[item]

        self.cat['inventory'].append(item)

        await self.bot.db.execute("UPDATE cats SET money = money - $1, inventory = $2 WHERE owner_id = $3", cost,
                                  self.inventory, self.cat['owner_id'])

        return self.money - cost

    async def sell(self, item):
        try:
            price = COST[item]
        except KeyError:
            price = WEAPONS_PRICE[item]

        self.cat['inventory'].remove(item)

        await self.bot.db.execute("UPDATE cats SET money = money + ($1 / 2), inventory = $2 WHERE owner_id = $3", price,
                                  self.inventory, self.cat['owner_id'])

        return self.money + round(price / 2)

WEAPON_DAMAGE_MAP = {
    "glock-6": 17,
    "shovel": 9,
    "glock-9": 12,
    "usp-s": 17,
    "axe": 13,
    "p90": 16,  # 999
    "mp7": 17,
    "mac-10": 27300,
    "mp9": 17,
    "m4a1": 23,
    "deagle": 66,
    "glock-17": 19,
    "ak-47": 24,
    "beretta": 25,
    "thompson": 28,
    "m16": 34,
    "awp": 79,
    "luger": 72
}

WEAPON_RELOAD_MAP = {
    "glock-6": 1,
    "shovel": 2,
    "glock-9": 1,
    "usp-s": 1,
    "axe": 2,
    "p90": 1,
    "mp7": 1,
    "mac-10": 1,
    "mp9": 1,
    "m4a1": 1,
    "deagle": 4,
    "glock-17": 1,
    "ak-47": 1,
    "beretta": 2,
    "thompson": 1,
    "m16": 2,
    "awp": 3,
    "luger": 3
}

class Weapon:
    def __init__(self, type_):
        self.type = type_

        self.reloaded = True

        self._reload_in = 0  # rounds
        if type_ not in WEAPON_RELOAD_MAP:
            raise ValueError(f"{type_} not in reload_map")
        self.__default_reload = WEAPON_RELOAD_MAP[type_]

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

    def __str__(self):
        return f'<Weapon type={self.type}>'

    @property
    def raw(self):
        return self.type

    @property
    def damage(self):
        try:
            return WEAPON_DAMAGE_MAP[self.type]
        except KeyError:
            return None

    @property
    def reload_in(self):
        return self._reload_in

    def _shoot(self, enemy):
        enemy.hp -= random.randint(self.damage - 5, self.damage + 4)

    def shoot(self, enemy: collections.namedtuple):
        if self.reloaded:
            self.reloaded = False
            self._reload_in = self.__default_reload

            self._shoot(enemy)

            self._reload_in -= 1

        elif self.__default_reload == 1:
            self._shoot(enemy)
        elif self._reload_in == 0:
            self.reloaded = True
        else:
            self._reload_in -= 1

ALL_WEAPONS = ['ak-47', 'knife', 'm4a1', 'm16', 'glock-17', 'glock-9', 'glock-6', 'desert eagle', 'awp', 'usp-s',
               'thompson', 'beretta', 'luger', 'p90', 'mp7', 'mac-10', 'mp9', 'axe', 'shovel']

class Box:
    def __init__(self, type_):
        self.type = type_

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

    def __str__(self):
        return f'<Box type={self.type}>'

    @property
    def raw(self):
        return self.type

ALL_BOXES = ['common_box', 'rare_box', 'epic_box', 'legendary_box', 'pretty_box']

USEABLE_ITEMS = ['karma', 'energy drink', 'health drink', 'food drink']

class ItemsConverter(commands.Converter):
    def __init__(self, type_):
        self.type = type_

    async def convert(self, ctx, argument):
        if self.type == 'box':
            box = fuzzy.extract_one(argument, ALL_BOXES, score_cutoff=60)

            if not box:
                return None

            box = box[0]

            return Box(box)

        elif self.type == 'weapon':
            weapon = fuzzy.extract_one(argument, ALL_WEAPONS, score_cutoff=60)

            if not weapon:
                return None

            weapon = weapon[0]

            return Weapon(weapon)

        elif self.type == 'item':
            item = fuzzy.extract_one(argument, ALL_ITEMS, score_cutoff=60)

            if not item:
                return None

            item = item[0]

            return item

        elif self.type == 'useable':
            item = fuzzy.extract_one(argument, USEABLE_ITEMS, score_cutoff=60)

            if not item:
                return None

            item = item[0]

            return item

        else:
            raise NotImplemented(f"{self.type} is not implemented in this converter.")

COST = {
    "karma": 2000,
    "energy drink": 22000,
    "health drink": 24000,
    "food drink": 20000,

    "common_box": 7000,
    "rare_box": 14500,
    "epic_box": 46000,
    "legendary_box": 320000,
}

WEAPONS_PRICE = {
    "glock-6": 12500,
    "shovel": 9500,
    "glock-9": 14000,
    "usp-s": 17000,
    "axe": 10000,
    "p90": 12000,
    "mp7": 16000,
    "mac-10": 27300,
    "mp9": 32000,
    "m4a1": 29200,
    "deagle": 26700,
    "glock-17": 22420,
    "ak-47": 92000,
    "beretta": 62000,
    "thompson": 175000,
    "m16": 340000,
    "awp": 420000,
    "luger": 1700000
}

ALL_ITEMS = ["karma", "energy drink", "food drink", "health drink"]
ALL_ITEMS.extend(ALL_BOXES)
ALL_ITEMS.extend(ALL_WEAPONS)

BOX_MAP = {
    "common_box": ['glock-6', 'shovel', 'glock-9', 'usp-s', 'p90'],
    "rare_box": ['axe', 'm4a1', 'desert eagle', 'glock-17', 'mp7'],
    "epic_box": ['ak-47', 'beretta', 'thompson', 'm16', 'mac-10', 'mp9'],
    "legendary_box": ['awp', 'luger'],
    "pretty_box": ['legendary_box']  # todo ...
}

BOX_COLOR_MAP = {
    "common_box": discord.Color.greyple(),
    "rare_box": discord.Color.blue(),
    "epic_box": discord.Color.red(),
    "legendary_box": discord.Color.gold(),
    "pretty_box": discord.Color.dark_purple()
}

class Cat(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

        self.losing_food.start()
        self.losing_sta.start()
        self.sleeping_restore.start()

        self.rose_team = [185712375628824577, 403600724342079490]

        self._fights = list()

    def cog_unload(self):
        self.losing_food.cancel()
        self.losing_sta.cancel()
        self.sleeping_restore.cancel()

    # async def update_cache(self, cat):
    #     user = self.bot.get_user(cat.owner_id)
    #
    #     cat_ = await self.bot.db.fetchrow("SELECT * FROM cats WHERE owner_id = $1", cat.owner_id)
    #     new_cat = DefaultCat(self.bot, cat_)
    #
    #     if cat.owner_id in self._cats_cache:
    #         self._cats_cache[cat.owner_id] = new_cat
    #
    #     if cat.owner_id in self._processing_cache:
    #         path = r"assets/images/cats/{}/{}.png"
    #         image_path = path.format(new_cat.color, 1)
    #
    #         avatar_bytes = await self.get_avatar(user)
    #
    #         fn = functools.partial(self.processing, avatar_bytes, new_cat, image_path, user)
    #         final_buffer = await self.bot.loop.run_in_executor(None, fn)
    #
    #         self._processing_cache[user.id] = final_buffer

    async def _prepare_trivia(self, ctx, *, diff=None):
        trivia_url = "https://opentdb.com/api.php?amount=1500&type=multiple"
        if diff is not None:
            trivia_url += f'&difficulty={diff}'

        async with self.bot.session.get(trivia_url) as req:
            data = await req.json()

        trivia = random.choice(data['results'])

        e = discord.Embed(
            description=f"{ctx.lang['category']}: {trivia['category']}\n{ctx.lang['difficulty']}: "
                        f"{trivia['difficulty']}\n{ctx.lang['you_have']} **60** {ctx.lang['seconds']}.",
            timestamp=ctx.message.created_at)

        question = trivia['question'].replace(''', "'")
        question = question.replace('"', "“")

        e.set_author(name=question)

        numbers = [
            '1\N{combining enclosing keycap}',
            '2\N{combining enclosing keycap}',
            '3\N{combining enclosing keycap}',
            '4\N{combining enclosing keycap}'
        ]

        answers = [trivia['correct_answer']]

        answers.extend(trivia['incorrect_answers'])

        random.shuffle(answers)

        data = {}

        for number, quest in zip(numbers, answers):

            quest = quest.replace(''', "'")
            quest = quest.replace('"', "“")

            data[number] = quest

            e.add_field(name=number, value=data[number], inline=False)

        message = await ctx.send(embed=e)
        for number in numbers:
            await message.add_reaction(number)

        def check(reaction, user):
            return user == ctx.author and reaction.message.channel.id == ctx.channel.id

        try:
            r, u = await self.bot.wait_for('reaction_add', check=check, timeout=60)
        except asyncio.TimeoutError:
            return await ctx.send(ctx.lang['TimeoutError'])

        correct = None

        for key, value in data.items():
            if value == trivia['correct_answer']:
                correct = key

        if r.emoji == correct:
            return True, trivia['correct_answer'], trivia['difficulty']
        return False, trivia['correct_answer'], trivia['difficulty']

    async def get_cat(self, member):
        cat = await self.bot.db.fetchrow("SELECT * FROM cats WHERE owner_id = $1", member.id)
        guild = await self.bot.get_guild_settings(member.guild.id)
        lang = self.bot.get_language_object(guild.lang)
        if await self.is_dead(member):
            raise commands.BadArgument(f"{member}, {lang['dead_cat'].format(guild.prefix)}")
        if not cat:
            raise commands.BadArgument(f"{member}, {lang['no_cat'].format(guild.prefix)}")
        return DefaultCat(self.bot, cat)

    async def lvl_up(self, cat):
        if cat['exp'] > round(9222 * cat['level']):
            await self.bot.db.execute("UPDATE cats SET level = level + 1, money = money + 1000  WHERE owner_id = $1",
                                      cat['owner_id'])
            return True
        else:
            return False

    async def is_dead(self, member):
        cat = await self.bot.db.fetchrow("SELECT * FROM cats WHERE owner_id = $1", member.id)
        if not cat:
            return

        cat = DefaultCat(self.bot, cat)

        if cat.food <= 0:
            await self.bot.db.execute("UPDATE cats SET is_dead = True, is_sleeping = False WHERE owner_id = $1",
                                      member.id)
            return True

        if cat.stamina <= 0:
            await self.bot.db.execute("UPDATE cats SET is_dead = True WHERE owner_id = $1", member.id)
            return True

        if cat.health <= 0:
            await self.bot.db.execute("UPDATE cats SET is_dead = True, is_sleeping = False WHERE owner_id = $1",
                                      member.id)
            return True

        if cat.is_dead:
            return True

        return False

    @tasks.loop(minutes=180)
    async def losing_food(self):  # todo notify about low food,sta
        async with self.bot.db.acquire():
            await self.bot.db.execute("UPDATE cats SET food = food - 1 WHERE food > 0")
            await self.bot.db.execute("UPDATE cats SET is_dead = TRUE WHERE food = 0")

    @tasks.loop(seconds=60)
    async def sleeping_restore(self):
        async with self.bot.db.acquire():
            await self.bot.db.execute("UPDATE cats SET sta = sta + 1, sleeping_time = sleeping_time + 1 WHERE sta < 100 AND is_sleeping = true AND is_dead = false")
            await self.bot.db.execute("UPDATE cats SET sleeping_time = 0, is_sleeping = false WHERE is_sleeping = true AND sta = 100")

    @tasks.loop(minutes=75)
    async def losing_sta(self):
        async with self.bot.db.acquire():
            await self.bot.db.execute("UPDATE cats SET sta = sta - 1 WHERE sta > 0 AND is_sleeping = FALSE")
            await self.bot.db.execute("UPDATE cats SET is_dead = TRUE WHERE sta = 0")

    @losing_food.before_loop
    @sleeping_restore.before_loop
    @losing_sta.before_loop
    async def b4_tasks(self):
        await self.bot.wait_until_ready()

    @staticmethod
    def progress_bar(draw, y: int, color: tuple, progress: int):
        draw.rectangle(((41, y), (42 + (round(progress * 3.34)), y + 27)), fill=color)

    @staticmethod
    def write(image, text, cords: tuple, font, color: tuple = (0, 0, 0)):
        image.text(cords, text, fill=color, font=font)

    async def get_avatar(self, user: typing.Union[discord.User, discord.Member]) -> bytes:
        avatar_url = user.avatar_url_as(format="png")

        async with self.bot.session.get(str(avatar_url)) as response:
            avatar_bytes = await response.read()

        return avatar_bytes

    @executor_function
    def processing(self, avatar_bytes: bytes, cat, image_path, user) -> BytesIO:  # color: tuple,
        with Image.open(BytesIO(avatar_bytes)) as im:
            pic = r"assets/images/profile.png"
            size = 80, 80
            theme = cat.theme

            with Image.open(image_path) as caty:
                caty = caty.convert("RGBA")
                caty = caty.resize((240, 240))

                with Image.open(pic) as profile:
                    bg = Image.new('RGBA', (700, 800))
                    if theme:
                        end = ".png"
                        if theme == 'jungle1':
                            end = '.jpg'
                        theme = Image.open(r"assets/images/themes/" + theme + end)
                        bg.paste(theme)

                    bg.paste(profile, (0, 0), profile)

                    mask = Image.new('L', size, 0)
                    draw = ImageDraw.Draw(mask)
                    draw.ellipse((0, 0) + size, fill=255)

                    rgb_avatar = ImageOps.fit(im, size, centering=(0.5, 0.5))
                    rgb_avatar.putalpha(mask)
                    rgb_avatar.thumbnail(size)

                    bg.paste(caty, (44, 105), caty)
                    bg.paste(rgb_avatar, (35, 10), rgb_avatar)

                    PISTOLS = ['glock_17', 'glock-6', 'glock-9']

                    if cat.wore_gun:
                        if cat.wore_gun in PISTOLS:
                            x, y = 80, 200
                            size = (10, 10)
                        else:
                            x, y = 70, 240
                            size = (188, 120)

                        gun = Image.open(f'assets/images/guns/{cat.wore_gun}.png')
                        gun = gun.convert("RGBA")
                        gun = gun.resize(size, Image.ANTIALIAS)

                        bg.paste(gun, (x, y), gun)

                    d = ImageDraw.Draw(bg)

                    font = ImageFont.truetype(r"assets/images/fonts/light.otf", 45)
                    emoji_font = ImageFont.truetype(r"assets/images/fonts/emotes.otf", 45)

                    name = cat.name
                    if len(name) > 12:
                        name = name[:12] + "..."

                    self.write(d, name, (330, 100), font=font, color=(255, 255, 255))

                    if cat.owner.id in self.rose_team:
                        width = d.textsize(name, font=font)
                        self.write(d, "\U0001f339", (width[0] + 335, 110), font=emoji_font, color=(255, 255, 255))

                    self.write(d, str(cat.premium), (500, 211),
                               font=ImageFont.truetype(r"assets/images/fonts/medium.otf", 30))

                    self.write(d, '{:,d}'.format(int(cat.money)), (470, 254),
                               font=ImageFont.truetype(r"assets/images/fonts/medium.otf", 30))

                    self.write(d, str(cat.level), (460, 304),
                               font=ImageFont.truetype(r"assets/images/fonts/medium.otf", 30))

                    member_name = user.name
                    if len(member_name) > 18:
                        member_name = member_name[:18] + "..."
                    self.write(d, member_name, (10, 765), font=ImageFont.truetype(r"assets/images/fonts/medium.otf", 15))

                    self.progress_bar(d, 441, (52, 152, 219), cat.stamina)
                    self.write(d, str(cat.stamina), (175, 435), font=ImageFont.truetype(r"assets/images/fonts/medium.otf", 30))
                    self.progress_bar(d, 548, (46, 204, 113), cat.food)
                    self.write(d, str(cat.food), (175, 542), font=ImageFont.truetype(r"assets/images/fonts/medium.otf", 30))
                    self.progress_bar(d, 647, (233, 30, 64), cat.health)
                    self.write(d, str(cat.health), (175, 641), font=ImageFont.truetype(r"assets/images/fonts/medium.otf", 30))
                    final_buffer = BytesIO()
                    bg.save(final_buffer, "png")

        final_buffer.seek(0)
        return final_buffer

    @commands.group(invoke_without_command=True, aliases=['profile'])
    async def cat(self, ctx, member: discord.Member = None):
        """Pokazuje profil twojego kota."""
        member = member or ctx.author
        cat = await self.get_cat(member)

        if cat.is_sleeping:
            return await ctx.send(ctx.lang['cat_is_sleeping'])

        if cat.is_sleeping and cat.stamina == 100:
            return await ctx.send(ctx.lang['full_sta_sleeping_cat'])

        async with ctx.typing():
            start = time.time()

            # if ctx.author.id in self._processing_cache:
            #     buffer = self._processing_cache[ctx.author.id]
            #     buffer.seek(0)
            #     file = discord.File(filename=f"{ctx.author.id}.png", fp=buffer)
            #
            #     end = time.time()
            #     await ctx.send(f"Done in: {round(end-start, 4)}s", file=file)
            # else:
            path = r"assets/images/cats/{}/{}.png"
            image_path = path.format(cat.color, 1)

            avatar_bytes = await self.get_avatar(member)

            buff = await self.processing(avatar_bytes=avatar_bytes, cat=cat, image_path=image_path, user=ctx.author)

            # self._processing_cache[ctx.author.id] = final_buffer

            file = discord.File(filename=f"{ctx.author.id}.png", fp=buff)

            end = time.time()
            await ctx.send(f"Done in: {round(end-start, 2)}s", file=file)

    @cat.command()
    async def adopt(self, ctx):
        """Adoptuj swojego zwierzaka"""
        cat = await self.bot.db.fetchrow("SELECT * FROM cats WHERE owner_id = $1", ctx.author.id)
        if cat:
            return await ctx.send(ctx.lang['already_has_cat'])

        def check(m):
            return m.author == ctx.author and m.channel == ctx.channel

        color = random.choice(["czarnego",
                               "szarego",
                               "brązowego"])

        colors = {"czarnego": "black",
                  "szarego": "grey",
                  "brązowego": "brown"}

        await ctx.send(ctx.lang['adopted_cat'].format(ctx.lang[color]))

        try:
            msg = await self.bot.wait_for('message', check=check, timeout=30)
        except asyncio.TimeoutError:
            return await ctx.send(ctx.lang['TimeoutError'])

        name = msg.content
        name = name[:1].upper() + name[1:].lower()
        c = colors[color]
        await self.bot.db.execute("INSERT INTO cats (owner_id, name, color) VALUES ($1, $2, $3)",
                                  ctx.author.id, name, c)
        return await ctx.send(ctx.lang['cat_will_be_called'].format(name))

    @commands.command()
    async def roulette(self, ctx, pick: str, money):
        cat = await self.get_cat(ctx.author)

        z = ['green', 'red', 'black']
        if not pick.lower() in z:
            return await ctx.send(ctx.lang['not_correct_choose'].format(', '.join(z)))

        if money == "all":
            money = cat.money

        if money == "half":
            money = round(cat.money / 2)  # :)))

        if not str(money).isdigit():
            return await ctx.send(ctx.lang['not_correct_value'])

        money = int(money)

        if money < 5:
            return await ctx.send(ctx.lang['too_low_value'])

        if cat.money < money:
            return await ctx.send(ctx.lang['you_dont_have_so_much'])

        x = random.randint(0, 30)
        win = False
        won_money = None

        if pick.lower() == "green":
            if x == 0:
                won_money = money * 14
                win = True
            else:
                win = False
        elif pick.lower() == "black":
            if x >= 15:
                won_money = money * 2
                win = True
            else:
                win = False
        elif pick.lower() == "red":
            if x < 15:
                won_money = money * 2
                win = True
            else:
                win = False

        if win and won_money:
            msg = ctx.lang['you_won'].format(won_money, x)
            await self.bot.db.execute("UPDATE cats SET money = money + $1 WHERE owner_id = $2", won_money,
                                          ctx.author.id)
        else:
            msg = ctx.lang['you_lose'].format(x)
            await self.bot.db.execute("UPDATE cats SET money = money - $1 WHERE owner_id = $2", money,
                                          ctx.author.id)

        await ctx.send(msg)

    @commands.command()
    async def coinflip(self, ctx, money):
        """Postaw i wygraj."""
        cat = await self.get_cat(ctx.author)

        if money == "all":
            money = cat.money

        if money == "half":
            money = round(cat.money / 2)  # :)))

        if not str(money).isdigit():
            return await ctx.send(ctx.lang['not_correct_value'])

        if money < 5:
            return await ctx.send(ctx.lang['too_low_value'])

        if cat.money < money:
            return await ctx.send(ctx.lang['you_dont_have_so_much'])

        x = random.randint(0, 100)

        if x == 0:
            new_money = int(money * random.randint(2, 4))
            await ctx.send(ctx.lang['you_won_jackpot'].format(f"{new_money:,d}"))
        elif x >= 50:
            new_money = int(money + money)
            await self.bot.db.execute("UPDATE cats SET money = $1 WHERE owner_id = $2", new_money, ctx.author.id)
            await ctx.send(ctx.lang['you_won_'].format(f"{new_money:,d}"))
        else:
            new_money = int(money - money)
            await self.bot.db.execute("UPDATE cats SET money = $1 WHERE owner_id = $2", new_money, ctx.author.id)
            await ctx.send(ctx.lang['you_lose_'].format(f"{new_money:,d}"))

    @commands.command()
    @commands.cooldown(1, 5, commands.BucketType.user)
    async def slots(self, ctx, money):
        cat = await self.get_cat(ctx.author)

        if money == "all":
            money = cat.money

        if money == "half":
            money = round(cat.money / 2)  # :)))

        if not str(money).isdigit():
            return await ctx.send(ctx.lang['not_correct_value'])

        money = int(money)

        if money < 5:
            return await ctx.send(ctx.lang['too_low_value'])

        if cat.money < money:
            return await ctx.send(ctx.lang['you_dont_have_so_much'])

        all_slots_icons = [icon.value for icon in SlotsEmojis]

        x1 = random.choice(all_slots_icons)
        x2 = random.choice(all_slots_icons)
        x3 = random.choice(all_slots_icons)

        z = f"{x1} | {x2} | {x3}"

        muliplier_map = {
            "\U0001f335": 1,
            "\U0001f48e": 2,
            "\U00002764": 3,
            "\U0001f496": 4,
            "\U00002b50": 5,
            "\U0001f339": 7
        }

        muliplier = (muliplier_map[x1] +
                     muliplier_map[x2] + muliplier_map[x3]) / 10

        m = muliplier * money
        won_money = round(m) - money

        if won_money < 0:
            text = ctx.lang['slots_lose'].format(z, muliplier, f"{abs(won_money):,d}")

        elif won_money == 0:
            text = ctx.lang['slots_nothing'].format(z, muliplier)

        else:
            text = ctx.lang['slots_won'].format(z, muliplier, f"{won_money:,d}")

        await ctx.send(text)
        await self.bot.db.execute("UPDATE cats SET money = money + $1 WHERE owner_id = $2", won_money,
                                  ctx.author.id)

    @commands.command()
    @commands.cooldown(1, 1800, commands.BucketType.user)
    async def trivia(self, ctx, difficulty=None):
        if difficulty not in [None, "easy", "medium", "hard"]:
            raise commands.BadArgument("Wrong difficulty passed.")

        m = await self._prepare_trivia(ctx, diff=difficulty)
        cat = await self.get_cat(ctx.author)

        won_map = {
            'easy': random.randint(50, 120),
            'medium': random.randint(150, 320),
            'hard': random.randint(400, 650)
        }

        if m[0]:

            won = won_map[m[2]]

            await ctx.send(ctx.lang['trivia_won'].format(won))
            await self.bot.db.execute("UPDATE cats SET money = $1 WHERE owner_id = $2", cat.money + won,
                                      ctx.author.id)
        else:
            return await ctx.send(ctx.lang['trivia_lose'].format(m[1]))

    @cat.command(aliases=['inv', 'i'])
    @commands.cooldown(1, 3, commands.BucketType.user)
    async def inventory(self, ctx):
        cat = await self.get_cat(ctx.author)

        inv_map = dict()

        if not cat.inventory:
            return await ctx.send(ctx.lang['empty_inventory'])

        for item in cat.inventory:
            if item not in inv_map:
                inv_map[item] = 1
            else:
                inv_map[item] += 1

        text = []

        for key, value in inv_map.items():
            text.append(f"{key} x{value}")

        fmt = '\n'.join(text)

        return await ctx.send(f"```\n{fmt}\n```")

    @cat.command(aliases=['o'])
    @commands.cooldown(1, 5, commands.BucketType.user)
    async def open(self, ctx, box: ItemsConverter("box"), amount: AmountConverter = 1):
        cat = await self.get_cat(ctx.author)

        if not box:
            return await ctx.send(ctx.lang['no_box_like'])

        if not cat.inventory:
            return await ctx.send(ctx.lang['empty_inventory'])

        box = box.raw

        if box not in cat.inventory:
            return await ctx.send(ctx.lang['no_box_in_inventory'])

        all_user_boxes = len([item for item in cat.inventory if item == box])

        if amount == "all":
            amount = all_user_boxes

        amount = int(amount)

        if amount > all_user_boxes:
            return await ctx.send(ctx.lang['too_many_boxes'])

        won_items = []

        for _ in range(0, amount):
            won_item = random.choice(BOX_MAP[box])
            won_items.append('`' + won_item + '`')

            cat.inventory.append(won_item)
            cat.inventory.remove(box)

        z = '\n'.join(won_items)
        e = discord.Embed(description=f"{ctx.lang['you_opened']}:\n{z}.", color=BOX_COLOR_MAP[box])
        e.set_footer(text=ctx.author, icon_url=ctx.author.avatar_url)

        await ctx.send(embed=e)
        await self.bot.db.execute("UPDATE cats SET inventory = $1 WHERE owner_id = $2", cat.inventory, ctx.author.id)

    @cat.command()
    @commands.cooldown(1, 5, commands.BucketType.user)
    async def shop(self, ctx):
        cat = await self.get_cat(ctx.author)

        e = discord.Embed(description=f"{ctx.lang['balance']}: {int(cat.money):,d}$")
        e.set_author(name=str(ctx.author), icon_url=ctx.author.avatar_url)

        for item in COST:
            e.add_field(name=item, value=f'{int(COST[item]):,d}$', inline=True)

        e.add_field(name='premium', value='1,5$ pp')
        e.set_footer(text=ctx.lang['buy_example'].format(ctx.prefix, random.choice([x for x in COST])))
        return await ctx.send(embed=e)

    @cat.command()
    @commands.cooldown(1, 10, commands.BucketType.user)
    async def buy(self, ctx, amount: typing.Optional[int] = 1, *, item: ItemsConverter("item") = None):
        cat = await self.get_cat(ctx.author)

        if not item:
            return await ctx.send(ctx.lang['no_item_like'])

        orginal_item = item

        try:
            item = COST[item]
        except KeyError:
            item = WEAPONS_PRICE.get(item)
            if not item:
                return await ctx.send(ctx.lang['no_item_like'])

        if cat.money < item * amount:
            return await ctx.send(ctx.lang['no_money_for'].format(orginal_item, (item * amount) - cat.money))

        if orginal_item == 'karma':
            money = cat.money - (item * amount)
            karma = cat.karma + amount
            await self.bot.db.execute("UPDATE cats SET money = $1, karma = $2 WHERE owner_id = $3", money,
                                      karma, ctx.author.id)
            return await ctx.send(ctx.lang['bought_food'].format(amount))

        x = await ctx.confirm(ctx.lang['confirm_buying'].format(orginal_item, amount, item * amount), ctx.author)

        if x:
            for _ in range(0, amount):
                new_balance = await cat.buy(orginal_item)
            else:
                await ctx.send(ctx.lang['bought'].format(orginal_item, amount, item * amount, new_balance))
        else:
            await ctx.send(ctx.lang['abort'])

    @cat.command(enabled=False)
    async def wear(self, ctx, *, weapon: ItemsConverter("weapon")):
        cat = await self.get_cat(ctx.author)

        if not weapon:
            return await ctx.send(ctx.lang['no_item_like'])

        if weapon.raw not in cat.inventory:
            return await ctx.send(ctx.lang['no_item_in_inventory'])

        await self.bot.db.execute("UPDATE cats SET wore_gun = $1 WHERE owner_id = $2", weapon.raw, ctx.author.id)
        await ctx.send(":ok_hand:")

    @cat.command()
    async def sell(self, ctx, *, item: ItemsConverter("item")):
        cat = await self.get_cat(ctx.author)

        if not item:
            return await ctx.send(ctx.lang['no_item_like'])

        if item not in cat.inventory:
            return await ctx.send(ctx.lang['no_item_in_inventory'])

        orginal_item = item

        try:
            item = WEAPONS_PRICE[item]
        except KeyError:
            item = COST[item]

        price = round(item / 2)
        multiplier = (item / 2) / item * 100

        x = await ctx.confirm(ctx.lang['confirm_selling'].format(orginal_item, price, multiplier),
                              ctx.author)

        if x:
            new_balance = await cat.sell(orginal_item)
            await ctx.send(ctx.lang['sold'].format(orginal_item, new_balance))
        else:
            await ctx.send(ctx.lang['abort'])

    @cat.group(invoke_without_command=True, aliases=['change'])
    async def edit(self, ctx):
        z = []
        for cmd in ctx.command.commands:
            z.append(f"- {cmd.name}")

        return await ctx.send(ctx.lang['commands_group'].format('\n'.join(z)))

    @edit.command()
    async def name(self, ctx, *, new_name: str):
        """Zmień imię zwierzaka."""
        cat = await self.get_cat(ctx.author)

        if cat.money < 1000:
            return await ctx.send(ctx.lang['need_more_money_name'])

        new_name = new_name[:1].upper() + new_name[1:].lower()

        await self.bot.db.execute("UPDATE cats SET name = $1, money = $2 WHERE owner_id = $3", new_name,
                                  cat.money - 1000, ctx.author.id)
        await ctx.send(":ok_hand:")

    @edit.command()
    async def color(self, ctx, *, new_color: str = None):
        """Zmień kolor zwierzaka."""
        cat = await self.get_cat(ctx.author)
        if cat.money < 15000:
            return await ctx.send(ctx.lang['need_more_money_color'])

        colors = ['black', 'brown', 'grey', 'yellow', 'pink']
        premium_colors = ['gold', 'plamablue', 'plama_sea', 'plama_pretty_pink',
                          'plama_mint', 'light_green']
        if cat.premium:
            colors.extend(premium_colors)
        if await self.bot.is_owner(ctx.author):
            colors.append('owner_cat')

        if not new_color:
            return await ctx.send(ctx.lang['avalaible_colors'].format(', '.join(colors)))

        if new_color not in colors:
            return await ctx.send(ctx.lang['no_color_like_this'].format(', '.join(colors)))

        await self.bot.db.execute("UPDATE cats SET color = $1, money = $2 WHERE owner_id = $3", new_color,
                                  cat.money - 15000, ctx.author.id)
        await ctx.send(":ok_hand:")

    @edit.command()
    async def theme(self, ctx, *, new_theme: str = None):
        """Zmień tło na profilu zwierzaka."""
        cat = await self.get_cat(ctx.author)
        if cat.money < 100000:
            return await ctx.send(ctx.lang['need_more_money_theme'])

        themes = ["weed1", "weed2", "weed3", "sky3", "landscape2"]
        premium_themes = ["sky1", "sky2", "sky4", "sky5", "colors1", "jungle1", "void1", "space1", "landscape1",
                          "landscape3", "landscape4", "night_sky1"]
        if cat.premium:
            themes.extend(premium_themes)

        if not new_theme:
            return await ctx.send(ctx.lang['avalaible_themes'].format(", ".join(themes)))

        if new_theme not in themes:
            return await ctx.send(ctx.lang['no_theme_like_this'].format(", ".join(themes)))

        await self.bot.db.execute("UPDATE cats SET theme = $1, money = $3 WHERE owner_id = $2", new_theme,
                                  ctx.author.id, cat.money - 100000)
        await ctx.send(":ok_hand:")

    @cat.command()
    async def daily(self, ctx):
        await ctx.send(f"<https://discordbots.org/bot/538369596621848577/vote>, {ctx.lang['vote_pls']}")

    @cat.command()
    async def premium(self, ctx):  # $$$
        cost = 1.5
        owner = str(self.bot.get_user(self.bot.owner_id))

        e = discord.Embed(description=ctx.lang['premium_text'])
        e.set_footer(text=ctx.lang['info_premium'].format(cost, owner))

        await ctx.send(embed=e)

    @cat.command()
    async def transfer(self, ctx, member: discord.Member, amount: int):
        author = await self.get_cat(ctx.author)
        member_cat = await self.get_cat(member)

        if member.id == ctx.author.id:
            return await ctx.send(ctx.lang['cant_transfer_to_yourself'])

        if author.money < amount:
            return await ctx.send(ctx.lang['not_enough_money_transfer'])

        query_1 = "UPDATE cats SET money = money - $2 WHERE owner_id = $1"
        query_2 = "UPDATE cats SET money = money + $2 WHERE owner_id = $1"

        async with self.bot.db.acquire():
            await self.bot.db.execute(query_1, ctx.author.id, amount)
            await self.bot.db.execute(query_2, member.id, amount)

        await ctx.send(ctx.lang['transfered_money'].format(ctx.author.mention, f"{amount:,d}", member.mention))

    # def processing_fight(self, author, enemy):
    #     path = "assets/images/battle.png"

    @cat.command()
    async def fight(self, ctx, member: discord.Member):
        if member.id == ctx.author.id:
            return await ctx.send(ctx.lang['cant_fight_with_yourself'])

        if member.id in self._fights:
            return await ctx.send(ctx.lang['is_already_in_fight'].format(member))

        if ctx.author.id in self._fights:
            return await ctx.send(ctx.lang['you_already_in_fight'].format(ctx.author.mention))

        author = await self.get_cat(ctx.author)
        enemy_cat = await self.get_cat(member)

        self._fights.append(member.id)
        self._fights.append(ctx.author.id)

        confirmation = await ctx.confirm(ctx.lang['confirm_fight'].format(member.mention, ctx.author.mention), member,
                                         timeout=30)
        if not confirmation:
            self._fights.remove(member.id)
            self._fights.remove(ctx.author.id)
            return await ctx.send(ctx.lang['abort'])

        default_hp = 100
        hp = round(default_hp/20) * '\U0001f7e5'

        text = f"{author.name} ({ctx.author})\n**HP**: {hp} ({(default_hp / 100) * 100}%)\n{enemy_cat.name} ({member})\n**HP:** {hp} ({(default_hp / 100) * 100}%)"

        e = discord.Embed(description=text)
        e.set_author(name=f"{ctx.author} vs {member}")
        # e.set_image(url=f"attachment://fight_{ctx.author.id}vs{member.id}.png")

        # async with ctx.typing():
        # start = time.time()
        #
        #     author_bytes = await self.get_avatar(ctx.author)
        #     enemy_bytes = await self.get_avatar(member)
        #
        #     fn = functools.partial(self.processing_fight, author_bytes, enemy_bytes)
        #     final_buffer = await self.bot.loop.run_in_executor(None, fn)
        #
        #     file = discord.File(filename=f"fight_{ctx.author.id}vs{member.id}.png", fp=final_buffer)
        #
        # end = time.time()
        # f"Done in: {round(end-start, 2)}s",

        author_weapon, enemy_weapon = await self.choose_weapon(ctx, member)

        if author_weapon is None and enemy_weapon is None:
            self._fights.remove(member.id)
            self._fights.remove(ctx.author.id)
            return await ctx.send(ctx.lang['abort'])

        class Player:
            def __init__(self, id_, weapon, hp_, member_object, cat):
                self.id = id_
                self.weapon = weapon
                self.hp = hp_
                self.member_object = member_object
                self.cat = cat

        enemy = Player(member.id, enemy_weapon, default_hp, member, enemy_cat)
        ally = Player(ctx.author.id, author_weapon, default_hp, ctx.author, author)

        winner, loser = await self.start_fight(ctx.channel, e, enemy, ally)

        query = "UPDATE cats SET rating = $1 WHERE owner_id = $2"

        rating1, rating2 = other.match(author, enemy_cat, winner.cat)

        rating1 = round(rating1)
        rating2 = round(rating2)

        new_hp_query = "UPDATE cats SET hp = $1 WHERE owner_id = $2"

        async with self.bot.db.acquire():  # todo
            await self.bot.db.execute(query, rating1, ctx.author.id)
            await self.bot.db.execute(query, rating2, member.id)

            await self.bot.db.execute(new_hp_query, winner.cat.hp - 5, winner.id)
            await self.bot.db.execute(new_hp_query, loser.cat.hp - 5, loser.id)

        new_rating = f"\nNowy ranking\n{ctx.author.mention}: **{rating1}**.\n{member.mention}: **{rating2}**"
        await ctx.send(ctx.lang['win_fight'].format(winner.member_object.mention, loser.member_object.mention,
                                                    new_rating))

        self._fights.remove(enemy.id)
        self._fights.remove(ally.id)

    async def choose_weapon(self, ctx, member):
        author = await self.get_cat(ctx.author)
        enemy = await self.get_cat(member)

        author_weapon = None
        enemy_weapon = None

        def author_check(m):
            return m.channel == ctx.channel and m.author == ctx.author

        def enemy_check(m):
            return m.channel == ctx.channel and m.author == member

        while author_weapon is None:
            await ctx.send(ctx.lang['choose_weapon'].format(ctx.author.mention))

            try:
                message = await self.bot.wait_for('message', check=author_check, timeout=32)
            except asyncio.TimeoutError:
                return None, None

            weapon = message.content

            if weapon.lower() in ['cancel', 'anuluj']:
                return None, None

            if weapon not in ALL_WEAPONS:
                await ctx.send(ctx.lang['no_weapon_like'])

            if weapon not in author.inventory:
                await ctx.send(ctx.lang['no_weapon_in_inv'])

            else:
                author_weapon = weapon

        while enemy_weapon is None:
            await ctx.send(ctx.lang['choose_weapon'].format(member.mention))

            try:
                message = await self.bot.wait_for('message', check=enemy_check, timeout=32)
            except asyncio.TimeoutError:
                return None, None

            weapon = message.content

            if weapon in ['cancel', 'anuluj']:
                return await ctx.send(ctx.lang['abort'])

            if weapon not in ALL_WEAPONS:
                await ctx.send(ctx.lang['no_weapon_like'])

            if weapon not in enemy.inventory:
                await ctx.send(ctx.lang['no_weapon_in_inv'])

            else:
                enemy_weapon = weapon

        return author_weapon, enemy_weapon

    @staticmethod
    async def start_fight(channel, embed, enemy, author):
        enemy_weapon = Weapon(enemy.weapon)
        ally_weapon = Weapon(author.weapon)

        message = await channel.send(embed=embed)

        round_ = 1

        while True:
            enemy_weapon.shoot(author)
            if author.hp <= 0:
                winner = enemy
                loser = author
                break

            ally_weapon.shoot(enemy)
            if enemy.hp <= 0:
                winner = author
                loser = enemy
                break

            round_ += 1

            ally_hp = math.ceil(author.hp / 20) * '\U0001f7e5'
            enemy_hp = math.ceil(enemy.hp / 20) * '\U0001f7e5'

            text = f"{author.cat.name} ({author.member_object})\n**HP**: {ally_hp} ({round((author.hp/100)*100)}%)\n{enemy.cat.name} ({enemy.member_object})\n**HP:** {enemy_hp} ({round((enemy.hp/100)*100)}%)\n\nRunda: {round_}"

            embed.description = text
            await message.edit(embed=embed)

            await asyncio.sleep(4)

        ally_hp = math.ceil(author.hp / 20) * '\U0001f7e5'
        enemy_hp = math.ceil(enemy.hp / 20) * '\U0001f7e5'

        text = f"{author.cat.name} ({author.member_object})\n**HP**: {ally_hp} ({(author.hp/100)*100}%)\n{enemy.cat.name} ({enemy.member_object})\n**HP:** {enemy_hp} ({(enemy.hp/100)*100}%)\n\nRunda: {round_}"

        embed.description = text
        await message.edit(embed=embed)

        return winner, loser

    @cat.command()
    async def revive(self, ctx):
        cat = await self.bot.db.fetchrow("SELECT * FROM cats WHERE owner_id = $1", ctx.author.id)
        if not cat:
            raise commands.BadArgument(f"{ctx.author.mention}, {ctx.lang['no_cat'].format(ctx.prefix)}")

        cat = DefaultCat(self.bot, cat)

        if not cat.is_dead:
            return await ctx.send(ctx.lang['cat_alive'])
        n_money = round(cat.money / 2)
        await self.bot.db.execute(f"UPDATE cats SET food = 10, karma = 0, sta = 15, hp = 50, is_dead = false, "
                                  f"money = $1 WHERE owner_id = $2", n_money, ctx.author.id)
        await ctx.send(ctx.lang['revive_story'].format(cat.name, ctx.author))

    @cat.command()
    async def sleep(self, ctx):
        cat = await self.get_cat(ctx.author)
        if cat.stamina == 100 and cat.is_sleeping:
            await ctx.send(ctx.lang['regenered_sta'])
            await self.bot.db.execute('UPDATE cats SET is_sleeping = false, sleeping_time = 0 WHERE owner_id = $1',
                                      ctx.author.id)
        elif cat.stamina == 100 and not cat.is_sleeping:
            return await ctx.send(ctx.lang['max_energy'])

        if cat.is_sleeping:
            await ctx.send(ctx.lang['cat_woke_up'].format(cat.sleeping_time, cat.stamina))
            await self.bot.db.execute("UPDATE cats SET is_sleeping = false, sleeping_time = 0 WHERE owner_id = $1",
                                      ctx.author.id)

        elif not cat.is_sleeping:
            await self.bot.db.execute('UPDATE cats SET is_sleeping = true, sleeping_time = 0 WHERE owner_id = $1',
                                      ctx.author.id)
            await ctx.send(ctx.lang['started_restoring'].format(ctx.prefix))

    @cat.command()
    @commands.cooldown(1, 1800, commands.BucketType.user)
    async def rob(self, ctx, member: discord.Member):
        robber_cat = await self.get_cat(ctx.author)
        cat = await self.get_cat(member)

        if robber_cat.money < 150:
            return await ctx.send(ctx.lang['need_more_money_to_rob'])

        ratio = random.randint(10, 45)
        robbed = random.choice([True, False])

        if robbed:
            amount = int(cat.money) * (ratio / 100)
            async with self.bot.db.acquire():
                await self.bot.db.execute("UPDATE cats SET money = money - $1 WHERE owner_id = $2", amount, member.id)
                await self.bot.db.execute("UPDATE cats SET money = money + $1 WHERE owner_id = $2", amount, ctx.author.id)
            await ctx.send(ctx.lang['robbed'].format(ctx.author.mention, member.mention, amount, ctx.prefix))
        else:
            amount = int(robber_cat.money) * (ratio / 100)
            await self.bot.db.execute("UPDATE cats SET money = money - $1 WHERE owner_id = $2", amount, ctx.author.id)
            await ctx.send(ctx.lang['robbed_wrong'].format(ctx.author.mention, member.mention, amount))

    @cat.command(aliases=['dep'])
    async def deposit(self, ctx, money):
        cat = await self.get_cat(ctx.author)

        if money == "all":
            money = cat.money

        if money == "half":
            money = round(cat.money / 2)  # :)))

        if not str(money).isdigit():
            return await ctx.send(ctx.lang['not_correct_value'])

        money = int(money)

        if money > cat.money:
            return await ctx.send(ctx.lang['you_dont_have_so_much'])

        await self.bot.db.execute("UPDATE cats SET bank = bank + $1, money = money - $1 WHERE owner_id = $2",
                                  money, ctx.author.id)
        await ctx.send(ctx.lang['deposited'].format(money))

    @cat.command(aliases=['with'])
    async def withdraw(self, ctx, money):
        cat = await self.get_cat(ctx.author)

        if money == "all":
            money = cat.bank

        if money == "half":
            money = round(cat.bank / 2)  # :)))

        if not str(money).isdigit():
            return await ctx.send(ctx.lang['not_correct_value'])

        money = int(money)

        if money > cat.bank:
            return await ctx.send(ctx.lang['you_dont_have_so_much'])

        await self.bot.db.execute("UPDATE cats SET bank = bank - $1, money = money + $1 WHERE owner_id = $2",
                                  money, ctx.author.id)
        await ctx.send(ctx.lang['withdrawed'].format(money))

    @cat.command(aliases=['bal', 'bank'])
    async def balance(self, ctx, member: discord.Member = None):
        member = member or ctx.author

        cat = await self.get_cat(member)

        desc = f"{member.mention}\n\tBalance: **{cat.money}$**\nBank: **{cat.bank}$**"

        e = discord.Embed(description=desc, timestamp=ctx.message.created_at, color=self.bot.color)
        await ctx.send(embed=e)

    @cat.command(enabled=False)
    @commands.cooldown(1, 600, commands.BucketType.user)
    async def work(self, ctx):
        plus_sentences = [ctx.lang['plus_work_1'], ctx.lang['plus_work_2']]
        minus_sentences = [ctx.lang['minus_work_1'], ctx.lang['minus_work_2']]

        choice = random.choice([True, False])

        if choice:
            amount = random.randint(55, 170)
            sentence = random.choice(plus_sentences)
            query = "UPDATE cats SET money = money + $1 WHERE owner_id = $2"
        else:
            amount = random.randint(20, 120)
            sentence = random.choice(minus_sentences)
            query = "UPDATE cats SET money = money - $1 WHERE owner_id = $2"

        await self.bot.db.execute(query, amount, ctx.author.id)
        await ctx.send(sentence.format(amount))

    @cat.command()
    @commands.cooldown(1, 9, commands.BucketType.user)
    async def top(self, ctx, sort: str = "money"):
        sorts = ['money', 'bank', 'level', 'rating']
        if sort.lower() not in sorts:
            return await ctx.send(ctx.lang['wrong_type_of_sort'].format(', '.join(sorts)))

        desc = f"**TOP {ctx.lang['for_2']} `{sort}`**\n"

        cats = await self.bot.db.fetch(f"SELECT * FROM cats ORDER BY {sort.lower()} DESC LIMIT 10")

        for i, cat in enumerate(cats):

            ads_map = {
                "money": "$",
                "bank": "$",
                "level": " level",
                "rating": " rating",
            }

            desc += f"{cat['name']} ({cat['owner_id']}): **{cat[sort.lower()]}{ads_map[sort.lower()]}**\n"

        e = discord.Embed(description=desc, color=self.bot.color)
        await ctx.send(embed=e)

    @cat.command()
    @commands.cooldown(1, 2, commands.BucketType.user)
    async def use(self, ctx, *, item: ItemsConverter('useable')):
        cat = await self.get_cat(ctx.author)

        drinks_map = {
            "energy drink": "sta",
            "health drink": "hp",
            "food drink": "food"
        }

        if not cat.inventory:
            return await ctx.send(ctx.lang['empty_inventory'])

        if item not in self.cat.inventory:
            return await ctx.send(ctx.lang['no_thing_in_inventory'].format(item))

        if item in drinks_map:
            cat.inventory.remove(item)
            await self.bot.db.execute(f"UPDATE cats SET {drinks_map[item]} = 100, inventory = $1 WHERE owner_id = $2", cat.inventory, ctx.author.id)
            return await ctx.send(ctx.lang['fuel_drink'].format(drinks_map[item]))

        elif item in ['karma', 'food']:
            cat.inventory.remove(item)
            await self.bot.db.execute(f"UPDATE cats SET food = food + 15, karma = karma - 1 WHERE owner_id = $2", cat.inventory, ctx.author.id)
            return await ctx.send(ctx.lang['feed_cat_with_karma'].format(cat.food))

    @commands.Cog.listener()
    async def on_message(self, message):
        if message.author.bot:
            return

        cat = await self.bot.db.fetchrow("SELECT * FROM cats WHERE owner_id = $1", message.author.id)
        if not cat:
            return

        x = random.randint(0, 100)
        if x >= 20:
            exp = random.randint(5, 22)
            await self.bot.db.execute("UPDATE cats SET exp = $1 WHERE owner_id = $2", cat['exp'] + exp, cat['owner_id'])
        if await self.lvl_up(cat):

            language = await get_language(self.bot, message.guild)
            lang = self.bot.get_language_object(language)

            try:
                await message.channel.send(lang['level_up'].format(message.author.mention, cat['level'] + 1))
            except (discord.Forbidden, discord.HTTPException):
                return

def setup(bot):
    bot.add_cog(Cat(bot))