# coding=utf-8
from __future__ import print_function, unicode_literals, absolute_import
from django.core.management.base import BaseCommand, CommandError
from multiprocessing import Process, Queue
from django.utils import timezone
from django.db import close_old_connections
from api.models import Problem, TopicReply, TopicVote, TopicRankingScore
from gm_types.gaia import TOPIC_TYPE
import math
import os


class Command(BaseCommand):
    help = 'initialize topic ranking score'

    def add_arguments(self, parser):
        parser.add_argument('-p', '--process-num', metavar='PROCESS_NUM', type=int, default=8,
                            dest='process_num', help='process num')

    def handle(self, *args, **options):
        base_time = timezone.now().replace(hour=0, minute=0, second=0)

        queue = Queue()
        process_list = []
        for p_index in range(0, options['process_num']):
            p = Process(target=self.calc_score, kwargs={'queue':queue, 'base_time':base_time})
            process_list.append(p)
            p.start()

        # 只对咨询帖和讨论帖计算分数
        all_topics = Problem.objects.filter(
            topic_type__in=(TOPIC_TYPE.ASK, TOPIC_TYPE.TOPIC)  # 只计算咨询帖和讨论帖
        ).order_by('pk')
        offset = 0
        size = 50
        while True:
            topics = all_topics[offset:offset+size]
            if len(topics)<=0:
                break
            topic_ids = list(topics.values_list('id', flat=True))
            queue.put(topic_ids)
            offset += size

        for p_index in range(0, options['process_num']):
            queue.put(None)  # sentinel
        for p in process_list:
            p.join()

    def calc_score(self, queue, base_time):
        close_old_connections()
        while True:
            topic_ids = queue.get(True)
            if topic_ids is None:
                break
            else:
                scores = {topic_id:{'reply_score':0.0, 'vote_score':0.0} for topic_id in topic_ids}

                replys = TopicReply.objects.filter(problem_id__in=topic_ids, reply_date__lt=base_time)
                for reply in replys:
                    scores[reply.problem_id]['reply_score'] += self.calc_reply_score(reply, base_time)

                votes = TopicVote.objects.filter(topic_id__in=topic_ids, vote_time__lt=base_time)
                for vote in votes:
                    scores[vote.topic_id]['vote_score'] += self.calc_vote_score(vote, base_time)

                for topic_id, sc in scores.items():
                    TopicRankingScore.objects.update_or_create(
                        topic_id = topic_id,
                        defaults={
                            'reply_score': sc['reply_score'],
                            'vote_score': sc['vote_score'],
                        }
                    )

                print('process_id[%d]: topics from %d to %d are done' % (os.getpid(), topic_ids[0], topic_ids[-1]))

    @classmethod
    def calc_reply_score(cls, reply, base_time):
        d = cls.delta_days(reply.reply_date, base_time)
        return TopicRankingScore.REPLY_BASE**d

    @classmethod
    def calc_vote_score(cls, vote, base_time):
        d = cls.delta_days(vote.vote_time, base_time)
        return TopicRankingScore.VOTE_BASE**d

    @classmethod
    def delta_days(cls, t, base_time):
        assert t <= base_time
        return math.ceil((base_time-t).total_seconds()/86400)
