from bs4 import BeautifulSoup
from strict_rfc3339 import rfc3339_to_timestamp

QUESTION_TYPE = 1
ANSWER_TYPE = 2
NEWLINE = '\n'


def is_multiline_code(code_tag):
    """
    Delete <code> tags that contain multiple lines.

    These are code tags that contain large amounts of source code.
    Sometimes <code> tags are used to describe a single technical word,
    and there would be no newline there.

    We want to keep the technical words in our analysis but remove the
    large amount of source code.
    """
    text = code_tag.get_text()
    return NEWLINE in text


def parse_raw_body(raw_body):
    """Strip code tags out of question / answer bodies."""
    soup = BeautifulSoup(raw_body, 'lxml')
    for code_tag in soup.find_all('code'):
        if is_multiline_code(code_tag):
            code_tag.extract()
    return soup.get_text()


def safe_int(something):
    """
    Turn a value into an integer, or None.

    Useful for data cases where we might have a number in a string
    or None.
    """
    try:
        return int(something)
    except (TypeError, ValueError):
        return None


def safe_time(string_time):
    """
    Safely convert StackExchange timestamps to Unix timestamp floats.

    StackExchange timestamps could be None for events that did not happen
    yet, so we guard against that.

    Additionally, StackExchange timestamps are slightly incompatible with
    the RFC3339 standard, because the timezone (which is UTC) is not specified.
    We fix that in this function.
    """
    if string_time:
        return rfc3339_to_timestamp(string_time + 'Z')


class StackItem(object):
    """Base class for StackExchange questions and answers."""

    __slots__ = [
        'body',
        'comment_count',
        'creation_date',
        'last_activity_date',
        'last_edit_date',
        'last_editor_user_id',
        'owner_user_id',
        'post_id',
        'score'
    ]

    def __init__(
            self,
            body,
            comment_count,
            creation_date,
            last_activity_date,
            last_edit_date,
            last_editor_user_id,
            owner_user_id,
            post_id,
            score
    ):
        self.body = parse_raw_body(body)
        self.comment_count = int(comment_count)
        self.creation_date = safe_time(creation_date)
        self.last_activity_date = safe_time(last_activity_date)
        self.last_edit_date = safe_time(last_edit_date)
        self.last_editor_user_id = safe_int(last_editor_user_id)
        self.owner_user_id = safe_int(owner_user_id)
        self.post_id = int(post_id)
        self.score = int(score)
        self.post_id = int(post_id)
        self.creation_date = safe_time(creation_date)
        self.score = int(score)


class Question(StackItem):
    """StackExchange question."""

    __slots__ = [
        'accepted_answer_id',
        'answer_count',
        'closed_date',
        'favorite_count',
        'tags',
        'title',
        'view_count',
        'answers'
    ]

    def __init__(
            self,
            accepted_answer_id,
            answer_count,
            closed_date,
            favorite_count,
            tags,
            title,
            view_count,
            **kwargs
    ):
        super(Question, self).__init__(**kwargs)
        self.accepted_answer_id = safe_int(accepted_answer_id)
        self.answer_count = int(answer_count)
        self.closed_date = safe_time(closed_date)
        # assume that favorite_count==None means 0
        self.favorite_count = safe_int(favorite_count) or 0
        self.tags = self.make_tag_list(tags)
        self.title = title
        self.view_count = int(view_count)
        self.answers = []

    def add_answer(self, answer):
        self.answers.append(answer)

    def is_processing_complete(self):
        """Returns True if we have collected all of the answers to
        the question.
        """
        return len(self.answers) == self.answer_count

    def make_tag_list(self, str_tags):
        """Turn the string format of tags into a list."""
        return str_tags.replace('<', '').split('>')[:-1]

    def __repr__(self):
        return repr(self.title)[:100]


class Answer(StackItem):
    """StackExchange answer. This lives in a list at Question.answers."""

    __slots__ = ['parent_id']

    def __init__(
            self,
            parent_id,
            **kwargs
    ):
        self.parent_id = int(parent_id)
        super(Answer, self).__init__(**kwargs)

    def __repr__(self):
        return repr(self.body)[:100]
