"""
Authorisation related functions.

The actions a User is authorised to perform are dependent on their reputation
and superuser status.
"""
import datetime
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext as _
from django.db import transaction
from models import Repute
from models import Question
from models import Answer
from const import TYPE_REPUTATION
import logging
question_type = ContentType.objects.get_for_model(Question)
answer_type = ContentType.objects.get_for_model(Answer)

VOTE_UP                   = 15
FLAG_OFFENSIVE            = 15
POST_IMAGES               = 15
LEAVE_COMMENTS            = 50
UPLOAD_FILES              = 60
VOTE_DOWN                 = 100
CLOSE_OWN_QUESTIONS       = 250
RETAG_OTHER_QUESTIONS     = 500
REOPEN_OWN_QUESTIONS      = 500
EDIT_COMMUNITY_WIKI_POSTS = 750
EDIT_OTHER_POSTS          = 2000
DELETE_COMMENTS           = 2000
VIEW_OFFENSIVE_FLAGS      = 2000
DISABLE_URL_NOFOLLOW      = 2000
CLOSE_OTHER_QUESTIONS     = 3000
LOCK_POSTS                = 4000

VOTE_RULES = {
    'scope_votes_per_user_per_day' : 30, # how many votes of one user has everyday
    'scope_flags_per_user_per_day' : 5,  # how many times user can flag posts everyday
    'scope_warn_votes_left' : 10,        # start when to warn user how many votes left
    'scope_deny_unvote_days' : 1,        # if 1 days passed, user can't cancel votes.
    'scope_flags_invisible_main_page' : 3, # post doesn't show on main page if has more than 3 offensive flags
    'scope_flags_delete_post' : 5,         # post will be deleted if it has more than 5 offensive flags
}

REPUTATION_RULES = {
    'initial_score'                       : 1,
    'scope_per_day_by_upvotes'            : 200,
    'gain_by_upvoted'                     : 10,
    'gain_by_answer_accepted'             : 15,
    'gain_by_accepting_answer'            : 2,
    'gain_by_downvote_canceled'           : 2,
    'gain_by_canceling_downvote'          : 1,
    'lose_by_canceling_accepted_answer'   : -2,
    'lose_by_accepted_answer_cancled'     : -15,
    'lose_by_downvoted'                   : -2,
    'lose_by_flagged'                     : -2,
    'lose_by_downvoting'                  : -1,
    'lose_by_flagged_lastrevision_3_times': -30,
    'lose_by_flagged_lastrevision_5_times': -100,
    'lose_by_upvote_canceled'             : -10,
}

def can_moderate_users(user):
    return user.is_superuser

def can_vote_up(user):
    """Determines if a User can vote Questions and Answers up."""
    return user.is_authenticated() and (
        user.reputation >= VOTE_UP or
        user.is_superuser)

def can_flag_offensive(user):
    """Determines if a User can flag Questions and Answers as offensive."""
    return user.is_authenticated() and (
        user.reputation >= FLAG_OFFENSIVE or
        user.is_superuser)

def can_add_comments(user,subject):
    """Determines if a User can add comments to Questions and Answers."""
    if user.is_authenticated():
        if user.id == subject.author.id:
            return True
        if user.reputation >= LEAVE_COMMENTS:
            return True
        if user.is_superuser:
            return True
        if isinstance(subject,Answer) and subject.question.author.id == user.id:
            return True
    return False

def can_vote_down(user):
    """Determines if a User can vote Questions and Answers down."""
    return user.is_authenticated() and (
        user.reputation >= VOTE_DOWN or
        user.is_superuser)

def can_retag_questions(user):
    """Determines if a User can retag Questions."""
    return user.is_authenticated() and (
        RETAG_OTHER_QUESTIONS <= user.reputation < EDIT_OTHER_POSTS or
        user.is_superuser)

def can_edit_post(user, post):
    """Determines if a User can edit the given Question or Answer."""
    return user.is_authenticated() and (
        user.id == post.author_id or
        (post.wiki and user.reputation >= EDIT_COMMUNITY_WIKI_POSTS) or
        user.reputation >= EDIT_OTHER_POSTS or
        user.is_superuser)

def can_delete_comment(user, comment):
    """Determines if a User can delete the given Comment."""
    return user.is_authenticated() and (
        user.id == comment.user_id or
        user.reputation >= DELETE_COMMENTS or
        user.is_superuser)

def can_view_offensive_flags(user):
    """Determines if a User can view offensive flag counts."""
    return user.is_authenticated() and (
        user.reputation >= VIEW_OFFENSIVE_FLAGS or
        user.is_superuser)

def can_close_question(user, question):
    """Determines if a User can close the given Question."""
    return user.is_authenticated() and (
        (user.id == question.author_id and
         user.reputation >= CLOSE_OWN_QUESTIONS) or
        user.reputation >= CLOSE_OTHER_QUESTIONS or
        user.is_superuser)

def can_lock_posts(user):
    """Determines if a User can lock Questions or Answers."""
    return user.is_authenticated() and (
        user.reputation >= LOCK_POSTS or
        user.is_superuser)

def can_follow_url(user):
    """Determines if the URL link can be followed by Google search engine."""
    return user.reputation >= DISABLE_URL_NOFOLLOW

def can_accept_answer(user, question, answer):
    return (user.is_authenticated() and
        question.author != answer.author and
        question.author == user) or user.is_superuser

# now only support to reopen own question except superuser
def can_reopen_question(user, question):
    return (user.is_authenticated() and
        user.id == question.author_id and
        user.reputation >= REOPEN_OWN_QUESTIONS) or user.is_superuser

def can_delete_post(user, post):
    if user.is_superuser:
        return True
    elif user.is_authenticated() and user == post.author:
        if isinstance(post,Answer):
            return True
        elif isinstance(post,Question):
            answers = post.answers.all()
            for answer in answers:
                if user != answer.author and answer.deleted == False:
                    return False
            return True
        else:
            return False
    else:
        return False

def can_view_deleted_post(user, post):
    return user.is_superuser

# user preferences view permissions
def is_user_self(request_user, target_user):
    return (request_user.is_authenticated() and request_user == target_user)
    
def can_view_user_votes(request_user, target_user):
    return (request_user.is_authenticated() and request_user == target_user)

def can_view_user_preferences(request_user, target_user):
    return (request_user.is_authenticated() and request_user == target_user)

def can_view_user_edit(request_user, target_user):
    return (request_user.is_authenticated() and request_user == target_user)

def can_upload_files(request_user):
    return (request_user.is_authenticated() and request_user.reputation >= UPLOAD_FILES) or \
           request_user.is_superuser

###########################################
## actions and reputation changes event
###########################################
def calculate_reputation(origin, offset):
    result = int(origin) + int(offset)
    if (result > 0):
        return result
    else:
        return 1

@transaction.commit_on_success
def onFlaggedItem(item, post, user):

    item.save()
    post.offensive_flag_count = post.offensive_flag_count + 1
    post.save()

    post.author.reputation = calculate_reputation(post.author.reputation,
                           int(REPUTATION_RULES['lose_by_flagged']))
    post.author.save()

    question = post
    if ContentType.objects.get_for_model(post) == answer_type:
        question = post.question

    reputation = Repute(user=post.author,
               negative=int(REPUTATION_RULES['lose_by_flagged']),
               question=question, reputed_at=datetime.datetime.now(),
               reputation_type=-4,
               reputation=post.author.reputation)
    reputation.save()

    #todo: These should be updated to work on same revisions.
    if post.offensive_flag_count ==  VOTE_RULES['scope_flags_invisible_main_page'] :
        post.author.reputation = calculate_reputation(post.author.reputation,
                               int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times']))
        post.author.save()

        reputation = Repute(user=post.author,
                   negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_3_times']),
                   question=question,
                   reputed_at=datetime.datetime.now(),
                   reputation_type=-6,
                   reputation=post.author.reputation)
        reputation.save()

    elif post.offensive_flag_count == VOTE_RULES['scope_flags_delete_post']:
        post.author.reputation = calculate_reputation(post.author.reputation,
                               int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times']))
        post.author.save()

        reputation = Repute(user=post.author,
                   negative=int(REPUTATION_RULES['lose_by_flagged_lastrevision_5_times']),
                   question=question,
                   reputed_at=datetime.datetime.now(),
                   reputation_type=-7,
                   reputation=post.author.reputation)
        reputation.save()

        post.deleted = True
        #post.deleted_at = datetime.datetime.now()
        #post.deleted_by = Admin
        post.save()


@transaction.commit_on_success
def onAnswerAccept(answer, user):
    answer.accepted = True
    answer.accepted_at = datetime.datetime.now()
    answer.question.answer_accepted = True
    answer.save()
    answer.question.save()

    answer.author.reputation = calculate_reputation(answer.author.reputation,
                             int(REPUTATION_RULES['gain_by_answer_accepted']))
    answer.author.save()
    reputation = Repute(user=answer.author,
               positive=int(REPUTATION_RULES['gain_by_answer_accepted']),
               question=answer.question,
               reputed_at=datetime.datetime.now(),
               reputation_type=2,
               reputation=answer.author.reputation)
    reputation.save()

    user.reputation = calculate_reputation(user.reputation,
                    int(REPUTATION_RULES['gain_by_accepting_answer']))
    user.save()
    reputation = Repute(user=user,
               positive=int(REPUTATION_RULES['gain_by_accepting_answer']),
               question=answer.question,
               reputed_at=datetime.datetime.now(),
               reputation_type=3,
               reputation=user.reputation)
    reputation.save()

@transaction.commit_on_success
def onAnswerAcceptCanceled(answer, user):
    answer.accepted = False
    answer.accepted_at = None
    answer.question.answer_accepted = False
    answer.save()
    answer.question.save()

    answer.author.reputation = calculate_reputation(answer.author.reputation,
                             int(REPUTATION_RULES['lose_by_accepted_answer_cancled']))
    answer.author.save()
    reputation = Repute(user=answer.author,
               negative=int(REPUTATION_RULES['lose_by_accepted_answer_cancled']),
               question=answer.question,
               reputed_at=datetime.datetime.now(),
               reputation_type=-2,
               reputation=answer.author.reputation)
    reputation.save()

    user.reputation = calculate_reputation(user.reputation,
                    int(REPUTATION_RULES['lose_by_canceling_accepted_answer']))
    user.save()
    reputation = Repute(user=user,
               negative=int(REPUTATION_RULES['lose_by_canceling_accepted_answer']),
               question=answer.question,
               reputed_at=datetime.datetime.now(),
               reputation_type=-1,
               reputation=user.reputation)
    reputation.save()

@transaction.commit_on_success
def onUpVoted(vote, post, user):
    vote.save()

    post.vote_up_count = int(post.vote_up_count) + 1
    post.score = int(post.score) + 1
    post.save()

    if not post.wiki:
        author = post.author
        if Repute.objects.get_reputation_by_upvoted_today(author) <  int(REPUTATION_RULES['scope_per_day_by_upvotes']):
            author.reputation = calculate_reputation(author.reputation,
                              int(REPUTATION_RULES['gain_by_upvoted']))
            author.save()

            question = post
            if ContentType.objects.get_for_model(post) == answer_type:
                question = post.question

            reputation = Repute(user=author,
                       positive=int(REPUTATION_RULES['gain_by_upvoted']),
                       question=question,
                       reputed_at=datetime.datetime.now(),
                       reputation_type=1,
                       reputation=author.reputation)
            reputation.save()

@transaction.commit_on_success
def onUpVotedCanceled(vote, post, user):
    vote.delete()

    post.vote_up_count = int(post.vote_up_count) - 1
    if post.vote_up_count < 0:
        post.vote_up_count  = 0
    post.score = int(post.score) - 1
    post.save()

    if not post.wiki:
        author = post.author
        author.reputation = calculate_reputation(author.reputation,
                          int(REPUTATION_RULES['lose_by_upvote_canceled']))
        author.save()

        question = post
        if ContentType.objects.get_for_model(post) == answer_type:
            question = post.question

        reputation = Repute(user=author,
                   negative=int(REPUTATION_RULES['lose_by_upvote_canceled']),
                   question=question,
                   reputed_at=datetime.datetime.now(),
                   reputation_type=-8,
                   reputation=author.reputation)
        reputation.save()

@transaction.commit_on_success
def onDownVoted(vote, post, user):
    vote.save()

    post.vote_down_count = int(post.vote_down_count) + 1
    post.score = int(post.score) - 1
    post.save()

    if not post.wiki:
        author = post.author
        author.reputation = calculate_reputation(author.reputation,
                          int(REPUTATION_RULES['lose_by_downvoted']))
        author.save()

        question = post
        if ContentType.objects.get_for_model(post) == answer_type:
            question = post.question

        reputation = Repute(user=author,
                   negative=int(REPUTATION_RULES['lose_by_downvoted']),
                   question=question,
                   reputed_at=datetime.datetime.now(),
                   reputation_type=-3,
                   reputation=author.reputation)
        reputation.save()

        user.reputation = calculate_reputation(user.reputation,
                        int(REPUTATION_RULES['lose_by_downvoting']))
        user.save()

        reputation = Repute(user=user,
                   negative=int(REPUTATION_RULES['lose_by_downvoting']),
                   question=question,
                   reputed_at=datetime.datetime.now(),
                   reputation_type=-5,
                   reputation=user.reputation)
        reputation.save()

@transaction.commit_on_success
def onDownVotedCanceled(vote, post, user):
    vote.delete()

    post.vote_down_count = int(post.vote_down_count) - 1
    if post.vote_down_count < 0:
        post.vote_down_count  = 0
    post.score = post.score + 1
    post.save()

    if not post.wiki:
        author = post.author
        author.reputation = calculate_reputation(author.reputation,
                          int(REPUTATION_RULES['gain_by_downvote_canceled']))
        author.save()

        question = post
        if ContentType.objects.get_for_model(post) == answer_type:
            question = post.question

        reputation = Repute(user=author,
                   positive=int(REPUTATION_RULES['gain_by_downvote_canceled']),
                   question=question,
                   reputed_at=datetime.datetime.now(),
                   reputation_type=4,
                   reputation=author.reputation)
        reputation.save()

        user.reputation = calculate_reputation(user.reputation,
                        int(REPUTATION_RULES['gain_by_canceling_downvote']))
        user.save()

        reputation = Repute(user=user,
                   positive=int(REPUTATION_RULES['gain_by_canceling_downvote']),
                   question=question,
                   reputed_at=datetime.datetime.now(),
                   reputation_type=5,
                   reputation=user.reputation)
        reputation.save()

def onDeleteCanceled(post, user):
    post.deleted = False
    post.deleted_by = None 
    post.deleted_at = None 
    post.save()
    logging.debug('now restoring something')
    if isinstance(post,Answer):
        logging.debug('updated answer count on undelete, have %d' % post.question.answer_count)
        Question.objects.update_answer_count(post.question)
    elif isinstance(post,Question):
        for tag in list(post.tags.all()):
            if tag.used_count == 1 and tag.deleted:
                tag.deleted = False
                tag.deleted_by = None
                tag.deleted_at = None 
                tag.save()

def onDeleted(post, user):
    post.deleted = True
    post.deleted_by = user
    post.deleted_at = datetime.datetime.now()
    post.save()

    if isinstance(post, Question):
        for tag in list(post.tags.all()):
            if tag.used_count == 1:
                tag.deleted = True
                tag.deleted_by = user
                tag.deleted_at = datetime.datetime.now()
            else:
                tag.used_count = tag.used_count - 1 
            tag.save()

        answers = post.answers.all()
        if user == post.author:
            if len(answers) > 0:
                msg = _('Your question and all of it\'s answers have been deleted')
            else:
                msg = _('Your question has been deleted')
        else:
            if len(answers) > 0:
                msg = _('The question and all of it\'s answers have been deleted')
            else:
                msg = _('The question has been deleted')
        user.message_set.create(message=msg)
        logging.debug('posted a message %s' % msg)
        for answer in answers:
            onDeleted(answer, user)
    elif isinstance(post, Answer):
        Question.objects.update_answer_count(post.question)
        logging.debug('updated answer count to %d' % post.question.answer_count)
