'''
Created on 15.04.2013

@author: capone
'''
import unittest
import os

from mock import MagicMock
from mock import patch
import mock
from crashtec.utils.exceptions import  CtGeneralError

from crashtec.utils.exceptions import CtBaseException
from crashtec.db.provider.routines import Record
from crashtec.infrastructure.public import definitions as infradefs
from crashtec.db.schema.fields import PRIMARY_KEY_FIELD

from crashtec.cdbprocessor import dbmodel
from crashtec.cdbprocessor import processor
from crashtec.cdbprocessor import resultparsers

import testconfig

TEST_DATA_FOLDER = 'test_data'
DUMP_FILE_NAME = os.path.join(os.path.dirname(__file__),
                                        TEST_DATA_FOLDER,
                                        "dump.dmp")

class Test01_CommandsHolder(unittest.TestCase):
    def test_positive_get_debugger_commands_for_task(self):
        task = Record()
        task[dbmodel.TASKS_PROBLEM_ID] =  infradefs.PROBLEMID_HANG
        commands_holder = processor.CdbCommandsHolder(testconfig)
        self.assertEqual(testconfig.HANG_COMMANDS_LIST,
                    commands_holder.get_debugger_commands_for_task(task),
                    "Wrong result for passed problem id"
                    )

    def test_negative_get_debugger_commands_for_task(self):
        task = Record()
        task[dbmodel.TASKS_PROBLEM_ID] = 'unknown_problem'
        commands_holder = processor.CdbCommandsHolder(testconfig)
        with self.assertRaises(CtBaseException):
                    commands_holder.get_debugger_commands_for_task(task)

class Test02_Debugger(unittest.TestCase):
    def test_positive_execute(self):
        debugger = processor.CdbDebugger(self.mock_config)
        task = Record()
        task[dbmodel.TASKS_PLATFORM_FIELD] = infradefs.PLATFORM_WIN32
        task[dbmodel.TASKS_DUMP_FILE_FIELD] = DUMP_FILE_NAME
        task[dbmodel.TASKS_SYMBOLS_PATH] = 'c:\\mock_path'
        result = debugger.execute(task, testconfig.HANG_COMMANDS_LIST)
        self.assertTrue(result, "Should not be empty")

    def test_negative_execute(self):
        debugger = processor.CdbDebugger(self.mock_config)
        task = Record()
        task[dbmodel.TASKS_PLATFORM_FIELD] = infradefs.PLATFORM_WIN32
        task[dbmodel.TASKS_DUMP_FILE_FIELD] = "z:\\unknown_dump.dmp"
        task[dbmodel.TASKS_SYMBOLS_PATH] = 'c:\\mock_path'
        with self.assertRaises(CtBaseException):
            debugger.execute(task, testconfig.HANG_COMMANDS_LIST)

    def setUp(self):
        self.mock_config = mock.MagicMock()
        self.mock_config.STANDARD_SYMBOLS_PATH = str()

_sample_task = {'id':1}
_sample_debugger_commnds = ['coommand1', 'command2']
_sample_debugger_output = 'sample output'
_sample_parsed_results = 'parsed_results'

class MockProcessorBuilder(object):
    def build_commands_holder(self):
        self.commands_holder = mock.create_autospec(
                            processor.CdbCommandsHolder(), spec_set = True)
        self.commands_holder.get_debugger_commands_for_task = MagicMock(
            return_value = _sample_debugger_commnds)
        return self

    def build_debugger(self):
        self.debugger =  mock.create_autospec(
                            processor.CdbDebugger(), spec_set = True)
        self.debugger.execute = MagicMock(
            return_value = _sample_debugger_output)
        return self

    def build_parser(self):
        self.parser =  mock.create_autospec(
                            resultparsers.create_parser(), spec_set = True)
        self.parser.parse_output = MagicMock(
                            return_value = _sample_parsed_results)
        return self

    def build_publisher(self):
        self.publisher =  mock.create_autospec(
                            processor.DbPublisher(), spec_set = True)
        self.publisher.publish_results = MagicMock()
        return self

    def create(self):
        impl = processor.Implementation(self.commands_holder,
                                        self.debugger,
                                        self.parser,
                                        self.publisher)
        return processor.Processor('class_type', 'instance_name',
                                   'group_id', impl)

class MockProcessorOneStepThrowsBuilder(MockProcessorBuilder):
    def build_publisher(self):
        self.publisher =  mock.create_autospec(
                            processor.DbPublisher(), spec_set = True)
        self.publisher.publish_results = MagicMock(
                        side_effect = CtGeneralError('Mock intended error'))
        return self

@patch('crashtec.infrastructure.public.agentbase.RegistrationHolder')
class Test03_Processor(unittest.TestCase):
    # Here we should not have any errors. It is just regular success path.
    def test_task_success(self, mock_class):
        # Setup mock's
        processor_instance = self.create_mock_processor(MockProcessorBuilder())

        processor_instance.task_failed = MagicMock(side_effect =
                                        RuntimeError("Should not be called"))

        # Call
        processor_instance.process_task(_sample_task)

        # Validate call's
        impl = processor_instance.impl
        impl.commands_holder.get_debugger_commands_for_task.\
                        assert_called_once_with(_sample_task)
        impl.debugger.execute.\
                        assert_called_once_with(_sample_task,
                                                _sample_debugger_commnds)
        impl.parser.parse_output.\
                        assert_called_once_with(_sample_debugger_output)
        impl.publisher.publish_results.\
                        assert_called_once_with(_sample_task,
                                                _sample_parsed_results)
        processor_instance.task_finished.assert_called_once_with(_sample_task)

    def test_task_failed(self, mock_class):
        # Setup mock's
        processor_instance = self.create_mock_processor(
                                        MockProcessorOneStepThrowsBuilder())

        processor_instance.task_finished = MagicMock(side_effect =
                                        RuntimeError("Should not be called"))

        # Call
        processor_instance.process_task(_sample_task)

        # Validate call's
        processor_instance.task_failed.assert_called_once_with(_sample_task)

    def create_mock_processor(self, builder):
        instance = builder.build_commands_holder().\
                    build_debugger().\
                    build_parser().\
                    build_publisher().\
                    create()
        instance.task_failed = MagicMock()
        instance.task_finished = MagicMock()
        return instance

def _get_sample_signature():
    return resultparsers.CrashSignature('problem_class',
                                        'image_name',
                                        'symbol_name',
                                        'failure_bucket_id')

@patch('crashtec.db.provider.routines.create_new_record')
class Test04_DbPublisher(unittest.TestCase):
    def test_publish_results(self, mock_create_new_record):
        # Prepare samnple data
        task = Record()
        d = dbmodel
        task[PRIMARY_KEY_FIELD] = 212
        SAMPLE_DBG_OUTPUT = 'sample_output'

        # Call publisher
        publisher = processor.DbPublisher()
        r = resultparsers
        publisher.publish_results(task, [
                        r.CrashSignatureParserResults(_get_sample_signature()),
                        r.RawOutputParserResults(SAMPLE_DBG_OUTPUT)])

        # Validate publisher behavior
        s = _get_sample_signature()
        self.assertEqual(task[d.TASKS_PROBLEM_CLASS], s.problem_class)
        self.assertEqual(task[d.TASKS_SYMBOL_NAME], s.symbol_name)
        self.assertEqual(task[d.TASKS_FAIL_IMAGE], s.image_name)
        self.assertEqual(task[d.TASKS_FAILURE_BUCKET_ID], s.failure_bucket_id)

        new_record = Record()
        new_record[d.RAWRESULTS_TASK_ID] = 212
        new_record[d.RAWRESULTS_DBG_OUTPUT] = SAMPLE_DBG_OUTPUT
        mock_create_new_record.assert_called_once_with(d.RAWRESULTS_TABLE,
                                                       new_record)

if __name__ == "__main__":
    #import sys;sys.argv = ['', 'Test.testName']e
    unittest.main()


import shutil
import tempfile

from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.cache import cache
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import Client, TestCase, override_settings
from django.urls import reverse
from django import forms

from ..models import Group, Post, Follow

TEMP_MEDIA_ROOT = tempfile.mkdtemp(dir=settings.BASE_DIR)

User = get_user_model()

@override_settings(MEDIA_ROOT=TEMP_MEDIA_ROOT)
class ViewTests(TestCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()

        cls.USER_NAME = 'test_user'
        cls.GROUP_TITLE = 'Тестовая группа'
        cls.GROUP_SLUG = 'test_group_slug'
        cls.GROUP_2_SLUG = 'test_group_slug_2'

        cls.POST_TEXT = 'Тестовый пост'

        cls.user = User.objects.create_user(username=cls.USER_NAME)
        cls.followed_user = User.objects.create_user(username='follower')

        cls.group = Group.objects.create(
            title='Тестовая группа',
            slug=cls.GROUP_SLUG,
            description='Тестовое описание',
        )
        cls.group2 = Group.objects.create(
            title='Тестовая группа 2',
            slug=cls.GROUP_2_SLUG,
            description='Тестовое описание 2',
        )

        cls.small_gif = (
            b'\x47\x49\x46\x38\x39\x61\x02\x00'
            b'\x01\x00\x80\x00\x00\x00\x00\x00'
            b'\xFF\xFF\xFF\x21\xF9\x04\x00\x00'
            b'\x00\x00\x00\x2C\x00\x00\x00\x00'
            b'\x02\x00\x01\x00\x00\x02\x02\x0C'
            b'\x0A\x00\x3B'
        )
        cls.uploaded = SimpleUploadedFile(
            name='small.gif',
            content=cls.small_gif,
            content_type='image/gif'
        )

        cls.post = Post.objects.create(
            author=cls.user,
            text=cls.POST_TEXT,
            group=cls.group,
            image=SimpleUploadedFile(
                name='small.gif',
                content=cls.small_gif,
                content_type='image/gif'
            )
        )
        cls.image = cls.post.image
        cls.post_id = cls.post.id

        cls.follow = Follow.objects.create(
            user=cls.followed_user,
            author=cls.user,
        )

    @classmethod
    def tearDownClass(cls):
        super().tearDownClass()
        shutil.rmtree(TEMP_MEDIA_ROOT, ignore_errors=True)

    def setUp(self):
        self.authorized_client = Client()
        self.authorized_client.force_login(self.user)

    def post_obj_test_correct_context(self, post_obj):
        self.assertEqual(post_obj.pk, self.post_id)
        self.assertEqual(post_obj.author, self.user)
        self.assertEqual(post_obj.text, self.POST_TEXT)
        self.assertEqual(post_obj.group, self.group)
        self.assertEqual(post_obj.image, self.image)

    def test_pages_uses_correct_template(self):
        templates_pages_names = {
            reverse('posts:index'): 'posts/index.html',
            reverse(
                'posts:group_list',
                kwargs={'slug': self.GROUP_SLUG}
            ): 'posts/group_list.html',
            reverse(
                'posts:profile',
                kwargs={'username': self.USER_NAME}
            ): 'posts/profile.html',
            reverse(
                'posts:post_detail',
                kwargs={'post_id': self.post_id}
            ): 'posts/post_detail.html',
            reverse(
                'posts:post_edit',
                kwargs={'post_id': self.post_id}
            ): 'posts/create_post.html',
            reverse('posts:post_create'): 'posts/create_post.html',
        }
        for reverse_name, template in templates_pages_names.items():
            with self.subTest(reverse_name=reverse_name):
                response = self.authorized_client.get(reverse_name)
                self.assertTemplateUsed(response, template)

    def test_index_show_correct_context(self):
        response = self.authorized_client.get(reverse('posts:index'))
        self.assertEqual(
            response.context.get('title'),
            'Последние обновления на сайте'
        )
        self.assertEqual(response.context.get('index'), True)
        self.post_obj_test_correct_context(response.context.get('page_obj')[0])

    def test_group_show_correct_context(self):
        response = self.authorized_client.get(
            reverse('posts:group_list', kwargs={'slug': self.GROUP_SLUG})
        )

        self.assertEqual(
            response.context.get('title'),
            f'Записи сообщества "{self.GROUP_TITLE}"'
        )
        self.assertEqual(response.context.get('group'), self.group)
        self.post_obj_test_correct_context(response.context.get('page_obj')[0])

    def test_group_show_own_posts(self):
        """ Проверка, что на страницу группы без постов не попали посты """
        response = self.authorized_client.get(
            reverse('posts:group_list', kwargs={'slug': self.GROUP_2_SLUG})
        )

        self.assertEqual(len(response.context.get('page_obj')), 0)

    def test_profile_show_correct_context(self):
        response = self.authorized_client.get(
            reverse('posts:profile', kwargs={'username': self.user.username})
        )

        self.assertEqual(response.context.get('author'), self.user)
        self.assertEqual(response.context.get('author_posts_count'), 1)
        self.post_obj_test_correct_context(response.context.get('page_obj')[0])

    def test_post_detail_show_correct_context(self):
        response = self.authorized_client.get(
            reverse('posts:post_detail', kwargs={'post_id': self.post_id})
        )

        self.post_obj_test_correct_context(response.context.get('post'))
        self.assertEqual(response.context.get('author_posts_count'), 1)

    def test_post_edit_show_correct_context(self):
        response = self.authorized_client.get(
            reverse('posts:post_edit', kwargs={'post_id': self.post_id})
        )

        form_fields = {
            'text': forms.fields.CharField,
            'group': forms.fields.ChoiceField,
        }

        for value, expected in form_fields.items():
            with self.subTest(value=value):
                form_field = response.context.get('form').fields.get(value)
                self.assertIsInstance(form_field, expected)

    def test_post_create_show_correct_context(self):
        response = self.authorized_client.get(
            reverse('posts:post_create')
        )

        form_fields = {
            'text': forms.fields.CharField,
            'group': forms.fields.ChoiceField,
        }

        for value, expected in form_fields.items():
            with self.subTest(value=value):
                form_field = response.context.get('form').fields.get(value)
                self.assertIsInstance(form_field, expected)

    def test_follow_index_follower_client(self):
        follower_client = Client()
        follower_client.force_login(self.followed_user)
        response = follower_client.get(reverse(
            'posts:follow_index'
        ))
        follower_posts_count = len(response.context['page_obj'])
        self.assertEqual(follower_posts_count, 1)

    def test_follow_index_not_follower_client(self):
        response = self.authorized_client.get(reverse(
            'posts:follow_index'
        ))
        follower_posts_count = len(response.context['page_obj'])
        self.assertEqual(follower_posts_count, 0)

    def test_follow(self):
        followed_user = User.objects.create_user(username='follow_user')
        followed_client = Client()
        followed_client.force_login(followed_user)

        follow_count = Follow.objects.count()

        response = followed_client.post(
            reverse('posts:profile_follow', kwargs={'username': self.user}),
        )
        self.assertRedirects(
            response,
            reverse('posts:profile', kwargs={'username': self.user})
        )
        self.assertEqual(Follow.objects.count(), follow_count + 1)
        self.assertEqual(Follow.objects.last().user, followed_user)
        self.assertEqual(Follow.objects.last().author, self.user)

    def test_unfollow(self):
        followed_user = User.objects.create_user(username='unfollow_user')
        followed_client = Client()
        followed_client.force_login(followed_user)

        follow = Follow.objects.create(
            user=followed_user,
            author=self.user,
        )
        follow_id = follow.id
        follow_count = Follow.objects.count()

        response = followed_client.post(
            reverse('posts:profile_unfollow', kwargs={'username': self.user}),
        )
        self.assertRedirects(
            response,
            reverse('posts:profile', kwargs={'username': self.user})
        )

        self.assertEqual(Follow.objects.count(), follow_count - 1)
        self.assertFalse(Follow.objects.filter(pk=follow_id).exists())

class CacheTests(TestCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()

        cls.USER_NAME = 'test_user'
        cls.GROUP_TITLE = 'Тестовая группа'
        cls.GROUP_SLUG = 'test_group_slug'
        cls.POST_TEXT = 'Тестовый пост'

        cls.user = User.objects.create_user(username=cls.USER_NAME)

        cls.group = Group.objects.create(
            title='Тестовая группа',
            slug=cls.GROUP_SLUG,
            description='Тестовое описание',
        )

        cls.post = Post.objects.create(
            author=cls.user,
            text=cls.POST_TEXT,
            group=cls.group,
        )
        cls.image = cls.post.image
        cls.post_id = cls.post.id
        cache.clear()

    def setUp(self):
        self.authorized_client = Client()
        self.authorized_client.force_login(self.user)

    def test_index_correct_cache(self):
        response = self.authorized_client.get(reverse('posts:index'))
        post_obj = response.context.get('page_obj')[0]
        self.assertEqual(post_obj.pk, self.post_id)
        cache_check = response.content
        Post.objects.get(pk=self.post_id).delete()
        response = self.authorized_client.get(reverse('posts:index'))
        self.assertEqual(response.content, cache_check)
        cache.clear()
        response = self.authorized_client.get(reverse('posts:index'))
        self.assertNotEqual(response.content, cache_check)

class PaginatorViewsTest(TestCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()

        cls.POST_COUNT = 13
        cls.POSTS_ON_PAGE = 10
        cls.GROUP_SLUG = 'test_group_slug'

        cls.user = User.objects.create_user(username='test_user')

        cls.group = Group.objects.create(
            title='Тестовая группа',
            slug=cls.GROUP_SLUG,
            description='Тестовое описание',
        )

        for i in range(cls.POST_COUNT):
            cls.post = Post.objects.create(
                author=cls.user,
                text=f'Тестовый пост {i}',
                group=cls.group
            )
        cache.clear()

    def test_index_paginator_first_page(self):
        response = self.client.get(reverse('posts:index'))
        self.assertEqual(len(response.context['page_obj']), self.POSTS_ON_PAGE)

    def test_index_paginator_second_page(self):
        response = self.client.get(reverse('posts:index') + '?page=2')
        self.assertEqual(
            len(response.context['page_obj']),
            self.POST_COUNT - self.POSTS_ON_PAGE
        )

    def test_group_paginator_first_page(self):
        response = self.client.get(
            reverse('posts:group_list', kwargs={'slug': self.GROUP_SLUG})
        )
        self.assertEqual(len(response.context['page_obj']), self.POSTS_ON_PAGE)

    def test_group_paginator_second_page(self):
        response = self.client.get(
            reverse('posts:group_list', kwargs={'slug': self.GROUP_SLUG})
            + '?page=2'
        )
        self.assertEqual(
            len(response.context['page_obj']),
            self.POST_COUNT - self.POSTS_ON_PAGE
        )

    def test_profile_paginator_first_page(self):
        response = self.client.get(
            reverse('posts:profile', kwargs={'username': self.user.username})
        )
        self.assertEqual(len(response.context['page_obj']), self.POSTS_ON_PAGE)

    def test_profile_paginator_second_page(self):
        response = self.client.get(
            reverse('posts:profile', kwargs={'username': self.user.username})
            + '?page=2'
        )
        self.assertEqual(
            len(response.context['page_obj']),
            self.POST_COUNT - self.POSTS_ON_PAGE
        )


""" Python 3 compatibility tools. """
from __future__ import division, print_function
import itertools
import sys

if sys.version_info[0] < 3:
  input = raw_input
  range = xrange

  filter = itertools.ifilter
  map = itertools.imap
  zip = itertools.izip

def input1(type=int):
  return type(input())

def input2(type=int):
  [a, b] = list(map(type, input().split()))
  return a, b

def input3(type=int):
  [a, b, c] = list(map(type, input().split()))
  return a, b, c

def input_array(type=int):
  return list(map(type, input().split()))

def input_string():
  s = input()
  return list(s)

def main():
  [a, b, c, d] = input_array()
  res = 0
  for i in range(a, b+1):
    nb = b + i + 1
    nc = c + i + 1

    l = max(c, nb)
    r = min(d, nc)

    print(nb, nc, l, r)

    if l <= r:
      res = r - l + 1

  print(res)

if __name__ == '__main__':
  main()


import json
import logging

from django.shortcuts import render
from django.utils import timezone
from dynaconf import settings as dynasettings
from ipware import get_client_ip
from rest_framework import mixins, viewsets, status, views
from rest_framework.response import Response

from common.auth.wechat.web_auth import WeChantWebAuthHandler
from common.model_manager.account_manager import AccountManager
from common.model_manager.marketer_manager import MarketerModelManager
from common.model_manager.transfer_record_manager import TransferRecordManager
from common.msg_service.sms_send import SendMessageApi, VerifySmsCode
from config import PAY_CHANNELS
from marketer.config import PHONE_CODE_STATUS, AREA_STATUS_CODE, \
    UNIONID_LIMIT, MAX_REQUEST_ONE_DAY, ALIPAY_VERIFY_TRANSFER_AMOUNT
from marketer.model_manager import AreaModelManager
from marketer.serializers.account import MarketerWithdrawSerializer
from marketer.serializers.marketer import CreateMarketerBaseSerializer, \
    UpdateMarketerSerializer
from marketer.serializers.nested_serializers import MarketerLoginSerializer
from marketer.utils.auth import MarketerRegisterAuthentication, \
    MarketerAuthentication
from marketer.utils.redis_utils import RedisUtil

ENV = dynasettings.ENV

dynasettings.configure()
dynasettings.namespace('PAYUNION')
app_id = dynasettings.SUBSCRIPTION_ACCOUNT_APP_ID_MARKETER if ENV != 'dev' else dynasettings.SUBSCRIPTION_ACCOUNT_APP_ID_USER
app_secret = dynasettings.SUBSCRIPTION_ACCOUNT_SECRET_MARKETER if ENV != 'dev' else dynasettings.SUBSCRIPTION_ACCOUNT_SECRET_USER
logger = logging.getLogger(__file__)

class MarketerLoginView(views.APIView):
    """
    登陆
    """

    def post(self, request):
        serializer = MarketerLoginSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        return Response(
            status=status.HTTP_200_OK,
            data=dict(token=serializer.token)
        )

class MarketerRegisterViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
    """
    create:
        Register marketer
    """
    serializer_class = CreateMarketerBaseSerializer
    authentication_classes = [MarketerRegisterAuthentication]

class MarketerWithdrawViewSet(viewsets.GenericViewSet):
    """ 邀请人/业务员 账户余额提现 """

    authentication_classes = [MarketerAuthentication]

    @staticmethod
    def withdraw(request):
        """ 提现 """
        marketer = request.user
        client_ip, _ = get_client_ip(request._request)

        logger.info(
            f'merchant({marketer.id, marketer.name, client_ip} '
            f'requested a withdraw: {request.data}')

        serializer = MarketerWithdrawSerializer(marketer.account,
                                                data=request.data,
                                                partial=True)
        serializer.is_valid(raise_exception=True)

        manager = AccountManager(marketer.account)

        if request.data['channel'] == PAY_CHANNELS.WECHAT:
            withdraw_error = manager.wechat_withdraw(
                serializer.validated_data['amount'],
                client_ip,
                dynasettings.SUBSCRIPTION_ACCOUNT_APP_ID_MARKETER
            )
        else:
            withdraw_error = manager.alipay_withdraw(
                serializer.validated_data['amount'])

        logger.info(
            f'marketer ({marketer.id, marketer.name, client_ip} '
            f'withdraw error is {withdraw_error}')

        return Response(status=status.HTTP_200_OK, data=withdraw_error or {})

class CheckAreaMarketerView(views.APIView):
    """验证区域是否存在业务员"""

    def get(self, request):
        adcode = self.request.query_params.get('adcode')
        manager = AreaModelManager()
        res = manager.has_marketer(adcode=adcode)
        if res:
            if res == 'DISABLE':
                return Response(status=status.HTTP_200_OK,
                                data=dict(code=AREA_STATUS_CODE['DISABLE'],
                                          message='未开放该区域'))
            else:
                return Response(status=status.HTTP_200_OK,
                                data=dict(code=AREA_STATUS_CODE['HAS_MARKETER'],
                                          message='该区域有业务员'))
        else:
            return Response(status=status.HTTP_200_OK,
                            data=dict(code=AREA_STATUS_CODE['NO_MARKETER'],
                                      message='该区域没业务员'))

class GetUserInfoView(views.APIView):
    """获取用户基本信息"""
    authentication_classes = [MarketerAuthentication]

    def get(self, request):
        user = self.request.user
        if user:
            manager = MarketerModelManager()
            user_info = manager.get_user_info(self.request.user.wechat_unionid)
            return Response(status=status.HTTP_200_OK, data=user_info)
        else:
            return Response(status=status.HTTP_200_OK, data=dict(
                user_name=None,
                user_phone=None,
                user_status='not_register',
                user_type=None,
                using_invited_merchants_num=0,
                reviewing_invited_merchants_num=0,
                total_bonus=0,
                user_withdrawable_balance=0,
                alipay_withdraw_balance=0,
                wechat_withdraw_balance=0
            ))

class MarketerSendMessageViewSet(viewsets.ViewSet):
    """ 邀请人、业务员 发短信 """
    authentication_classes = [MarketerRegisterAuthentication]

    def msg_send(self, request):
        # 发送短信验证码
        unionid = request.user.get('unionid')
        phone = request.data.get('phone')
        send_msg_api = SendMessageApi(phone=phone, wechat_unionid=unionid)
        result = send_msg_api.message_send()
        return Response(status=status.HTTP_200_OK, data=result)

class AuthenticatedMarketerSendMessageViewSet(viewsets.ViewSet):
    authentication_classes = [MarketerAuthentication]

    def msg_send(self, request):
        # 发送短信验证码
        user = request.user
        phone = request.data.get('phone')
        send_msg_api = SendMessageApi(phone=phone,
                                      wechat_unionid=user.wechat_unionid)
        result = send_msg_api.message_send()
        return Response(status=status.HTTP_200_OK, data=result)

class VerifySmsCodeViewSet(viewsets.ViewSet):
    """ 验证短信验证码 """
    authentication_classes = [MarketerAuthentication]

    def verfy_sms_code(self, request):
        phone = request.data.get('phone')
        verify_code = request.data.get('verify_code')
        verify_instace = VerifySmsCode(phone=phone, verify_code=verify_code)
        data = {'code': 0, 'message': "短信验证成功"}
        if not verify_instace.verify():
            data = {'code': -1, 'message': "验证码输入错误，请重新输入"}
        return Response(status=status.HTTP_200_OK, data=data)

class GetMarketerWechatInfo(viewsets.ViewSet):
    """
    微信授权回调 接收wechat code 根据code获取 access_token
    1、授权成功，先创建业务员／邀请人
    2、网页展示授权状态
    """

    def wechat_auth_redirect(self, request):
        code = request.query_params.get('code', '')

        wechat_auth_handler = WeChantWebAuthHandler(app_id=app_id,
                                                    app_secret=app_secret)
        access_token, openid = wechat_auth_handler.gen_access_token(code=code)
        user_info = wechat_auth_handler.gen_user_info(access_token, openid)
        if 'openid' not in user_info:
            user_info['openid'] = openid

        if not user_info.get('unionid'):
            return render(request, 'auth_web/auth_marketer_error.html',
                          {'title': "注册业务员", 'info': "授权异常"})
        if MarketerModelManager.check_marketer_exists(
                unionid=user_info['unionid']):
            logger.info(f"{user_info['nickname']} 该邀请人已经注册")
            return render(request, 'auth_web/auth_marketer_error.html',
                          {'title': "注册业务员", 'info': "该邀请人已经注册"})

        # marketer还没有注册 缓存openid, 昵称，缓存头像
        _user_info = json.dumps(user_info)
        RedisUtil.cache_data(user_info['unionid'], _user_info, data_type='wechat_info')

        return render(request, 'auth_web/auth_marketer.html',
                      {'title': "注册业务员", 'nickname': user_info['nickname']})

class UpdateMarketerInfoView(views.APIView):
    authentication_classes = [MarketerAuthentication]

    def put(self, requeest):
        serializer = UpdateMarketerSerializer(data=self.request.data)
        serializer.is_valid()
        serializer.update(instance=self.request.user,
                          validated_data=serializer.validated_data)
        return Response(status=status.HTTP_202_ACCEPTED,
                        data=serializer.validated_data)

class CheckPhoneExistView(views.APIView):
    def get(self, request):
        manager = MarketerModelManager()
        res = manager.check_phone_exist(self.request.query_params.get('phone'))
        if res:
            data = dict(code=PHONE_CODE_STATUS['EXIST'], message='手机已存在')
        else:
            data = dict(code=PHONE_CODE_STATUS['CAN_USE'], message='可以使用')
        return Response(status=status.HTTP_200_OK, data=data)

class CheckAlipayAccountView(viewsets.ViewSet):
    """ 验证支付宝账号名字是否正确 """
    authentication_classes = [MarketerRegisterAuthentication]

    API_CODE = {
        'SUCCESS_RECORD_NOT_EXIST': -5,
        'PARAMS_ERROR': -4,
        'REQUEST_FREQUENTLY': -3,
        'REQUEST_TOO_MANY': -2,
        'TRANSFER_FAILED': -1,
        'TRANSFER_FINISHED': 0,
        'SUCCESS_RECORD_EXIST': 1
    }

    def show_success_transfer_record(self, request):
        """ 根据unionid查询记录 """
        unionid = request.user.get('unionid')
        res = TransferRecordManager.check_transfer_success(unionid=unionid)
        data = dict(code=self.API_CODE['SUCCESS_RECORD_NOT_EXIST'],
                    message="没有与该unionid对应的成功记录",
                    alipay_data=res['record'])
        if res['exist']:
            data.update(
                code=self.API_CODE['SUCCESS_RECORD_EXIST'],
                message="存在转账成功记录",
                alipay_data=res['record'])
        return Response(status=status.HTTP_200_OK, data=data)

    def check_alipay_account(self, request):
        now = timezone.now()
        timestamp = now.timestamp()
        record_key = now.strftime('%Y_%m_%d')
        unionid = request.user.get('unionid')
        account_number = request.data.get('account_number')
        account_name = request.data.get('account_name')

        # 验证账号、名字不为空
        if not all([account_number, account_name]):
            return Response(status=status.HTTP_200_OK, data=dict(
                code=self.API_CODE['PARAMS_ERROR'],
                message="支付宝账号和姓名必填"
            ))

        # 验证当前接口请求次数
        if RedisUtil.record_request_one_day(record_key) > MAX_REQUEST_ONE_DAY:
            return Response(status=status.HTTP_200_OK, data=dict(
                code=self.API_CODE['REQUEST_TOO_MANY'],
                message="接口服务已达到单日请求上限"
            ))

        # 验证当前微信号请求转账频率
        list_len = RedisUtil.get_request_time_len(unionid=unionid)
        if list_len and list_len >= UNIONID_LIMIT['max_times']:
            oldest_time = RedisUtil.get_oldest_item(unionid=unionid)
            if timestamp < float(oldest_time) + UNIONID_LIMIT['limit_seconds']:
                data = dict(
                    code=self.API_CODE['REQUEST_FREQUENTLY'],
                    message=f"过去7天内已达到上限{UNIONID_LIMIT['max_times']}次"
                )
                return Response(status=status.HTTP_200_OK, data=data)

        # 验证该微信号、支付宝账号、姓名是否有成功转账记录
        check_data = TransferRecordManager.check_transfer_success(
            unionid=unionid,
            account_number=account_number,
            account_name=account_name)

        if check_data['exist']:
            data = dict(code=self.API_CODE['SUCCESS_RECORD_EXIST'],
                        message="存在转账成功记录",
                        alipay_data=check_data['record'])
            return Response(status=status.HTTP_200_OK, data=data)

        # 转账并记录
        errors = TransferRecordManager.transfer_to_account(
            unionid=unionid,
            account_number=account_number,
            account_name=account_name,
            amount=ALIPAY_VERIFY_TRANSFER_AMOUNT
        )

        data = dict(
            code=self.API_CODE['TRANSFER_FAILED'],
            message="支付宝账号或姓名错误",
            alipay_data={}
        )

        if not errors:
            RedisUtil.record_request_one_day(record_key, num_incr=True)
            if list_len + 1 > UNIONID_LIMIT['max_times']:
                RedisUtil.get_oldest_item(unionid=unionid, pop_item=True)
            RedisUtil.cache_request_time(unionid=unionid, time=timestamp)
            data.update(
                code=self.API_CODE['TRANSFER_FINISHED'],
                message="支付宝信息验证成功",
                alipay_data=dict(account_number=account_number,
                                 account_name=account_name)
            )

        return Response(status=status.HTTP_200_OK, data=data)

marketer_withdraw = MarketerWithdrawViewSet.as_view({
    'put': 'withdraw',
})
marketer_msg_send = MarketerSendMessageViewSet.as_view({
    'post': 'msg_send',
})
authenticated_marketer_msg_send = AuthenticatedMarketerSendMessageViewSet.as_view({
    'post': 'msg_send',
})
marketer_msg_verify = VerifySmsCodeViewSet.as_view({
    'post': 'verfy_sms_code',
})
get_marketer_wechat_info = GetMarketerWechatInfo.as_view({
    'get': 'wechat_auth_redirect',
})
check_alipay_account = CheckAlipayAccountView.as_view({
    'get': 'show_success_transfer_record',
    'post': 'check_alipay_account',
})


# -*- coding=utf-8 -*-
"""
Created by lichen on 16/12/13.
技能在渔场中释放
"""

import time
import random
import math
import json

import freetime.util.log as ftlog
from poker.entity.biz import bireport
from poker.entity.dao import gamedata, daobase
from newfish.entity import config, util, weakdata
from newfish.entity.config import FISH_GAMEID
from newfish.entity.redis_keys import GameData, UserData, WeakData
from hall.entity import hallranking

class SkillBase(object):

    def __init__(self, table, player, skillId, skillState, skillStar, skillGrade, skillType=0):
        self.table = table
        self.player = player
        self.skillId = skillId
        self.skillState = skillState                        # 技能状态
        self.skillStar = skillStar                          # 技能星级
        self.skillGrade = skillGrade                        # 技能等级
        self.skillGrade = self.getRealEffectSkillGrade()    # 玩家在欢乐竞技和王者争霸中夺冠，则下次比赛中使用该技能降级
        self.skillType = skillType                          # 技能类型（0:主技能 1:辅助技能）
        self.skillMode = self.table.gameMode                # 0:经典 1:千炮
        self.position = {}                                  # [bulletId] = [fPosx, fPosy]
        self.initData()                                     # 初始化技能数据
        self.initState()                                    # 初始化技能状态

    def initLevel(self):
        """
        初始化等级属性数据
        """
        skillGradeConf = config.getSkillGradeConf(self.skillId, self.skillGrade, self.skillMode)
        self.cost = skillGradeConf.get("cost", 0)
        self.power = skillGradeConf.get("power", 0)
        self.HP = skillGradeConf.get("HP", 0)
        self.impale = skillGradeConf.get("impale", 0)
        self.clip = skillGradeConf.get("clip", 0)
        self.interval = skillGradeConf.get("interval", 0)
        self.duration = skillGradeConf.get("duration", 0)
        self.coolDown = skillGradeConf.get("coolDown", 0)
        self.range = skillGradeConf.get("range", 0)
        self.weaponId = skillGradeConf.get("weaponId", 0)
        self.double = skillGradeConf.get("double", [])
        self.isReturn = skillGradeConf.get("isReturn", 0)

    def initStar(self):
        """
        初始化星级属性数据
        """
        self.fatal = 0
        self.distance = False
        self.impaleCatch = False
        self.gunSkinMultiple = 1
        self.dayFreeMaxMulti = 0
        for skillStar in xrange(1, self.skillStar + 1):
            skillStarConf = config.getSkillStarConf(self.skillId, skillStar, self.skillMode)
            for abilityConf in skillStarConf["abilities"]:
                ability = abilityConf.get("ability", 0)
                value = abilityConf.get("value", 0)
                if ability == 1:                                # 减少冷却时间
                    self.coolDown -= value
                elif ability == 2:                              # 提高威力
                    self.power += self.power * value / 100
                elif ability == 3:                              # 增加技能效果时间
                    self.duration += value
                elif ability == 6:                              # 增加子弹数
                    self.clip += value
                elif ability == 7:                              # 增加机器人HP
                    self.HP += self.HP * value / 100
                elif ability == 8:                              # 提高对远距离鱼的威力
                    self.distance = True
                elif ability == 9:                              # 冰锥HP在65%以上时无法一击致命
                    self.fatal += value / 100
                elif ability == 10:                             # 被贯穿的鱼必被捕获
                    self.impaleCatch = True
                elif ability == 13:                             # 提升火蛇加农炮喷射距离
                    self.range += self.range * value / 100
                elif ability == 25:                             # 每天可在最高[var]倍下免费使用1次
                    self.dayFreeMaxMulti = value
        if self.table.typeName in config.NORMAL_ROOM_TYPE:      # 新手/普通/好友渔场-皮肤炮特性，技能双倍消耗/收益
            self.gunSkinMultiple = 2 if self.player.gunId in self.double else 1
            if self.table.typeName == config.FISH_GRAND_PRIX and self.player.isGrandPrixMode():  # 大奖赛冷却减半
                self.coolDown //= 2
                if self.skillType == 1:
                    self.cost = 0
        elif self.table.typeName in [config.FISH_TIME_MATCH, config.FISH_TIME_POINT_MATCH]: # 回馈赛-弹药消耗为0，冷却减半
            self.cost = 0
        elif self.table.typeName == config.FISH_FIGHT:          # 渔友竞技-冷却减半
            self.coolDown //= 2
        self.coolDown = max(self.coolDown, 1)

    def initCommonData(self):
        """
        初始化公共数据
        """
        self.fpMultiple = self.player.fpMultiple
        self.skillMultiple = 1
        # 格林机关枪除回馈赛外收益翻倍
        if self.skillId == 5110 and self.table.typeName != config.FISH_TIME_POINT_MATCH:
            self.skillMultiple = 2
        self.gunSkinMultiple *= self.skillMultiple
        self.gunX = util.getGunX(self.player.nowGunLevel, self.skillMode)
        self.powerRate = 1                                  # 技能威力倍率
        self.durationEndTime = 0                            # 技能效果结束时间
        self.skillFishes = []                               # 技能召唤的鱼(捕鱼机器人)
        self.originClip = self.clip                         # 技能原始总子弹数
        self.originEnergy = 0                               # 技能单发原始能量
        self.energy = 0                                     # 技能单发扣减能量
        self.totalPower = 0                                 # 技能总威力（无加成）
        self.originRealPower = 0                            # 技能总威力（加能量）
        self.deductionHP = 0                                # 技能扣减HP
        self.consumePower = 0                               # 技能曲线消耗能量
        self.onceCostCoin = self.getCost / float(self.originClip) * self.fpMultiple  # 技能单发价值金币数
        # 新手期间技能使用次数
        _key = UserData.newbieUseSkillTimes % (FISH_GAMEID, self.player.userId)
        self.newbieUseTimes = daobase.executeUserCmd(self.player.userId, "HGET", _key, str(self.skillId)) or 0

    def initData(self):
        """
        初始化技能数据
        """
        self.initLevel()
        self.initStar()
        self.initCommonData()

    def initState(self):
        """
        初始化技能状态
        """
        self.state = 0                                      # 0:未使用 1:装备中 2:使用中
        self.cdStartTime = int(time.time())                 # 技能冷却开始时的时间
        self.usingDayFree = False                           # 是否每日免费装备中

    def use(self, select):
        """
        选中取消技能
        """
        eventId = None
        orgState = self.state
        if select:
            if self.state == 0:
                if self.player.gchgTimer:
                    self.player.gchgTimer.cancel()
                self.state = 1
                self.clear()
                self.initData()
                if self.checkIfDayFree():
                    # 每日免费使用
                    self.doDayFreeUse()
                    self.usingDayFree = True
                else:
                    self.player.costClip(self.getCost, "BI_NFISH_USE_SKILL_%d" % self.skillId)
                self.player.addUsingSkill(self.skillId, self.skillType)
                eventId = "BI_NFISH_GE_SKILL_USE"
        else:
            if self.state == 1:
                if self.usingDayFree:
                    self.cancelDayFreeUse()
                else:
                    self.player.addClip(self.getCost, 0, self.skillId)
                self.state = 0
                self.player.removeUsingSkill(self.skillId, self.skillType)
                eventId = "BI_NFISH_GE_SKILL_CANCEL"
        self.table.broadcastSkillUse(self, select, self.player.userId, orgState)
        if config.LOCK_KINDID in self.player.skill_item:
            self.player.skill_item[config.LOCK_KINDID].pause_and_continue_time(config.LOCK_KINDID)
        self.player.gunEffectState(2)
        if eventId:
            bireport.reportGameEvent(eventId, self.player.userId, FISH_GAMEID, self.table.roomId,  self.table.tableId,
                                     int(self.skillId), self.player.level, 1 if self.usingDayFree else 0, 0, [], self.player.clientId)
            ftlog.info(
                "skill_release->use",
                "userId =", self.player.userId,
                "eventId =", eventId,
                "skillId =", self.skillId,
                "skillStar =", self.skillStar,
                "skillGrade =", self.skillGrade,
                "skillType =", self.skillType,
                "gunId =", self.player.gunId,
                "nowGunLevel =", self.player.nowGunLevel,
                "isDayFree =", self.usingDayFree
            )

    def end(self):
        """
        结束技能
        """
        if ftlog.is_debug():
            ftlog.debug("skill end, userId =", self.player.userId, "skillId =", self.skillId, "useTimes =", self.newbieUseTimes)
        if self.table.typeName == config.FISH_NEWBIE and not self.player.redState:
            _key = UserData.newbieUseSkillTimes % (FISH_GAMEID, self.player.userId)
            daobase.executeUserCmd(self.player.userId, "HINCRBY", _key, str(self.skillId), 1)
        self.initState()
        self.player.removeUsingSkill(self.skillId, self.skillType)
        self.table.broadcastSkillEnd(self)
        if config.LOCK_KINDID in self.player.skill_item:        # 锁定继承开始时间计时
            self.player.skill_item[config.LOCK_KINDID].pause_and_continue_time(config.LOCK_KINDID)
        self.player.syncSkillItemSlots()
        self.player.gunEffectState(2)
        if self.table.typeName in config.NORMAL_ROOM_TYPE:
            # 使用技能事件
            from newfish.game import TGFish
            from newfish.entity.event import UseSkillEvent
            chip = int(self.getCost * self.fpMultiple)
            event = UseSkillEvent(self.player.userId, FISH_GAMEID, self.table.roomId, self.table.tableId, self.skillId, self.fpMultiple, chip)
            TGFish.getEventBus().publishEvent(event)
            self.player.triggerUseSkillEvent(event)

    def clear(self):
        """
        清除技能数据(广播捕鱼机器人因主人离开房间而死亡)
        """
        self.player.delFire(wpId=self.weaponId)
        if self.skillFishes:
            fishes = []
            for fishId in self.skillFishes:
                if fishId in self.table.fishMap and self.table.fishMap[fishId]["alive"]:
                    for buffer in self.table.fishMap[fishId]["buffer"]:
                        skillId = buffer[0]
                        skillEndTime = buffer[1]
                        if skillId == self.skillId and skillEndTime >= time.time():
                            if self.table.fishMap[fishId]["alive"]:
                                self.table.refreshFishTypeCount(self.table.fishMap[fishId])
                            self.table.fishMap[fishId]["alive"] = False
                            fishes.append(fishId)
            if fishes:
                self.table.broadcastSkillEffect(self.player, time.time(), fishes, self.skillId)

    def getSkillFishesAlive(self):
        """
        获取捕鱼机器人是否存活
        """
        if self.skillFishes:
            for fishId in self.skillFishes:
                if fishId in self.table.fishMap and self.table.fishMap[fishId]["alive"]:
                    for buffer in self.table.fishMap[fishId]["buffer"]:
                        skillId = buffer[0]
                        skillEndTime = buffer[1]
                        if skillId == self.skillId and skillEndTime >= time.time():
                            return True
        return False

    def costClip(self, bulletId, fPosx=0, fPosy=0):
        """
        消耗技能子弹
        """
        if self.clip > 0:
            lastCoin = self.player.holdCoin
            for val in self.player.usingSkill:      # 当前技能使用中时，检测之前技能是否在使用中，如果是，结束之前技能
                skillId = val.get("skillId")
                skillType = val.get("skillType")
                if skillId != self.skillId:
                    skill = self.player.getSkill(skillId)
                    if skill.state == 2:
                        skill.end()
            self.state = 2
            self.clip -= 1
            self.position[bulletId] = [fPosx, fPosy]
            self.updateSkillData()
            self.player.reportBIFeatureData("BI_NFISH_GE_FT_FIRE", self.weaponId, self.onceCostCoin)

            # TODO 该段代码有问题（lichen）
            if lastCoin > self.table.runConfig.coinShortage > self.player.holdCoin:
                coinShortageCount = gamedata.getGameAttrJson(self.player.userId, FISH_GAMEID, GameData.coinShortageCount, {})
                coinShortageCount.setdefault(str(self.table.runConfig.fishPool), 0)
                coinShortageCount[str(self.table.runConfig.fishPool)] += 1
                gamedata.setGameAttr(self.player.userId, FISH_GAMEID, GameData.coinShortageCount, json.dumps(coinShortageCount))
            return True
        return False

    def returnClip(self):
        """
        返还技能打空子弹
        """
        pass

    def updateSkillData(self):
        """
        更新技能数据
        """
        self.updateSkillEnergy()
        skillGradeConf = config.getSkillGradeConf(self.skillId, self.skillGrade, self.skillMode)
        self.impale = skillGradeConf.get("impale", 0)
        powerAddtion = self.player.getPowerAddition(self.weaponId)
        self.totalPower = float(self.power * self.powerRate * powerAddtion)
        self.originRealPower = self.totalPower + self.energy
        self.deductionHP = self.totalPower
        if ftlog.is_debug():
            ftlog.debug(
                "updateSkillData->",
                "userId =", self.player.userId,
                "impale =", self.impale,
                "totalPower =", self.totalPower,
                "deductionHP =", self.deductionHP
            )

    def updateSkillEnergy(self):
        """
        更新技能能量（通过奖池转换而来）
        """
        bonus = 0
        if self.table.room.lotteryPool:
            bonus = self.table.room.lotteryPool.getSkillPoolCoin() // self.fpMultiple
        randomNum = random.uniform(0.5, 1.5)
        singleEnergy = math.ceil(self.power * self.powerRate * randomNum)
        self.originEnergy = min(self.player.energy, singleEnergy)
        self.energy = max(min(self.originEnergy, bonus), 0)
        self.player.energy -= self.energy
        if self.table.room.lotteryPool:
            self.table.room.lotteryPool.deductionSkillPoolCoin(int(self.energy * self.fpMultiple))
        if ftlog.is_debug():
            ftlog.debug(
                "updateSkillEnergy->",
                "userId =", self.player.userId,
                "bonus =", bonus,
                "randomNum =", randomNum,
                "singleEnergy =", singleEnergy,
                "originEnergy =", self.originEnergy,
                "energy =", self.energy,
                "player.energy =", self.player.energy
            )

    @property
    def getCost(self):
        """
        技能消耗子弹数
        """
        return self.cost * self.gunSkinMultiple / self.skillMultiple * self.gunX

    @property
    def cdTimeLeft(self):
        """
        技能剩余CD时间
        """
        timeLeft = self.cdStartTime + self.coolDown - int(time.time())
        return max(timeLeft, 0)

    def changeCDStartTime(self, coolDown=0):
        """
        改变技能CD的开始时间
        """
        cdAddition = self.player.getSkillCDAddition(self.weaponId)
        if coolDown:
            self.cdStartTime -= coolDown
        else:
            self.cdStartTime -= self.coolDown * cdAddition

    def catchFish(self, bulletId, wpId, fIds, extends):
        """
        技能捕获判定
        """
        raise NotImplementedError

    def normalCatchFish(self, bulletId, wpId, fIds, extends):
        """
        普通捕获算法
        """
        catch = []
        gain = []
        gainChip = 0
        exp = 0
        otherCatch = {}
        isCatch = False
        self.consumePower = 0
        fishes, fishesHPDict = self._sortedFishes(fIds, extends)
        totalPowerRate = self.totalPower / self.originRealPower  # 技能总威力（无加成）的占比
        realPower = self.originRealPower
        bufferCoinAdd = self.player.getCoinAddition(self.weaponId)
        curveProfitCoin = 0
        aloofOdds = self.player.dynamicOdds.getOdds(skill=self, aloofFish=True)
        nonAloofOdds = self.player.dynamicOdds.getOdds(skill=self, aloofFish=False)
        for fId, probbRadix in fishes:
            isOK, catchUserId = self.table.verifyFish(self.player.userId, fId, wpId)
            if not isOK:
                continue
            catchMap = {}
            catchMap["fId"] = fId
            catchMap["reason"] = 1
            fishInfo = self.table.fishMap[fId]
            probb, realPower = self.getSkillCatchProbb(fId, len(fIds), realPower, probbRadix, aloofOdds, nonAloofOdds)
            catchMap["HP"] = fishInfo["HP"]
            randInt = random.randint(1, 10000)
            if randInt <= probb:
                multiple = self.gunSkinMultiple
                # 欺诈只获得1倍收益.
                if catchUserId != self.player.userId:
                    multiple = 1
                fishGainChip, fishGain, fishExp = self.table.dealKillFishGain(fId, self.player, self.fpMultiple, multiple, bufferCoinAdd)
                catchMap["reason"] = 0
                if catchUserId == self.player.userId:
                    isCatch = True
                    catch.append(catchMap)
                    gainChip += fishGainChip
                    exp += fishExp
                    if fishGain:
                        gain.extend(fishGain)
                else:
                    otherCatch = self.table.extendOtherCatchGain(fId, catchUserId, otherCatch, fishGainChip, fishGain, catchMap, fishExp)
            else:
                if fId in fishesHPDict:
                    catch.append(catchMap)
                fishGainChip, fishGain = self.table.dealHitBossGain(self.totalPower, fId, self.player)
                if catchUserId == self.player.userId:
                    gainChip += fishGainChip
                    if fishGain:
                        gain.extend(fishGain)
                else:
                    otherCatch = self.table.extendOtherCatchGain(fId, catchUserId, otherCatch, fishGainChip, fishGain)
            # 技能曲线
            if self.table.typeName in config.DYNAMIC_ODDS_ROOM_TYPE:
                fishType = fishInfo["fishType"]
                fishConf = config.getFishConf(fishType, self.table.typeName, self.fpMultiple)
                if fishConf["type"] in config.NON_ALOOF_FISH_TYPE:
                    if catchMap["reason"] == 0:
                        curveProfitCoin += totalPowerRate * fishConf["value"] * self.gunSkinMultiple * self.fpMultiple
        self.consumePower = min(self.consumePower, self.originRealPower)
        curveLossCoin = self.onceCostCoin * self.consumePower / self.originRealPower
        if ftlog.is_debug():
            ftlog.debug(
                "normalCatchFish->",
                "userId =", self.player.userId,
                "catch =", catch,
                "gain =", gain,
                "gainChip =", gainChip,
                "onceCostCoin =", self.onceCostCoin,
                "consumePower =", self.consumePower,
                "originRealPower =", self.originRealPower,
                "totalPowerRate =", totalPowerRate,
                "curveLossCoin =", curveLossCoin,
                "curveProfitCoin =", curveProfitCoin
            )
        self.player.dynamicOdds.updateOdds(curveProfitCoin - curveLossCoin)
        if isCatch:
            self.player.addCombo()
        for userId, catchInfo in otherCatch.iteritems():
            otherPlayer = self.table.getPlayer(userId)
            if otherPlayer:
                self.table.dealCatch(bulletId, wpId, otherPlayer, catchInfo["catch"], catchInfo["gain"],
                                     catchInfo["gainChip"], catchInfo["exp"], self.fpMultiple, self.gunSkinMultiple, self.gunX)
        return catch, gain, gainChip, exp

    def _getFishProbbRadix(self, fishInfo, fishConf):
        # 新手任务期间概率特殊处理
        if self.player and util.isNewbieRoom(self.player.table.typeName):
            curTaskId = self.player.taskSystemUser.curTask.getTaskId()
            isTaskOver = self.player.taskSystemUser.curTask.isTaskOver()
            probbRadix = config.getCommonValueByKey("newbieFishProbb").get(str(fishConf["type"]), {}).get(str(curTaskId))
            if not isTaskOver and probbRadix:
                if fishConf["type"] == 2:
                    if self.player.bossAppearCount > 1:
                        return probbRadix
                else:
                    return probbRadix
        if fishInfo["HP"] > 0:
            return fishConf["probb2"]
        elif fishConf["type"] in config.RAINBOW_BONUS_FISH_TYPE:
            value = fishConf["score"]
            gunMultiple = config.getGunConf(self.player.gunId, self.player.clientId, self.player.gunLv, self.table.gameMode).get("multiple", 1)
            rainbowPoolCoin = 0
            if self.table.room.lotteryPool:
                rainbowPoolCoin = self.table.room.lotteryPool.getRainbowPoolCoin()
            if fishConf["type"] in config.TERROR_FISH_TYPE:
                value = config.getWeaponConf(fishConf["weaponId"], False, self.table.gameMode)["power"]
            if self.player.dynamicOdds.currRechargeBonus >= value * gunMultiple * self.fpMultiple:
                # 存在充值奖池
                if ftlog.is_debug():
                    ftlog.debug("_getFishProbbRadix->currRechargeBonus =", self.player.dynamicOdds.currRechargeBonus, fishConf)
                return fishConf["probb1"]
            elif (min(3 * self.originEnergy * self.fpMultiple, rainbowPoolCoin) >= value * gunMultiple * self.fpMultiple):
                # 存在能量及彩虹鱼奖池
                if ftlog.is_debug():
                    ftlog.debug("_getFishProbbRadix->energy =", self.originEnergy, rainbowPoolCoin, fishConf)
                return fishConf["probb1"]
            return fishConf["probb2"]
        else:
            return fishConf["probb1"]

    def _sortedFishes(self, fIds, extends=None):
        """
        排序所有鱼
        :param fIds: 鱼的Ids
        :param extends: 扩展
        """
        fishesDict = {}
        fishesHPDict = {}
        for fId in fIds:
            isOK = self.table.findFish(fId)
            if not isOK:
                continue
            fishInfo = self.table.fishMap[fId]
            originHP = fishInfo["HP"]
            fishType = fishInfo["fishType"]
            fishConf = config.getFishConf(fishType, self.table.typeName, self.fpMultiple)
            fatal = self.table.dealIceConeEffect(fId, fishConf)
            fishHP = int(originHP - self.deductionHP)
            fishHP = self.table.dealFishHP(fId, fishHP, fatal)
            fishesDict[fId] = self._getFishProbbRadix(fishInfo, fishConf)
            if originHP != fishHP:
                fishesHPDict[fId] = originHP
        fishes = sorted(fishesDict.iteritems(), key=lambda d: d[1])
        if ftlog.is_debug():
            ftlog.debug("_sortedFishes->", fishes, fishesHPDict, fIds)
        return fishes, fishesHPDict

    def getSkillCatchProbb(self, fId, fIdsCount, realPower, probbRadix, aloofOdds, nonAloofOdds):
        """
        技能捕获鱼的概率
        :param fId: 鱼的ID
        :param fIdsCount: 鱼的数量
        :param realPower: 真实威力
        :param probbRadix: 鱼被捕获的概率数
        :param aloofOdds: 高冷鱼概率
        :param nonAloofOdds: 非高冷鱼概率
        :return: 捕获概率、剩余能量
        """
        fishInfo = self.table.fishMap[fId]
        fishHP = fishInfo["HP"]
        ftType = fishInfo["type"]
        coefficient = self.table.getProbbCoefficient(self.player, fishInfo)
        odds = 0
        lossPower = 0
        # 概率参数为0时，无HP必定被捕获，有HP无法被捕获
        if probbRadix == 0:
            probb = 10000 if fishHP <= 0 else 0
        else:
            if realPower <= 0:
                probb = 0
            else:
                if ftType in config.NON_ALOOF_FISH_TYPE:
                    odds = nonAloofOdds
                else:
                    odds = aloofOdds
                if odds > 0:
                    lossPower = probbRadix / odds
                    if realPower >= lossPower:
                        probb = 10000
                    else:
                        probb = realPower / lossPower * 10000
                else:
                    lossPower = probbRadix * 2
                    probb = 0
                if ftType in config.NON_ALOOF_FISH_TYPE:
                    if realPower >= lossPower:
                        self.consumePower += lossPower
                    else:
                        self.consumePower += realPower
                realPower -= lossPower
        probb *= coefficient
        if ftType in config.RED_FISH_TYPE:
            probb *= self.player.catchRedFishProbbRatio
            if self.player.userId in config.getPublic("banRedFishList", []):
                probb *= 0.3
        if ftlog.is_debug():
            ftlog.debug(
                "getSkillCatchProbb->",
                "userId =", self.player.userId,
                "probb =", probb,
                "odds =", odds,
                "fId =", fId,
                "fIdsCount =", fIdsCount,
                "fishType =", fishInfo["fishType"],
                "fishHP =", fishHP,
                "realPower =", realPower,
                "probbRadix =", probbRadix,
                "aloofOdds =", aloofOdds,
                "nonAloofOdds =", nonAloofOdds,
                "lossPower =", lossPower,
                "consumePower =", self.consumePower,
                "coefficient =", coefficient
            )
        return probb, realPower

    def getRealEffectSkillGrade(self):
        """
        玩家在欢乐竞技和王者争霸中夺冠，则下次比赛中使用该技能降级
        """
        _skillGrade = self.skillGrade
        match_rank = None
        reductGrade = 0
        if self.table.runConfig.fishPool == 44102:
            reductGrade = 5
            match_rank = config.RANK_MATCH_HLJJ
        elif self.table.runConfig.fishPool == 44103:
            reductGrade = 8
            match_rank = config.RANK_MATCH_WZZB
        if match_rank and reductGrade:
            rankingList = hallranking.rankingSystem.getTopN(match_rank, 1, int(time.time()))
            if rankingList and len(rankingList.rankingUserList) > 0 and rankingList.rankingUserList[0].userId == self.player.userId:
                _skillGrade = max(1, _skillGrade - reductGrade)
        if ftlog.is_debug():
            ftlog.debug("getRealEffectSkillGrade, userId =", self.player.userId, "lastGrade =", self.skillGrade,
                    "curGrade =", _skillGrade, self.table.runConfig.fishPool, match_rank, reductGrade)
        return _skillGrade

    def checkIfDayFree(self):
        """检查技能是否免费"""
        if ftlog.is_debug():
            ftlog.debug("SkillBase.checkIfDayFree IN", "tableId=", self.table.tableId, "userId=", self.player.userId,
                        "dayFreeMaxMulti=", self.dayFreeMaxMulti, "gunX=", self.gunX)
        if self.table.typeName == config.FISH_TIME_MATCH or self.table.typeName == config.FISH_TIME_POINT_MATCH:
            return False
        self.gunX = util.getGunX(self.player.nowGunLevel, self.skillMode)
        if self.dayFreeMaxMulti >= self.gunX:
            data = weakdata.getDayFishData(self.player.userId, WeakData.skillFreeUseCount, {})
            if ftlog.is_debug():
                ftlog.debug("SkillBase.checkIfDayFree get data", "userId=", self.player.userId, "data=", data)
            if str(self.skillId) in data:
                # 目前固定每日免费1次，不走配置
                if data[str(self.skillId)] < 1:
                    return True
            else:
                return True
        return False

    def doDayFreeUse(self):
        """免费使用次数"""
        data = weakdata.getDayFishData(self.player.userId, WeakData.skillFreeUseCount, {})
        skillId = str(self.skillId)
        if skillId in data:
            data[skillId] += 1
        else:
            data[skillId] = 1
        weakdata.setDayFishData(self.player.userId, WeakData.skillFreeUseCount, data)
        return data[skillId]

    def cancelDayFreeUse(self):
        """取消免费使用的次数"""
        data = weakdata.getDayFishData(self.player.userId, WeakData.skillFreeUseCount, {})
        skillId = str(self.skillId)
        if skillId in data:
            data[skillId] -= 1
            weakdata.setDayFishData(self.player.userId, WeakData.skillFreeUseCount, data)
        return data.get(skillId, 0)

class SkillMissile(SkillBase):
    """
    合金飞弹(5101)
    """
    def catchFish(self, bulletId, wpId, fIds, extends):
        """合金飞弹捕鱼"""
        useMissileCount = gamedata.getGameAttr(self.player.userId, FISH_GAMEID, GameData.useMissileCount)
        if not useMissileCount or useMissileCount < 5 and self.table.runConfig.fishPool == 44001:   # 普通渔场2倍爆炸威力增加5倍
            self.powerRate = 5
            useMissileCount = 1 if not useMissileCount else useMissileCount + 1
            gamedata.setGameAttr(self.player.userId, FISH_GAMEID, GameData.useMissileCount, useMissileCount)
        return self.normalCatchFish(bulletId, wpId, fIds, extends)

    def _sortedFishes(self, fIds, extends=None):
        """按照鱼的捕获概率排序"""
        fishesDict = {}
        fishesHPDict = {}
        for fId in fIds:
            isOK = self.table.findFish(fId)
            if not isOK:
                continue
            fishInfo = self.table.fishMap[fId]
            originHP = fishInfo["HP"]
            fishType = fishInfo["fishType"]
            fishConf = config.getFishConf(fishType, self.table.typeName, self.fpMultiple)
            fatal = self.table.dealIceConeEffect(fId, fishConf)         # 冰锥HP在65%以上时无法一击致命
            fishHP = int(originHP - self.deductionHP)
            fishHP = self.table.dealFishHP(fId, fishHP, fatal)
            fishesDict[fId] = self._getFishProbbRadix(fishInfo, fishConf)
            if originHP != fishHP:
                fishesHPDict[fId] = originHP
        # 网中的鱼数量大于5时从小到大排序;反之从大到小.
        fishes = sorted(fishesDict.iteritems(), key=lambda d: d[1], reverse=(len(fishesDict) <= 5))
        if ftlog.is_debug():
            ftlog.debug(self.skillId, ", _sortedFishes->", fishes, fishesHPDict, fIds)
        return fishes, fishesHPDict

class SkillMagic(SkillBase):
    """
    魔术炮(5102)
    """
    def initStar(self):
        super(SkillMagic, self).initStar()
        if self.table.typeName == config.FISH_TIME_MATCH:   # 回馈赛-召唤出的鱼永久无敌
            self.duration += 999
        elif self.table.typeName == config.FISH_TIME_POINT_MATCH:
            self.duration += 999

    def catchFish(self, bulletId, wpId, fIds, extends):
        catch, gain, gainChip, exp = [], [], 0, 0
        position = self.position.get(bulletId, [0, 0])
        buffer = None
        endTime = time.time() + self.duration
        fishType = 0
        if self.duration:
            buffer = [self.skillId, endTime, self.player.userId, self.skillStar, self.skillGrade, self.duration, 0]
            fishes = self.player.getTargetFishs()
            if fishes:
                fishType = random.choice(fishes)
        _skillGrade = self.skillGrade                       # self.getRealEffectSkillGrade()
        commonRoom = config.NORMAL_ROOM_TYPE
        if (self.table.typeName in commonRoom and not fishType) or \
           (self.table.typeName not in commonRoom):         # 普通场无任务时或者非普通场召唤倍率鱼
            callFishList = config.getCallMultipleFishConf(_skillGrade, self.table.typeName, self.table.runConfig.fishPool)
            totalWeight = sum([dict(fishes)["weight"] for fishes in callFishList if fishes])
            fishType = 0                                    # callFishList[0]["fishType"]
            if totalWeight > 0:
                weight = random.randint(0, totalWeight)
                for callFish in callFishList:
                    if callFish["weight"] == 0:
                        continue
                    if callFish and weight - callFish["weight"] <= 0:
                        fishType = callFish["fishType"]
                        break
                    weight -= callFish["weight"]
        if fishType > 0:
            groupName = "call_%d_lv%d" % (fishType, _skillGrade)
            self.table.insertFishGroup(groupName, position, buffer=buffer, gameResolution=self.player.gameResolution)
            if ftlog.is_debug():
                ftlog.debug("SkillMagic, insertFishGroup, userId =", self.player.userId, groupName, self.skillGrade, _skillGrade)
        else:
            if ftlog.is_debug():
                ftlog.debug("catchFish, not find fishType !", self.player.userId)
        return catch, gain, gainChip, exp

class SkillCannon(SkillBase):
    """
    火蛇加农炮(5103)
    """
    def catchFish(self, bulletId, wpId, fIds, extends):
        catch = []
        gain = []
        gainChip = 0
        exp = 0
        otherCatch = {}
        isCatch = False
        bufferCoinAdd = self.player.getCoinAddition(self.weaponId)
        realPower = self.originRealPower
        aloofOdds = self.player.dynamicOdds.getOdds(skill=self, aloofFish=True)
        for fId in fIds:
            isOK, catchUserId = self.table.verifyFish(self.player.userId, fId)
            if not isOK or self.impale <= 0:
                break
            catchMap = {}
            catchMap["fId"] = fId
            catchMap["reason"] = 1
            fishInfo = self.table.fishMap[fId]
            fishType = fishInfo["fishType"]
            fishConf = config.getFishConf(fishType, self.table.typeName, self.fpMultiple)
            originHP = fishInfo["HP"]
            probbRadix = fishInfo["HP"] + fishConf["probb2"]
            if self.impale > probbRadix:  # 子弹能穿刺鱼
                self.impale -= probbRadix
                self.impale = max(self.impale, 0)
                randInt = random.randint(1, 10000)
                if randInt <= 9000 or self.impaleCatch:
                    catchMap["reason"] = 0
                if ftlog.is_debug():
                    ftlog.debug("SkillCannon->impale->",
                                "userId =", self.player.userId,
                                "randInt =", randInt,
                                "fId =", fId,
                                "fishType =", fishInfo["fishType"],
                                "probbRadix =", probbRadix,
                                "impale =", self.impale)
            else:  # 子弹无法穿刺鱼
                realPower += self.impale
                self.impale = 0
                fatal = self.table.dealIceConeEffect(fId, fishConf)
                self.deductionHP = self.totalPower
                fishHP = int(originHP - self.deductionHP)
                self.table.dealFishHP(fId, fishHP, fatal)
                probbRadix = self._getFishProbbRadix(fishInfo, fishConf)
                probb, realPower = self.getSkillCatchProbb(fId, len(fIds), realPower, probbRadix, aloofOdds, aloofOdds)
                randInt = random.randint(1, 10000)
                if randInt <= probb:
                    catchMap["reason"] = 0
            catchMap["HP"] = fishInfo["HP"]
            if catchMap["reason"] == 0:
                multiple = self.gunSkinMultiple
                # 欺诈只获得1倍收益.
                if catchUserId != self.player.userId:
                    multiple = 1
                fishGainChip, fishGain, fishExp = self.table.dealKillFishGain(fId, self.player, self.fpMultiple, multiple, bufferCoinAdd)
                if catchUserId == self.player.userId:
                    isCatch = True
                    catch.append(catchMap)
                    gainChip += fishGainChip
                    exp += fishExp
                    if fishGain:
                        gain.extend(fishGain)
                else:
                    otherCatch = self.table.extendOtherCatchGain(fId, catchUserId, otherCatch, fishGainChip, fishGain, catchMap, fishExp)
            else:
                if originHP != catchMap["HP"]:
                    catch.append(catchMap)
                fishGainChip, fishGain = self.table.dealHitBossGain(self.power + self.impale, fId, self.player)
                if catchUserId == self.player.userId:
                    gainChip += fishGainChip
                    if fishGain:
                        gain.extend(fishGain)
                else:
                    otherCatch = self.table.extendOtherCatchGain(fId, catchUserId, otherCatch, fishGainChip, fishGain)
        if ftlog.is_debug():
            ftlog.debug("catchFish->",
                        "userId =", self.player.userId,
                        "catch =", catch,
                        "gain =", gain,
                        "onceCostCoin =", self.onceCostCoin,
                        "originRealPower =", self.originRealPower)
        if isCatch:
            self.player.addCombo()
        for userId, catchInfo in otherCatch.iteritems():
            otherPlayer = self.table.getPlayer(userId)
            if otherPlayer:
                self.table.dealCatch(bulletId, wpId, otherPlayer, catchInfo["catch"], catchInfo["gain"],
                                     catchInfo["gainChip"], catchInfo["exp"], self.fpMultiple, self.gunSkinMultiple, self.gunX)
        return catch, gain, gainChip, exp

class SkillFrozen(SkillBase):
    """
    极冻炮(5104)
    """
    def catchFish(self, bulletId, wpId, fIds, extends):
        catch = []
        gain = []
        gainChip = 0
        exp = 0
        position = self.position.get(bulletId, [0, 0])
        endTime = time.time() + self.duration
        invincibleEndTime = 0
        frozenBuffer = [self.skillId, endTime, self.player.userId, self.skillStar, self.skillGrade, self.duration, 0]
        self.table.insertFishGroup("call_piton_%d" % self.skillStar, position, self.HP, frozenBuffer,
                                   gameResolution=self.player.gameResolution)
        frozenFishes = []
        invincibleFishes = []
        addTimeGroups = []
        for fId in fIds:
            isOK = self.table.findFish(fId)
            if not isOK:
                continue
            ftType = self.table.fishMap[fId]["type"]
            if ftType in config.ICE_FISH_TYPE:    # 冰锥不会刷新冰冻时间
                continue
            isCoverInvincible = True
            buffers = self.table.fishMap[fId]["buffer"]
            # 计算能否冰冻
            isCoverFrozen, lastFrozenTime, frozenTime, _ = self.table.checkCoverFrozen(fId, self.duration, endTime)
            if isCoverFrozen:
                self.table.frozenFish(fId, frozenBuffer, lastFrozenTime, frozenTime, addTimeGroups)
                frozenFishes.append(fId)
            # 计算能否无敌
            for lastBuffer in buffers:
                if lastBuffer[0] == 5102 and lastBuffer[1] > time.time():
                    isCoverInvincible = False
                    break
            if isCoverInvincible and self.table.typeName not in config.NORMAL_ROOM_TYPE:  # 非普通场极冻炮附加无敌效果
                if ftType in [6, 7]:  # 冰锥、捕鱼机器人不会有无敌效果
                    continue
                invincibleDuration = round(self.duration / 3.0, 3)
                if self.player.gunId == 1165:
                    invincibleDuration += 2
                invincibleEndTime = time.time() + invincibleDuration
                invincibleBuffer = [5102, invincibleEndTime, self.player.userId, self.skillStar, self.skillGrade, invincibleDuration, 0, self.skillType]
                self.table.setFishBuffer(fId, invincibleBuffer)
                invincibleFishes.append(fId)
        if frozenFishes:    # 广播新处于冰冻状态的鱼
            self.table.broadcastSkillEffect(self.player, endTime, frozenFishes, self.skillId)
        if invincibleFishes:     # 广播新处于无敌状态的鱼
            self.table.broadcastSkillEffect(self.player, invincibleEndTime, invincibleFishes, 5102)
        return catch, gain, gainChip, exp

class SkillGrenade(SkillBase):
    """
    榴弹炮(5105)
    """
    def catchFish(self, bulletId, wpId, fIds, extends):
        return self.normalCatchFish(bulletId, wpId, fIds, extends)

    def _sortedFishes(self, fIds, extends=None):
        fishesDict = {}
        fishesHPDict = {}
        for fId in fIds:
            isOK = self.table.findFish(fId)
            if not isOK:
                continue
            fishInfo = self.table.fishMap[fId]
            originHP = fishInfo["HP"]
            fishType = fishInfo["fishType"]
            fishConf = config.getFishConf(fishType, self.table.typeName, self.fpMultiple)
            fatal = self.table.dealIceConeEffect(fId, fishConf)
            fishHP = int(originHP - self.deductionHP)
            fishHP = self.table.dealFishHP(fId, fishHP, fatal)
            fishesDict[fId] = self._getFishProbbRadix(fishInfo, fishConf)
            if originHP != fishHP:
                fishesHPDict[fId] = originHP
        # 从大到小排序.
        fishes = sorted(fishesDict.iteritems(), key=lambda d: d[1], reverse=True)
        if ftlog.is_debug():
            ftlog.debug(self.skillId, ", _sortedFishes->", fishes, fishesHPDict, fIds)
        return fishes, fishesHPDict

    def returnClip(self):
        """
        打空返还技能子弹
        """
        if self.state == 2:
            if self.clip < self.originClip:
                self.clip += 1
                self.updateSkillData()

class SkillEnergy(SkillBase):
    """
    汇能弹(5106)
    """
    def catchFish(self, bulletId, wpId, fIds, extends):
        catch = []
        gain = []
        gainChip = 0
        exp = 0
        otherCatch = {}
        isCatch = False
        bufferCoinAdd = self.player.getCoinAddition(self.weaponId)
        realPower = self.originRealPower
        aloofOdds = self.player.dynamicOdds.getOdds(skill=self, aloofFish=True)
        for fId in fIds:
            isOK, catchUserId = self.table.verifyFish(self.player.userId, fId)
            if not isOK or self.impale <= 0:
                break
            catchMap = {}
            catchMap["fId"] = fId
            catchMap["reason"] = 1
            fishInfo = self.table.fishMap[fId]
            fishType = fishInfo["fishType"]
            fishConf = config.getFishConf(fishType, self.table.typeName, self.fpMultiple)
            originHP = fishInfo["HP"]
            probbRadix = fishInfo["HP"] + fishConf["probb2"]
            if self.impale > probbRadix:    # 子弹能穿刺鱼
                self.impale -= probbRadix
                self.impale = max(self.impale, 0)
                randInt = random.randint(1, 10000)
                if randInt <= 9000 or self.impaleCatch:
                    catchMap["reason"] = 0
                if ftlog.is_debug():
                    ftlog.debug("SkillEnergy->impale->",
                                "userId =", self.player.userId,
                                "randInt =", randInt,
                                "fId =", fId,
                                "fishType =", fishInfo["fishType"],
                                "probbRadix =", probbRadix,
                                "impale =", self.impale)
            else:                           # 子弹无法穿刺鱼
                realPower += self.impale
                self.impale = 0
                fatal = self.table.dealIceConeEffect(fId, fishConf)
                self.deductionHP = self.totalPower
                fishHP = int(originHP - self.deductionHP)
                self.table.dealFishHP(fId, fishHP, fatal)
                probbRadix = self._getFishProbbRadix(fishInfo, fishConf)
                probb, realPower = self.getSkillCatchProbb(fId, len(fIds), realPower, probbRadix, aloofOdds, aloofOdds)
                randInt = random.randint(1, 10000)
                if randInt <= probb:
                    catchMap["reason"] = 0
            catchMap["HP"] = fishInfo["HP"]
            if catchMap["reason"] == 0:
                multiple = self.gunSkinMultiple
                # 欺诈只获得1倍收益.
                if catchUserId != self.player.userId:
                    multiple = 1
                fishGainChip, fishGain, fishExp = self.table.dealKillFishGain(fId, self.player, self.fpMultiple, multiple, bufferCoinAdd)
                if catchUserId == self.player.userId:
                    isCatch = True
                    catch.append(catchMap)
                    gainChip += fishGainChip
                    exp += fishExp
                    if fishGain:
                        gain.extend(fishGain)
                else:
                    otherCatch = self.table.extendOtherCatchGain(fId, catchUserId, otherCatch, fishGainChip, fishGain, catchMap, fishExp)
            else:
                if originHP != catchMap["HP"]:
                    catch.append(catchMap)
                fishGainChip, fishGain = self.table.dealHitBossGain(self.power + self.impale, fId, self.player)
                if catchUserId == self.player.userId:
                    gainChip += fishGainChip
                    if fishGain:
                        gain.extend(fishGain)
                else:
                    otherCatch = self.table.extendOtherCatchGain(fId, catchUserId, otherCatch, fishGainChip, fishGain)
        if ftlog.is_debug():
            ftlog.debug("catchFish->",
                        "userId =", self.player.userId,
                        "catch =", catch,
                        "gain =", gain,
                        "onceCostCoin =", self.onceCostCoin,
                        "originRealPower =", self.originRealPower)
        if isCatch:
            self.player.addCombo()
        for userId, catchInfo in otherCatch.iteritems():
            otherPlayer = self.table.getPlayer(userId)
            if otherPlayer:
                self.table.dealCatch(bulletId, wpId, otherPlayer, catchInfo["catch"], catchInfo["gain"],
                                     catchInfo["gainChip"], catchInfo["exp"], self.fpMultiple, self.gunSkinMultiple, self.gunX)
        return catch, gain, gainChip, exp

class SkillFraud(SkillBase):
    """
    欺诈水晶(5107)
    """
    def initStar(self):
        super(SkillFraud, self).initStar()
        if self.table.typeName in [config.FISH_TIME_MATCH, config.FISH_FIGHT]:  # [回馈赛, 渔友竞技]-欺诈水晶3倍时长
            self.duration *= 3
        elif self.table.typeName == config.FISH_TIME_POINT_MATCH:
            self.duration *= 3

    def catchFish(self, bulletId, wpId, fIds, extends):
        catch = []
        gain = []
        gainChip = 0
        exp = 0
        fishes = []
        endTime = time.time() + self.duration
        for fId in fIds:
            isOK, catchUserId = self.table.verifyFish(self.player.userId, fId)
            if not isOK:
                continue
            buffers = self.table.fishMap[fId]["buffer"]
            for lastBuffer in buffers:
                if lastBuffer[0] == 5102 and lastBuffer[1] > time.time():
                    isOK = False
                    break
            if not isOK:
                continue
            ftType = self.table.fishMap[fId]["type"]
            if ftType in config.ICE_FISH_TYPE + config.ROBOT_FISH_TYPE:  # 欺诈水晶对冰锥、机器人无效
                continue
            buffer = [self.skillId, endTime, self.player.userId, self.skillStar, self.skillGrade, self.duration, 0, self.skillType]
            self.table.setFishBuffer(fId, buffer)
            fishes.append(fId)
        if fishes:
            self.table.broadcastSkillEffect(self.player, endTime, fishes, self.skillId)
        return catch, gain, gainChip, exp

class SkillLaser(SkillBase):
    """
    激光炮(5108)
    """
    def costClip(self, bulletId, fPosx=0, fPosy=0):
        """
        消耗技能子弹
        """
        if self.clip > 0:
            for val in self.player.usingSkill:      # 当前技能使用中时，检测之前技能是否在使用中，如果是，结束之前技能
                skillId = val.get("skillId")
                skillType = val.get("skillType")
                if skillId != self.skillId:
                    skill = self.player.getSkill(skillId)
                    if skill.state == 2:
                        skill.end()
            self.state = 2
            self.clip -= 1
            self.position[bulletId] = [fPosx, fPosy]
            return True
        return False

    def catchFish(self, bulletId, wpId, fIds, extends):
        catch = []
        gain = []
        gainChip = 0
        exp = 0
        otherCatch = {}
        isCatch = False
        if not extends:
            return catch, gain, gainChip, exp
        self.consumePower = 0
        self.powerRate = self.getPowerRate(extends)
        self.updateSkillData()
        fishes, fishesHPDict = self._sortedFishes(fIds, extends)
        totalPowerRate = self.totalPower / self.originRealPower  # 技能总威力（无加成）的占比
        realPower = self.originRealPower
        bufferCoinAdd = self.player.getCoinAddition(self.weaponId)
        curveProfitCoin = 0
        aloofOdds = self.player.dynamicOdds.getOdds(skill=self, aloofFish=True)
        nonAloofOdds = self.player.dynamicOdds.getOdds(skill=self, aloofFish=False)
        for fId, probbRadix in fishes:
            isOK, catchUserId = self.table.verifyFish(self.player.userId, fId, wpId)
            if not isOK:
                continue
            catchMap = {}
            catchMap["fId"] = fId
            catchMap["reason"] = 1
            fishInfo = self.table.fishMap[fId]
            probb, realPower = self.getSkillCatchProbb(fId, len(fIds), realPower, probbRadix, aloofOdds, nonAloofOdds)
            catchMap["HP"] = fishInfo["HP"]
            randInt = random.randint(1, 10000)
            if randInt <= probb:
                multiple = self.gunSkinMultiple
                # 欺诈只获得1倍收益.
                if catchUserId != self.player.userId:
                    multiple = 1
                fishGainChip, fishGain, fishExp = self.table.dealKillFishGain(fId, self.player, self.fpMultiple, multiple, bufferCoinAdd)
                catchMap["reason"] = 0
                if catchUserId == self.player.userId:
                    isCatch = True
                    catch.append(catchMap)
                    gainChip += fishGainChip
                    exp += fishExp
                    if fishGain:
                        gain.extend(fishGain)
                else:
                    otherCatch = self.table.extendOtherCatchGain(fId, catchUserId, otherCatch, fishGainChip, fishGain, catchMap, fishExp)
            else:
                if fId in fishesHPDict:
                    catch.append(catchMap)
                count, fishGain, fishGainChip = extends[0], [], 0
                if count == 1:  # 只有第一次算捕获
                    fishGainChip, fishGain = self.table.dealHitBossGain(self.totalPower, fId, self.player)
                if catchUserId == self.player.userId:
                    gainChip += fishGainChip
                    if fishGain:
                        gain.extend(fishGain)
                else:
                    otherCatch = self.table.extendOtherCatchGain(fId, catchUserId, otherCatch, fishGainChip, fishGain)
            # 技能曲线
            if self.table.typeName in config.DYNAMIC_ODDS_ROOM_TYPE:
                fishType = fishInfo["fishType"]
                fishConf = config.getFishConf(fishType, self.table.typeName, self.fpMultiple)
                if fishConf["type"] in config.NON_ALOOF_FISH_TYPE:
                    if catchMap["reason"] == 0:
                        curveProfitCoin += totalPowerRate * fishConf["value"] * self.gunSkinMultiple * self.fpMultiple
        self.consumePower = min(self.consumePower, self.originRealPower)
        curveLossCoin = self.onceCostCoin * self.powerRate * self.consumePower / self.originRealPower
        if ftlog.is_debug():
            ftlog.debug("catchFish->",
                        "userId =", self.player.userId,
                        "catch =", catch,
                        "gain =", gain,
                        "onceCostCoin =", self.onceCostCoin * self.powerRate,
                        "consumePower =", self.consumePower,
                        "originRealPower =", self.originRealPower,
                        "totalPowerRate =", totalPowerRate,
                        "curveLossCoin =", curveLossCoin,
                        "curveProfitCoin =", curveProfitCoin)
        self.player.dynamicOdds.updateOdds(curveProfitCoin - curveLossCoin)
        if isCatch:
            self.player.addCombo()
        for userId, catchInfo in otherCatch.iteritems():
            otherPlayer = self.table.getPlayer(userId)
            if otherPlayer:
                self.table.dealCatch(bulletId, wpId, otherPlayer, catchInfo["catch"], catchInfo["gain"],
                                     catchInfo["gainChip"], catchInfo["exp"], self.fpMultiple, self.gunSkinMultiple, self.gunX)
        return catch, gain, gainChip, exp

    def _sortedFishes(self, fIds, extends=None):
        count = extends[0]
        fishesDict = {}
        fishesHPDict = {}
        for fId in fIds:
            isOK = self.table.findFish(fId)
            if not isOK:
                continue
            fishInfo = self.table.fishMap[fId]
            originHP = fishInfo["HP"]
            fishType = fishInfo["fishType"]
            fishConf = config.getFishConf(fishType, self.table.typeName, self.fpMultiple)
            fatal = self.table.dealIceConeEffect(fId, fishConf)
            fishHP = int(originHP - self.deductionHP)
            fishHP = self.table.dealFishHP(fId, fishHP, fatal)
            fishesDict[fId] = self._getFishProbbRadix(fishInfo, fishConf)
            if originHP != fishHP:
                fishesHPDict[fId] = originHP
        if count <= 2:
            fishes = sorted(fishesDict.iteritems(), key=lambda d: d[1])
        else:
            fishes = sorted(fishesDict.iteritems(), key=lambda d: d[1], reverse=True)
        if ftlog.is_debug():
            ftlog.debug("_sortedFishes->", fishes, fishesHPDict, fIds)
        return fishes, fishesHPDict

    def getPowerRate(self, extends):
        count = extends[0]
        if count == 1:
            powerRate = 0.2
        elif count == 2:
            powerRate = 0.3
        elif count == 3:
            powerRate = 0.5
        else:
            powerRate = 0.2
            ftlog.error("getPowerRate error", self.player.userId, extends)
        return powerRate

class SkillHunt(SkillBase):
    """
    猎鱼机甲(5109)
    """
    def catchFish(self, bulletId, wpId, fIds, extends):
        catch, gain, gainChip, exp = [], [], 0, 0
        if wpId == 2209:        # 释放猎鱼机甲
            position = self.position.get(bulletId, [0, 0])
            endTime = time.time() + self.duration
            buffer = [self.skillId, endTime, self.player.userId, self.skillStar, self.skillGrade, self.duration, 0]
            group = self.table.insertFishGroup("call_robot", position, self.HP, buffer,
                                               gameResolution=self.player.gameResolution)
            self.durationEndTime = endTime
            self.onceCostCoin /= int(self.duration / self.interval)
            if group:
                self.skillFishes.append(group.startFishId)
        elif wpId == 2301:      # 猎鱼机甲开火
            if self.originRealPower and time.time() <= self.durationEndTime:
                return self.normalCatchFish(bulletId, wpId, fIds, extends)
        elif wpId == 2302:      # 猎鱼机甲爆炸
            if self.originRealPower:
                aliveTime = self.durationEndTime - time.time()
                aliveTime = aliveTime if 0 < aliveTime < self.duration else self.duration
                self.powerRate = (self.duration - aliveTime) * (1 / self.interval)
                powerAddtion = self.player.getPowerAddition(self.weaponId)
                self.totalPower = self.power * self.powerRate * powerAddtion + self.impale
                self.energy *= self.powerRate
                self.onceCostCoin *= self.powerRate
                self.originRealPower = self.totalPower + self.energy
                self.deductionHP = self.totalPower
                self.skillFishes = []
                if ftlog.is_debug():
                    ftlog.debug("SkillHunt->catchFish powerRate =", self.powerRate)
                return self.normalCatchFish(bulletId, wpId, fIds, extends)
        return catch, gain, gainChip, exp

class SkillGatling(SkillBase):
    """
    格林机关枪(5110)
    """
    def catchFish(self, bulletId, wpId, fIds, extends):
        return self.normalCatchFish(bulletId, wpId, fIds, extends)

    def returnClip(self):
        """
        打空返还技能子弹
        """
        if self.state == 2:
            if self.clip < self.originClip:
                self.clip += 1
                self.updateSkillData()

skillMap = {
    5101: SkillMissile,     # 合金飞弹
    5102: SkillMagic,       # 魔术炮
    5103: SkillCannon,      # 火蛇加农炮
    5104: SkillFrozen,      # 极冻炮
    5105: SkillGrenade,     # 榴弹炮
    5106: SkillEnergy,      # 汇能弹
    5107: SkillFraud,       # 欺诈水晶
    5108: SkillLaser,       # 激光炮
    5109: SkillHunt,        # 猎鱼机甲
    5110: SkillGatling      # 格林机关枪
}

# 技能ID-武器ID对应关系
skillWeaponMap = {
    5101: 2201,
    5102: 2202,
    5103: 2203,
    5104: 2204,
    5105: 2205,
    5106: 2206,
    5107: 2207,
    5108: 2208,
    5109: 2209,
    5110: 2210
}

# 武器ID-技能ID对应关系
weaponSkillMap = {
    2201: 5101,
    2202: 5102,
    2203: 5103,
    2204: 5104,
    2205: 5105,
    2206: 5106,
    2207: 5107,
    2208: 5108,
    2209: 5109,
    2210: 5110
}

def createSkill(table, player, skillId, skillState, skillStar, skillGrade, skillType):
    skillClass = skillMap.get(skillId)
    skill = None
    if skillClass:
        skill = skillClass(table, player, skillId, skillState, skillStar, skillGrade, skillType)
    return skill


import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import pdb
from matplotlib.table import Table

def stext(ax,text):
    ax.text(0.9, 0.9, text, horizontalalignment='right', verticalalignment='top',transform=ax.transAxes)

def main():
    data = pd.DataFrame()
    # overall lag distribution, with total number of edges + filename
    #fig2 = plt.figure(2, figsize=(20,20))
    # lag counts vs # of edges above certain threshold, percentage, total number of edges

    for ind in range(2,5):
        fig1 = plt.figure(figsize=(20,20))
        filename = 'lag_df2_parse_biocyc_%s.pkl' % (ind)
        lag_df = pd.read_pickle(filename)

        lag_df['lag_counts'] = [len(x) if type(x) is list else 0 for x in lag_df['Lag'].tolist()]

        ax1 = fig1.add_subplot(2,2,1)
        bins = [x for x in range(0,30)]

        ax1.hist(lag_df['lag_counts'], normed=True, bins = bins, color='salmon', alpha=0.8)
        stext(ax1, 'Counts')
        ax2 = fig1.add_subplot(2,2,2)
        ax2.hist(lag_df['lag_std'].dropna(), normed=True, color='salmon', alpha=0.8)
        stext(ax2, 'Stdev')
        ax3 = fig1.add_subplot(2,2,3)
        ax3.hist(lag_df['lag_mean'].dropna(), normed=True, color='salmon', alpha=0.8)
        stext(ax3, 'Mean')
        ax4 = fig1.add_subplot(2,2,4)
        ax4.hist(lag_df['lag_median'].dropna(), normed=True, color='salmon', alpha=0.8)
        stext(ax4, 'Median')

        plt.savefig('lag_stats_biocyc_%s.png'%(ind))

        fig2 = plt.figure(figsize=(20,20))
        # get percentage of edges and number over thresholds

        lag_thresh = [0,1,2]
        count_thresh = [0,5,10,15,20,25]

        data = np.zeros([len(lag_thresh),len(count_thresh)])
        for idx,lag in enumerate(lag_thresh):
            for jdx,count in enumerate(count_thresh):
                items = lag_df[(lag_df['lag_counts'] >= count) & (lag_df['lag_mean'] >=lag)]
                data[idx][jdx] = len(items)

        p_data = data/len(lag_df)
        data = pd.DataFrame(data, index = lag_thresh, columns = count_thresh)
        p_data = pd.DataFrame(p_data, index = lag_thresh, columns = count_thresh)
        checkerboard_table(data)
        plt.savefig('lag_table_biocyc_%s.png'%(ind))
        checkerboard_table(p_data)
        plt.savefig('lag_table_p_biocyc_%s.png'%(ind))

        # get the distribution of lag counts (histogram)
        # the total number of edges

        # lag counts vs # of edges above certain threshold

def checkerboard_table(data, fmt='{:.2f}', bkg_colors=['yellow', 'white']):
    fig, ax = plt.subplots()
    ax.set_axis_off()
    tb = Table(ax, bbox=[0,0,1,1])

    nrows, ncols = data.shape
    width, height = 1.0 / ncols, 1.0 / nrows

    # Add cells
    for (i,j), val in np.ndenumerate(data):
        # Index either the first or second item of bkg_colors based on
        # a checker board pattern
        idx = [j % 2, (j + 1) % 2][i % 2]
        color = bkg_colors[idx]

        tb.add_cell(i, j, width, height, text=fmt.format(val),
                    loc='center', facecolor=color)

    # Row Labels...
    for i, label in enumerate(data.index):
        tb.add_cell(i, -1, width, height, text=label, loc='right',
                    edgecolor='none', facecolor='none')
    # Column Labels...
    for j, label in enumerate(data.columns):
        tb.add_cell(-1, j, width, height/2, text=label, loc='center',
                           edgecolor='none', facecolor='none')
    ax.add_table(tb)
    plt.ylabel('Lag Threshold')
    plt.xlabel('Count Threshold')
    return fig

if __name__ == '__main__':
    main()


"""
Calculate the middle points, areas and best predictors of the polygons and the relative areas of the best predictors
"""

from shapely.geometry.polygon import Polygon

def get_predictor_cost(x, y, rho, sens, spec, cov):
    """
    Calculate the predictor's cost on a point (x, y) based on the rho and its sensitivity, specificity and coverage
    """
    return x * ((rho * cov * (1 - sens)) + cov - 1) + y * (((1 - rho) * cov * (1 - spec)) + cov - 1) + 1 - cov

def get_polygon_best_predictor(rho, predictors, polygons):
    """
    Calculate the predictor with the best cost in a polygon
    """
    polygon_best_predictor = []
    for polygon_id, polygon in enumerate(polygons):
        centroid = Polygon(polygon).centroid
        cost_predictors = {predictor: get_predictor_cost(centroid.x, centroid.y, rho, *predictors[predictor])
                           for predictor in predictors}
        best_predictor = min(cost_predictors, key=cost_predictors.get)
        polygon_best_predictor.append(best_predictor)
    return polygon_best_predictor

def get_best_predictor_area(polygons, polygon_best_predictor):
    """
    Calculate the total area of the best predictors in the triangle
    """
    best_predictor_areas = dict.fromkeys(polygon_best_predictor, 0.0)
    for polygon_id, polygon in enumerate(polygons):
        best_predictor_areas[polygon_best_predictor[polygon_id]] += Polygon(polygon).area
    return best_predictor_areas

def get_predictor_area(best_predictor_areas, predictors):
    """
    Calculate the area and relative areas of predictors
    """
    predictor_areas = {predictor: best_predictor_areas.get(predictor, 0.0) for predictor in predictors}
    predictor_relative_areas = {predictor: predictor_areas[predictor] / 0.5 for predictor in predictors}
    return predictor_areas, predictor_relative_areas

def get_polygons_data(rho, predictors, polygons):
    """
    Calculate the best predictor in a polygon and best predictors' areas and relative areas
    """
    polygon_best_predictor = get_polygon_best_predictor(rho, predictors, polygons)
    best_predictor_areas = get_best_predictor_area(polygons, polygon_best_predictor)
    predictor_areas, predictor_relative_areas = get_predictor_area(best_predictor_areas, predictors)
    return predictor_areas, predictor_relative_areas