# -*- coding: utf-8 -*-
# 'future imports'
from __future__ import unicode_literals

# 'Standard Library'
import json

from calendar import timegm

try:
    from rfc822 import parsedate
except ImportError:
    from email.utils import parsedate


class TwitterModel(object):

    """ Base class from which all twitter models will inherit. """

    def __init__(self, **kwargs):
        self.param_defaults = {}

    def __str__(self):
        """ Returns a string representation of TwitterModel. By default
        this is the same as AsJsonString(). """
        return self.AsJsonString()

    def __eq__(self, other):
        return other and self.AsDict() == other.AsDict()

    def __ne__(self, other):
        return not self.__eq__(other)

    def __hash__(self):
        if hasattr(self, "id"):
            return hash(self.id)
        else:
            raise TypeError(
                "unhashable type: {} (no id attribute)".format(type(self))
            )

    def AsJsonString(self, ensure_ascii=True):
        """ Returns the TwitterModel as a JSON string based on key/value
        pairs returned from the AsDict() method. """
        return json.dumps(
            self.AsDict(), ensure_ascii=ensure_ascii, sort_keys=True
        )

    def AsDict(self):
        """ Create a dictionary representation of the object. Please see inline
        comments on construction when dictionaries contain TwitterModels. """
        data = {}

        for (key, value) in self.param_defaults.items():

            # If the value is a list, we need to create a list to hold the
            # dicts created by an object supporting the AsDict() method,
            # i.e., if it inherits from TwitterModel. If the item in the list
            # doesn't support the AsDict() method, then we assign the value
            # directly. An example being a list of Media objects contained
            # within a Status object.
            if isinstance(getattr(self, key, None), (list, tuple, set)):
                data[key] = list()
                for subobj in getattr(self, key, None):
                    if getattr(subobj, "AsDict", None):
                        data[key].append(subobj.AsDict())
                    else:
                        data[key].append(subobj)

            # Not a list, *but still a subclass of TwitterModel* and
            # and we can assign the data[key] directly with the AsDict()
            # method of the object. An example being a Status object contained
            # within a User object.
            elif getattr(getattr(self, key, None), "AsDict", None):
                data[key] = getattr(self, key).AsDict()

            # If the value doesn't have an AsDict() method, i.e., it's not
            # something that subclasses TwitterModel, then we can use direct
            # assigment.
            elif getattr(self, key, None):
                data[key] = getattr(self, key, None)
        return data

    @classmethod
    def NewFromJsonDict(cls, data, **kwargs):
        """ Create a new instance based on a JSON dict. Any kwargs should be
        supplied by the inherited, calling class.

        Args:
            data: A JSON dict, as converted from the JSON in the twitter API.

        """

        json_data = data.copy()
        if kwargs:
            for key, val in kwargs.items():
                json_data[key] = val

        c = cls(**json_data)
        c._json = data
        return c


class Media(TwitterModel):

    """A class representing the Media component of a tweet. """

    def __init__(self, **kwargs):
        self.param_defaults = {
            "display_url": None,
            "expanded_url": None,
            "ext_alt_text": None,
            "id": None,
            "media_url": None,
            "media_url_https": None,
            "sizes": None,
            "type": None,
            "url": None,
            "video_info": None,
        }

        for (param, default) in self.param_defaults.items():
            setattr(self, param, kwargs.get(param, default))

    def __repr__(self):
        return "Media(ID={media_id}, Type={media_type}, DisplayURL='{url}')".format(
            media_id=self.id, media_type=self.type, url=self.display_url
        )


class List(TwitterModel):

    """A class representing the List structure used by the twitter API. """

    def __init__(self, **kwargs):
        self.param_defaults = {
            "description": None,
            "following": None,
            "full_name": None,
            "id": None,
            "member_count": None,
            "mode": None,
            "name": None,
            "slug": None,
            "subscriber_count": None,
            "uri": None,
            "user": None,
        }

        for (param, default) in self.param_defaults.items():
            setattr(self, param, kwargs.get(param, default))

        if "user" in kwargs:
            self.user = User.NewFromJsonDict(kwargs.get("user"))

    def __repr__(self):
        return "List(ID={list_id}, FullName={full_name!r}, Slug={slug}, User={user})".format(
            list_id=self.id,
            full_name=self.full_name,
            slug=self.slug,
            user=self.user.screen_name,
        )


class Category(TwitterModel):

    """A class representing the suggested user category structure. """

    def __init__(self, **kwargs):
        self.param_defaults = {
            "name": None,
            "size": None,
            "slug": None,
        }

        for (param, default) in self.param_defaults.items():
            setattr(self, param, kwargs.get(param, default))

    def __repr__(self):
        return "Category(Name={name!r}, Slug={slug}, Size={size})".format(
            name=self.name, slug=self.slug, size=self.size
        )


class DirectMessage(TwitterModel):

    """A class representing a Direct Message. """

    def __init__(self, **kwargs):
        self.param_defaults = {
            "created_at": None,
            "id": None,
            "recipient_id": None,
            "sender_id": None,
            "text": None,
        }

        for (param, default) in self.param_defaults.items():
            setattr(self, param, kwargs.get(param, default))

    def __repr__(self):
        if self.text and len(self.text) > 140:
            text = "{text}[...]".format(text=self.text[:140])
        else:
            text = self.text
        return "DirectMessage(ID={dm_id}, Sender={sender}, Created={time}, Text='{text!r}')".format(
            dm_id=self.id,
            sender=self.sender_id,
            time=self.created_at,
            text=text,
        )


class Trend(TwitterModel):

    """ A class representing a trending topic. """

    def __init__(self, **kwargs):
        self.param_defaults = {
            "events": None,
            "name": None,
            "promoted_content": None,
            "query": None,
            "timestamp": None,
            "url": None,
            "tweet_volume": None,
        }

        for (param, default) in self.param_defaults.items():
            setattr(self, param, kwargs.get(param, default))

    def __repr__(self):
        return "Trend(Name={0!r}, Time={1}, URL={2})".format(
            self.name, self.timestamp, self.url
        )

    @property
    def volume(self):
        return self.tweet_volume


class Hashtag(TwitterModel):

    """ A class representing a twitter hashtag. """

    def __init__(self, **kwargs):
        self.param_defaults = {"text": None}

        for (param, default) in self.param_defaults.items():
            setattr(self, param, kwargs.get(param, default))

    def __repr__(self):
        return "Hashtag(Text={text!r})".format(text=self.text)


class Url(TwitterModel):

    """ A class representing an URL contained in a tweet. """

    def __init__(self, **kwargs):
        self.param_defaults = {"expanded_url": None, "url": None}

        for (param, default) in self.param_defaults.items():
            setattr(self, param, kwargs.get(param, default))

    def __repr__(self):
        return "URL(URL={url}, ExpandedURL={eurl})".format(
            url=self.url, eurl=self.expanded_url
        )


class UserStatus(TwitterModel):

    """ A class representing the UserStatus structure. This is an abbreviated
    form of the twitter.User object. """

    _connections = {
        "following": False,
        "followed_by": False,
        "following_received": False,
        "following_requested": False,
        "blocking": False,
        "muting": False,
    }

    def __init__(self, **kwargs):
        self.param_defaults = {
            "blocking": False,
            "followed_by": False,
            "following": False,
            "following_received": False,
            "following_requested": False,
            "id": None,
            "id_str": None,
            "muting": False,
            "name": None,
            "screen_name": None,
        }

        for (param, default) in self.param_defaults.items():
            setattr(self, param, kwargs.get(param, default))

        if "connections" in kwargs:
            for param in self._connections:
                if param in kwargs["connections"]:
                    setattr(self, param, True)

    @property
    def connections(self):
        return {
            "following": self.following,
            "followed_by": self.followed_by,
            "following_received": self.following_received,
            "following_requested": self.following_requested,
            "blocking": self.blocking,
            "muting": self.muting,
        }

    def __repr__(self):
        connections = [
            param for param in self.connections if getattr(self, param)
        ]
        return "UserStatus(ID={uid}, ScreenName={sn}, Connections=[{conn}])".format(
            uid=self.id, sn=self.screen_name, conn=", ".join(connections)
        )


class User(TwitterModel):

    """A class representing the User structure. """

    def __init__(self, **kwargs):
        self.param_defaults = {
            "contributors_enabled": None,
            "created_at": None,
            "default_profile": None,
            "default_profile_image": None,
            "description": None,
            "email": None,
            "favourites_count": None,
            "followers_count": None,
            "following": None,
            "friends_count": None,
            "geo_enabled": None,
            "id": None,
            "id_str": None,
            "lang": None,
            "listed_count": None,
            "location": None,
            "name": None,
            "notifications": None,
            "profile_background_color": None,
            "profile_background_image_url": None,
            "profile_background_image_url_https": None,
            "profile_background_tile": None,
            "profile_banner_url": None,
            "profile_image_url": None,
            "profile_image_url_https": None,
            "profile_link_color": None,
            "profile_sidebar_border_color": None,
            "profile_sidebar_fill_color": None,
            "profile_text_color": None,
            "profile_use_background_image": None,
            "protected": None,
            "screen_name": None,
            "status": None,
            "statuses_count": None,
            "time_zone": None,
            "url": None,
            "utc_offset": None,
            "verified": None,
            "withheld_in_countries": None,
            "withheld_scope": None,
        }

        for (param, default) in self.param_defaults.items():
            setattr(self, param, kwargs.get(param, default))

    def __repr__(self):
        return "User(ID={uid}, ScreenName={sn})".format(
            uid=self.id, sn=self.screen_name
        )

    @classmethod
    def NewFromJsonDict(cls, data, **kwargs):
        from twitter import Status

        if data.get("status", None):
            status = Status.NewFromJsonDict(data.get("status"))
            return super(cls, cls).NewFromJsonDict(data=data, status=status)
        else:
            return super(cls, cls).NewFromJsonDict(data=data)


class Status(TwitterModel):
    """A class representing the Status structure used by the twitter API.
    """

    def __init__(self, **kwargs):
        self.param_defaults = {
            "contributors": None,
            "coordinates": None,
            "created_at": None,
            "current_user_retweet": None,
            "favorite_count": None,
            "favorited": None,
            "full_text": None,
            "geo": None,
            "hashtags": None,
            "id": None,
            "id_str": None,
            "in_reply_to_screen_name": None,
            "in_reply_to_status_id": None,
            "in_reply_to_user_id": None,
            "lang": None,
            "location": None,
            "media": None,
            "place": None,
            "possibly_sensitive": None,
            "quoted_status": None,
            "quoted_status_id": None,
            "quoted_status_id_str": None,
            "retweet_count": None,
            "retweeted": None,
            "retweeted_status": None,
            "scopes": None,
            "source": None,
            "text": None,
            "truncated": None,
            "urls": None,
            "user": None,
            "user_mentions": None,
            "withheld_copyright": None,
            "withheld_in_countries": None,
            "withheld_scope": None,
        }

        for (param, default) in self.param_defaults.items():
            setattr(self, param, kwargs.get(param, default))

        if kwargs.get("full_text", None):
            self.tweet_mode = "extended"
        else:
            self.tweet_mode = "compatibility"

    @property
    def created_at_in_seconds(self):
        """ Get the time this status message was posted, in seconds since
        the epoch (1 Jan 1970).

        Returns:
            int: The time this status message was posted, in seconds since
            the epoch.
        """
        return timegm(parsedate(self.created_at))

    def __repr__(self):
        """ A string representation of this twitter.Status instance.
        The return value is the ID of status, username and datetime.

        Returns:
            string: A string representation of this twitter.Status instance with
            the ID of status, username and datetime.
        """
        if self.tweet_mode == "extended":
            text = self.full_text
        else:
            text = self.text
        if self.user:
            return "Status(ID={0}, ScreenName={1}, Created={2}, Text={3!r})".format(
                self.id, self.user.screen_name, self.created_at, text
            )
        else:
            return "Status(ID={0}, Created={1}, Text={2!r})".format(
                self.id, self.created_at, text
            )

    @classmethod
    def NewFromJsonDict(cls, data, **kwargs):
        """ Create a new instance based on a JSON dict.

        Args:
            data: A JSON dict, as converted from the JSON in the twitter API

        Returns:
            A twitter.Status instance
        """
        current_user_retweet = None
        hashtags = None
        media = None
        quoted_status = None
        retweeted_status = None
        urls = None
        user = None
        user_mentions = None

        # for loading extended tweets from the streaming API.
        if "extended_tweet" in data:
            for k, v in data["extended_tweet"].items():
                data[k] = v

        if "user" in data:
            user = User.NewFromJsonDict(data["user"])
        if "retweeted_status" in data:
            retweeted_status = Status.NewFromJsonDict(data["retweeted_status"])
        if "current_user_retweet" in data:
            current_user_retweet = data["current_user_retweet"]["id"]
        if "quoted_status" in data:
            quoted_status = Status.NewFromJsonDict(data.get("quoted_status"))

        if "entities" in data:
            if "urls" in data["entities"]:
                urls = [
                    Url.NewFromJsonDict(u) for u in data["entities"]["urls"]
                ]
            if "user_mentions" in data["entities"]:
                user_mentions = [
                    User.NewFromJsonDict(u)
                    for u in data["entities"]["user_mentions"]
                ]
            if "hashtags" in data["entities"]:
                hashtags = [
                    Hashtag.NewFromJsonDict(h)
                    for h in data["entities"]["hashtags"]
                ]
            if "media" in data["entities"]:
                media = [
                    Media.NewFromJsonDict(m) for m in data["entities"]["media"]
                ]

        # the new extended entities
        if "extended_entities" in data:
            if "media" in data["extended_entities"]:
                media = [
                    Media.NewFromJsonDict(m)
                    for m in data["extended_entities"]["media"]
                ]

        return super(cls, cls).NewFromJsonDict(
            data=data,
            current_user_retweet=current_user_retweet,
            hashtags=hashtags,
            media=media,
            quoted_status=quoted_status,
            retweeted_status=retweeted_status,
            urls=urls,
            user=user,
            user_mentions=user_mentions,
        )
