from __future__ import print_function

"""
==============

The followig information about Open Graph 1.0 is copied verbatim from Facebook, and remains their copyright:

    https://developers.facebook.com/docs/opengraphprotocol/

    Example:

         <meta property="og:title" content="The Rock"/>


    The Open Graph protocol defines four required properties:

        og:title - The title of your object as it should appear within the graph, e.g., "The Rock".
        og:type - The type of your object, e.g., "movie". See the complete list of supported types.
        og:image - An image URL which should represent your object within the graph. The image must be at least 50px by 50px and have a maximum aspect ratio of 3:1. We support PNG, JPEG and GIF formats. You may include multiple og:image tags to associate multiple images with your page.
        og:url - The canonical URL of your object that will be used as its permanent ID in the graph, e.g., http://www.imdb.com/title/tt0117500/.

    In addition, we've extended the basic meta data to add a required field to connect your webpage with Facebook:

        fb:app_id - A Facebook Platform application ID that administers this page.

    It's also recommended that you include the following properties as well as these multi-part properties.

        og:site_name - A human-readable name for your site, e.g., "IMDb".
        og:description - A one to two sentence description of your page.

    ...

    To associate the page with your Facebook account, add the additional property fb:admins to your page with a comma-separated list of the user IDs or usernames of the Facebook accounts who own the page, e.g.:

        <meta property="fb:admins" content="USER_ID1,USER_ID2"/>

The following is taken from the 2.0 spec -- http://ogp.me/, and remains their copyright

Arrays
    If a tag can have multiple values, just put multiple versions of the same <meta> tag on your page. The first tag (from top to bottom) is given preference during conflicts.

        <meta property="og:image" content="http://example.com/rock.jpg" />
        <meta property="og:image" content="http://example.com/rock2.jpg" />

    Put structured properties after you declare their root tag. Whenever another root element is parsed, that structured property is considered to be done and another one is started.
"""

__VERSION__ = "0.3.1"

# stdlib
import datetime
import re

# pypi
from metadata_utils import html_attribute_escape
import six


# http://en.wikipedia.org/wiki/ISO_8601
regex_dates = {
    #  Date    2012-02-02
    "date": re.compile("^([0-9]{4})-(1[0-2]|0[1-9])-(3[0-1]|0[1-9]|[1-2][0-9])$"),
    # Ordinal date:    2012-033
    "ordinal_date": re.compile(
        "^([0-9]{4})-(36[0-6]|3[0-5][0-9]|[12][0-9]{2}|0[1-9][0-9]|00[1-9])$"
    ),
    # Date with week number:   2012-W05-4
    "week_number": re.compile("^([0-9]{4})-?W(5[0-3]|[1-4][0-9]|0[1-9])-?([1-7])$"),
    # Separate date and time in UTC:   2012-02-02 15:29Z
    "datetime, UTC": re.compile(
        "^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[0-1]|0[1-9]|[1-2][0-9])T(2[0-3]|[0-1][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z|[+-](?:2[0-3]|[0-1][0-9]):[0-5][0-9])?$"
    ),
}


# basically just testing that the string starts with http/https, and has some resemeblence of a domain on it.  after an optional trailing slash, i don't need to make this super accurate
regex_url = re.compile("""^http[s]?:\/\/[a-z0-9.\-]+[.][a-z]{2,4}\/?""")


og_properties = {
    "og:title": {
        "required": True,
        "description": 'The title of your object as it should appear within the graph, e.g., "The Rock".',
        "type": "string",
    },
    "og:type": {
        "required": True,
        "description": 'The type of your object, e.g., "movie". See the complete list of supported types.',
        "type": "string",
        "valid_types-1": {
            "activity": {"grouping": "Activities"},
            "sport": {"grouping": "Activities"},
            "bar": {"grouping": "Businesses"},
            "company": {"grouping": "Businesses"},
            "cafe": {"grouping": "Businesses"},
            "hotel": {"grouping": "Businesses"},
            "restaurant": {"grouping": "Businesses"},
            "cause": {"grouping": "Groups"},
            "sports_league": {"grouping": "Groups"},
            "sports_team": {"grouping": "Groups"},
            "band": {"grouping": "Organizations"},
            "government": {"grouping": "Organizations"},
            "non_profit": {"grouping": "Organizations"},
            "school": {"grouping": "Organizations"},
            "university": {"grouping": "Organizations"},
            "actor": {"grouping": "People"},
            "athlete": {"grouping": "People"},
            "author": {"grouping": "People "},
            "director": {"grouping": "People"},
            "musician": {"grouping": "People"},
            "politician": {"grouping": "People"},
            "public_figure": {"grouping": "People"},
            "city": {"grouping": "Places"},
            "country": {"grouping": "Places"},
            "landmark": {"grouping": "Places"},
            "state_province": {"grouping": "Places"},
            "album": {"grouping": "Products and Entertainment"},
            "book": {"grouping": "Products and Entertainment"},
            "drink": {"grouping": "Products and Entertainment"},
            "food": {"grouping": "Products and Entertainment"},
            "game": {"grouping": "Products and Entertainment"},
            "product": {"grouping": "Products and Entertainment"},
            "song": {"grouping": "Products and Entertainment"},
            "movie": {"grouping": "Products and Entertainment"},
            "tv_show": {"grouping": "Products and Entertainment"},
            "blog": {"grouping": "Websites "},
            "website": {"grouping": "Websites"},
            "article": {"grouping": "Websites"},
            "game.achievement": {
                "grouping": "Game",
                "properties": {
                    "game:points": {"description": "POINTS_FOR_ACHIEVEMENT"}
                },
            },
        },
        "valid_types-2": {
            "website": {"namespace": "http://ogp.me/ns/website#", "properties": {}},
            "article": {
                "namespace": "http://ogp.me/ns/article#",
                "properties": {
                    "article:published_time": {
                        "type": "datetime",
                        "description": "When the article was first published.",
                    },
                    "article:modified_time": {
                        "type": "datetime",
                        "description": "When the article was last changed.",
                    },
                    "article:expiration_time": {
                        "type": "datetime",
                        "description": "When the article is out of date after.",
                    },
                    "article:author": {
                        "type": "profile",
                        "description": "Writers of the article.",
                        "array_allowed": True,
                    },
                    "article:section": {
                        "type": "string",
                        "description": "A high-level section name. E.g. Technology",
                    },
                    "article:tag": {
                        "type": "string",
                        "description": "Tag words associated with this article.",
                        "array_allowed": True,
                    },
                },
            },
            "book": {
                "namespace": "http://ogp.me/ns/book#",
                "properties": {
                    "book:author": {
                        "type": "profile",
                        "description": "Who wrote this book.",
                        "array_allowed": True,
                    },
                    "book:isbn": {"type": "string", "description": "The ISBN"},
                    "book:release_date": {
                        "type": "datetime",
                        "description": "The date the book was released.",
                    },
                    "book:tag": {
                        "type": "string",
                        "description": "Tag words associated with this book.",
                        "array_allowed": True,
                    },
                },
            },
            "profile": {
                "namespace": "http://ogp.me/ns/profile#",
                "properties": {
                    "profile:first_name": {
                        "type": "string",
                        "description": "first name",
                    },
                    "profile:last_name": {"type": "string", "description": "last name"},
                    "profile:username": {
                        "type": "string",
                        "description": "A short unique string to identify them.",
                    },
                    "profile:gender": {
                        "type": "enum",
                        "enums": ["male", "female"],
                        "description": "Their gender",
                    },
                },
            },
            "video.movie": {
                "namespace": "http://ogp.me/ns/video#",
                "properties": {
                    "video:actor": {
                        "type": "profile",
                        "description": "actors in the movie",
                        "array_allowed": True,
                    },
                    "video:actor:role": {
                        "type": "string",
                        "description": "the role they played",
                    },
                    "video:director": {
                        "type": "profile",
                        "description": "directors of the movie",
                        "array_allowed": True,
                    },
                    "video:writer": {
                        "type": "profile",
                        "description": "writers of the movie",
                        "array_allowed": True,
                    },
                    "video:duration": {
                        "type": "integer",
                        "description": "The movie's length in seconds",
                    },
                    "video:release_date": {
                        "type": "datetime",
                        "description": "The date the movie was released",
                    },
                    "video:tag": {
                        "type": "string",
                        "description": "Tag words associated with this video.",
                        "array_allowed": True,
                    },
                },
            },
            "video.episode": {
                "namespace": "http://ogp.me/ns/video#",
                "properties": {
                    "video:actor": {
                        "type": "profile",
                        "description": "actors in the movie",
                        "array_allowed": True,
                    },
                    "video:actor:role": {
                        "type": "string",
                        "description": "the role they played",
                    },
                    "video:director": {
                        "type": "profile",
                        "description": "directors of the movie",
                        "array_allowed": True,
                    },
                    "video:writer": {
                        "type": "profile",
                        "description": "writers of the movie",
                        "array_allowed": True,
                    },
                    "video:duration": {
                        "type": "integer",
                        "description": "The movie's length in seconds",
                    },
                    "video:release_date": {
                        "type": "datetime",
                        "description": "The date the movie was released",
                    },
                    "video:tag": {
                        "type": "string",
                        "description": "Tag words associated with this video.",
                        "array_allowed": True,
                    },
                    "video:series": {
                        "type": "video.tv_show",
                        "description": "Which series this episode belongs to.",
                    },
                },
            },
            "video.tv_show": {
                "namespace": "http://ogp.me/ns/video#",
                "properties": {
                    "video:actor": {
                        "type": "profile",
                        "description": "actors in the movie",
                        "array_allowed": True,
                    },
                    "video:actor:role": {
                        "type": "string",
                        "description": "the role they played",
                    },
                    "video:director": {
                        "type": "profile",
                        "description": "directors of the movie",
                        "array_allowed": True,
                    },
                    "video:writer": {
                        "type": "profile",
                        "description": "writers of the movie",
                        "array_allowed": True,
                    },
                    "video:duration": {
                        "type": "integer",
                        "description": "The movie's length in seconds",
                    },
                    "video:release_date": {
                        "type": "datetime",
                        "description": "The date the movie was released",
                    },
                    "video:tag": {
                        "type": "string",
                        "description": "Tag words associated with this video.",
                        "array_allowed": True,
                    },
                },
            },
            "video.other": {
                "namespace": "http://ogp.me/ns/video#",
                "properties": {
                    "video:actor": {
                        "type": "profile",
                        "description": "actors in the movie",
                        "array_allowed": True,
                    },
                    "video:actor:role": {
                        "type": "string",
                        "description": "the role they played",
                    },
                    "video:director": {
                        "type": "profile",
                        "description": "directors of the movie",
                        "array_allowed": True,
                    },
                    "video:writer": {
                        "type": "profile",
                        "description": "writers of the movie",
                        "array_allowed": True,
                    },
                    "video:duration": {
                        "type": "integer",
                        "description": "The movie's length in seconds",
                    },
                    "video:release_date": {
                        "type": "datetime",
                        "description": "The date the movie was released",
                    },
                    "video:tag": {
                        "type": "string",
                        "description": "Tag words associated with this video.",
                        "array_allowed": True,
                    },
                },
            },
            "music.song": {
                "namespace": "http://ogp.me/ns/music#",
                "properties": {
                    "music:duration": {
                        "type": "integer",
                        "description": "The song's length in seconds",
                    },
                    "music:album": {
                        "type": "music.album",
                        "description": "The album this song is from.",
                        "array_allowed": True,
                    },
                    "music:album:disc": {
                        "type": "integer",
                        "description": "Which disc of the album this song is on",
                    },
                    "music:album:track": {
                        "type": "integer",
                        "description": "Which track this song is",
                    },
                    "music:musician": {
                        "type": "profile",
                        "description": "The musician that made this song",
                        "array_allowed": True,
                    },
                },
            },
            "music.album": {
                "namespace": "http://ogp.me/ns/music#",
                "properties": {
                    "music:song": {
                        "type": "music.song",
                        "description": "The song on this album.",
                    },
                    "music:song:disc": {
                        "type": "integer",
                        "description": "the disc the song is on for the album",
                    },
                    "music:song:track": {
                        "type": "integer",
                        "description": "the track number the song is on for the album",
                    },
                    "music:musician": {
                        "type": "profile",
                        "description": "The musician that made this song",
                    },
                    "music:release_date": {
                        "type": "datetime",
                        "description": "The date the album was released.",
                    },
                },
            },
            "music.playlist": {
                "namespace": "http://ogp.me/ns/music#",
                "properties": {
                    "music:song": {"type": "music.song", "description": "The song"},
                    "music:song:disc": {
                        "type": "integer",
                        "description": "the disc the song is on for the album",
                    },
                    "music:song:track": {
                        "type": "integer",
                        "description": "the track number the song is on for the album",
                    },
                    "music:creator": {
                        "type": "profile",
                        "description": "The creator of this playlist.",
                    },
                },
            },
            "music.radio_station": {
                "namespace": "http://ogp.me/ns/music#",
                "properties": {
                    "music:creator": {
                        "type": "profile",
                        "description": "The creator of this station.",
                    }
                },
            },
        },
    },
    "og:image": {
        "required": True,
        "type": "url",
        "description": "An image URL which should represent your object within the graph. The image must be at least 50px by 50px and have a maximum aspect ratio of 3:1. We support PNG, JPEG and GIF formats. You may include multiple og:image tags to associate multiple images with your page.",
        "properties": {
            "og:image:url": {"description": "Identical to og:image.", "type": "url"},
            "og:image:secure_url": {
                "description": " An alternate url to use if the webpage requires HTTPS.",
                "type": "url",
            },
            "og:image:type": {
                "description": "A MIME type for this image.",
                "type": "string",
            },
            "og:image:width": {
                "description": "The number of pixels wide.",
                "type": "integer",
            },
            "og:image:height": {
                "description": "The number of pixels high.",
                "type": "integer",
            },
        },
    },
    "og:url": {
        "required": True,
        "description": "The canonical URL of your object that will be used as its permanent ID in the graph, e.g., http://www.imdb.com/title/tt0117500/",
        "type": "url",
    },
    "og:site_name": {
        "required": False,
        "description": 'A human-readable name for your site, e.g., "IMDb".',
        "type": "string",
    },
    "og:description": {
        "required": False,
        "description": "A one to two sentence description of your page.",
        "type": "string",
    },
    "og:isbn": {
        "required": False,
        "description": "For products which have a UPC code or ISBN number, you can specify them using the og:upc and og:isbn properties. These properties help uniquely identify products.",
        "type": "string",
    },
    "og:upc": {
        "required": False,
        "description": "For products which have a UPC code or ISBN number, you can specify them using the og:upc and og:isbn properties. These properties help uniquely identify products.",
        "type": "string",
    },
    "og:audio": {
        "required": False,
        "description": "A URL to an audio file to accompany this object.",
        "type": "url",
        "properties": {
            "og:audio:secure_url": {
                "description": " An alternate url to use if the webpage requires HTTPS.",
                "type": "url",
            },
            "og:audio:type": {
                "description": "A MIME type for this audio.",
                "type": "string",
            },
            "og:audio:title": {
                "description": "NOT IN 2.0 SPEC -- song title",
                "type": "string",
            },
            "og:audio:artist": {
                "description": "NOT IN 2.0 SPEC -- song artist",
                "type": "string",
            },
            "og:audio:album": {
                "description": "NOT IN 2.0 SPEC -- song album",
                "type": "string",
            },
        },
    },
    "og:determiner": {
        "required": False,
        "description": """The word that appears before this object's title in a sentence. An enum of (a, an, the, "", auto). If auto is chosen, the consumer of your data should chose between "a" or "an". Default is "" (blank).""",
        "type": "enum",
        "enums": ("a", "an", "the", "", "auto"),
    },
    "og:locale": {
        "required": False,
        "description": """The locale these tags are marked up in. Of the format language_TERRITORY. Default is en_US.""",
        "type": "string",
    },
    "og:locale:alternate": {
        "required": False,
        "description": """An array of other locales this page is available in..""",
        "type": "string",
        "array_allowed": True,
    },
    "og:video": {
        "required": False,
        "description": "A URL to a video file that complements this object. set content to url of video file. You may specify more than one og:video. If you specify more than one og:video, then og:video:type is required for each video. You must include a valid og:image for your video to be displayed in the news feed.",
        "properties": {
            "og:video:secure_url": {
                "description": " An alternate url to use if the webpage requires HTTPS.",
                "type": "url",
            },
            "og:video:type": {
                "description": "A MIME type for this video.",
                "type": "string",
            },
            "og:video:width": {
                "description": "The number of pixels wide.",
                "type": "integer",
            },
            "og:video:height": {
                "description": "The number of pixels high.",
                "type": "integer",
            },
        },
    },
}
facebook_extensions = {
    "fb:admins": {
        "required": False,
        "description": 'To associate the page with your Facebook account, add the additional property fb:admins to your page with a comma-separated list of the user IDs or usernames of the Facebook accounts who own the page, e.g.: <meta property="fb:admins" content="USER_ID1,USER_ID2"/>',
    },
    "fb:app_id": {
        "required": False,
        "description": "A Facebook Platform application ID that administers this page.",
    },
}


def validate_item(info_dict, value):
    if info_dict["type"] == "string":
        if isinstance(value, six.string_types):
            return True
        return False

    elif info_dict["type"] == "boolean":
        try:
            success = (0, 1, "true", "false").index(value)
            return True
        except ValueError:
            return False

    elif info_dict["type"] == "enum":
        try:
            success = info_dict.enums.index(value)
            return True
        except ValueError:
            return False

    elif info_dict["type"] == "integer":
        try:
            if isinstance(value, int):
                return True
            else:
                # coercing an int(value) into a string should catch utf8&string types, and fail on floats
                # this can raise a valueerror though
                if value == "%s" % int(value):
                    return True
        except ValueError:
            return False

    elif info_dict["type"] == "datetime":
        if isinstance(value, (datetime.date, datetime.datetime)):
            return True
        if isinstance(value, six.string_types):
            for test in regex_dates:
                if re.match(regex_dates[test], value):
                    return True
            return False

    elif info_dict["type"] == "url":
        if re.match(regex_url, value):
            return True
        return False

    elif info_dict["type"] == "profile":
        # TO DO
        # this involves looking for other fields.
        return True

    return False


class OpenGraphItem(object):
    _data = None
    _errors = None

    def __init__(self, sets=None):
        self._data = {}
        self._errors = {}
        if sets:
            self.set_many(sets)

    def set_many(self, pairs):
        for pair in pairs:
            (f, v) = pair
            self.set(f, v)

    def set(self, field, value, append=False):
        if not append:
            self._data[field] = value
        else:
            if field not in self._data:
                self._data[field] = []
            else:
                if not isinstance(self._data[field], list):
                    self._data[field] = [self._data[field]]
            self._data[field].append(value)

    def validate(self, facebook=False, schema1=False, schema2=True):
        errors = {"critical": {}, "recommended": {}}

        def _errror(level, item, message):
            # if level not in errors:
            #    errors[level]= {}
            errors[level][item] = message

        for i in og_properties:
            # make sure that everything in og_properties which is required is present
            if og_properties[i]["required"] and (i not in self._data):
                _errror("critical", i, "Missing Required Element")
            elif og_properties[i]["required"] and (i in self._data):
                if not validate_item(og_properties[i], self._data[i]):
                    _errror("critical", i, "Required Element does not validate")
            else:
                if i in self._data:
                    if not validate_item(og_properties[i], self._data[i]):
                        _errror(
                            "recommended", i, "non-required Element does not validate"
                        )

        if (
            "og:type" not in errors["critical"]
            and "og:type" not in errors["recommended"]
        ):
            _error = None
            if schema1:
                if (
                    self._data["og:type"]
                    not in og_properties["og:type"]["valid_types-1"]
                ):
                    _errror("critical", "og:type", "Invalid og:type")
            elif schema2:
                if (
                    self._data["og:type"]
                    not in og_properties["og:type"]["valid_types-2"]
                ):
                    _errror("critical", "og:type", "Invalid og:type")
                for subtype in og_properties["og:type"]["valid_types-2"][
                    self._data["og:type"]
                ]["properties"]:
                    info = og_properties["og:type"]["valid_types-2"][
                        self._data["og:type"]
                    ]
                    info_dict = info["properties"][subtype]
                    error = None
                    value = None
                    if subtype in self._data:
                        value = self._data[subtype]
                    if "required" in info and info["required"]:
                        if subtype not in og_properties:
                            _errror(
                                "critical", "%s" % subtype, "Missing required subtype"
                            )
                        elif not validate_item(info_dict, value):
                            _errror(
                                "critical",
                                "%s" % subtype,
                                "Required subtype does not validate correctly",
                            )
                    else:
                        if subtype not in self._data:
                            _errror(
                                "recommended",
                                "%s" % subtype,
                                "non-required subtype not included",
                            )
                        else:
                            if not validate_item(info_dict, value):
                                _errror(
                                    "recommended",
                                    "%s" % subtype,
                                    "non-required subtype does not validate correctly",
                                )

        self._errors = errors
        if errors["critical"]:
            return False
        return True

    def errors(self):
        return self._errors

    def as_html(self, debug=False):
        output = []
        _keys = sorted(self._data.keys())
        for k in _keys:
            v = self._data[k]
            _error = u""
            if debug:
                if k in self._errors["critical"]:
                    _error = u' critical-error="%s"' % html_attribute_escape(
                        self._errors["critical"][k]
                    )
                elif k in self._errors["recommended"]:
                    _error = u' recommended-error="%s"' % html_attribute_escape(
                        self._errors["recommended"][k]
                    )
            if isinstance(v, list):
                for i in v:
                    output.append(
                        u"""<meta property="%s" content="%s"%s/>"""
                        % (html_attribute_escape(k), html_attribute_escape(i), _error)
                    )
            else:
                output.append(
                    u"""<meta property="%s" content="%s"%s/>"""
                    % (html_attribute_escape(k), html_attribute_escape(v), _error)
                )
        output = u"\n".join(output)
        return output


if __name__ == "__main__":
    a = OpenGraphItem()
    a.set("og:title", "MyWebsite")
    a.set("og:type", "article")
    a.set_many(
        (
            ("og:url", "http://f.me"),
            ("og:image", "http://f.me/a.png"),
            ("article:author", "abc"),
            ("article:published_time", "2012-01-10"),
        )
    )
    status = a.validate()
    if status:
        print("object ok")
    else:
        print("object not ok")
    print(a.as_html(debug=True))
