# -*- coding: utf8 -*-
import functools
from datetime import datetime, timedelta
from os import path
from jinja2 import Markup


# For demo purposes
IMAGE_API_URL = 'http://gq-images.condecdn.net'
ALL_MONTHS_INDEX = {
    1: 'january',
    2: 'february',
    3: 'march',
    4: 'april',
    5: 'may',
    6: 'june',
    7: 'july',
    8: 'august',
    9: 'september',
    10: 'october',
    11: 'november',
    12: 'december'
}


"""
LANDSCAPE/PORTRAIT STANDARD
- 400 [Mobile basic card & feature] - 405x270
- 800 [Feature][400][2x] - 810x540
- 1000 [Home page super feature] - 1020x680
- 1600 [Hero image instead of 1440][800](2x) - 1620x1080

LANDSCAPE/PORTRAIT RETINA
- 2000 [1000](2x) - 2040x1360
- 3200 [1600](2x) - 3240x2160
"""
SIZES_LANDSCAPE = SIZES_PORTRAIT = [(405,270), (810,540), (1020,680),
    (1620,1080)]
SIZES_LANDSCAPE_RETINA = SIZES_PORTRAIT_RETINA = [(2040,1360), (3240,2160)]



"""
SQUARE STANDARD
- 100 [Mobile most recent, media block] - 100x100
- 200 [Most recent, media block][2x] - 200x200
- 300 [Shows news list] - 300x300
- 400 [200](2x) - 400x400
- 600 [300](2x) - 600x600
- 900 [Home page super feature tablet] - 900x900

SQUARE RETINA
- 1800 [900](2x) - 1800x1800
"""
SIZES_SQUARE = [(100,100), (200,200), (300,300), (400,400), (600,600),
    (900,900)]
SIZES_SQUARE_RETINA = [(1800,1800)]


def _get_height(width, orientation):
    '''Calculates the height based on the width and orientation'''

    accepted_orientations = ['landscape', 'portrait', 'square']
    if not orientation in accepted_orientations:
        return 0
    if orientation == 'square':
        return width
    if orientation == 'landscape':
        return width * .666666667
    if orientation == 'portrait':
        return width / .666666667
    return 0



def _import_svg_icon(svg_filename=None):
    '''Reads the svg file and returns the contents'''

    assert svg_filename is not None, 'svg_filename cannot be equal to None'
    with open(svg_filename) as f:
        return f.read()



def _is_too_big(width, crop_width, max_width):
    if crop_width > width:
        return True
    if max_width and max_width > width:
        return True
    return False



def returns_markup(func):
    '''Decorator for functions which should be marked as returning markup (i.e.
       a string which should not be escaped in the template).'''
    # ***
    # NOTE: Returns 'safe' markup, ie, non-escaped!
    # ***
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        if result is not None:
            result = Markup(result)
        return result
    return wrapper



def create_crops(image, image_sources=None, landscape=False, portrait=False,
    square=False, retina=False, max_width=None):


    if not any([image_sources, landscape, portrait, square]):
        raise ValueError(
            'image sources, landscape, portrait or square was not set')

    crops = {}

    if image_sources:
        crops = get_image_source_crops(
            image, image_sources, retina=retina,max_width=max_width)
    else:
        if landscape:
            crops['landscape'] = get_landscape_crops(image, retina=retina,
                max_width=max_width)
        if portrait:
            crops['portrait'] = get_portrait_crops(image, retina=retina,
                max_width=max_width)
        if square:
            crops['square'] = get_square_crops(image, retina=retina,
                max_width=max_width)

        # If the user only want one sizes, just return that one
        if len(crops.keys()) == 1:
            return crops.popitem()[1]

    # Return the whole thing
    return crops



def create_fake_crop(**kwargs):
    '''Returns a fake crop object that matches the api'''

    return {
        'content_type': kwargs.get('content_type', 'unknown'),
        'crop_type': kwargs.get('crop_type', 'unknown'),
        'filesize_bytes': kwargs.get('filesize_bytes', 'unknown'),
        'height': kwargs.get('height', 0),
        'orientation': kwargs.get('orientation', 'unknown'),
        'public_url': kwargs.get('public_url', ''),
        'uid': kwargs.get('uid', ''),
        'width': kwargs.get('width', 0)
    }


def create_picture_sources(crops, breakpoints):

    if not crops:
        raise TypeError('crops was not set')


    def _create_source(media, url, breakpoint_width):
        return {
            'breakpoint_width': breakpoint_width,
            'image': url,
            'media': media
        }


    def _find_image(width):
        image = None
        for crop in crops:
            if not image or (
                crop['width'] <= width and crop['width'] > image['width']):
                image = crop
            else:
                break
        return image

    sources = []


    # If the crops is a dict, merge all the keys to one list
    try:
        tmp = []
        for orientation in crops:
            for item in crops[orientation]:
                tmp.append(item)
        crops = tmp
    except TypeError:
        pass


    if breakpoints:
        for breakpoint in breakpoints:
            # No more crops from previous extraction
            if len(crops) == 0:
                break
            # Find image closest to breakpoint[1]
            image = _find_image(breakpoint[1])
            # Remove closest from crops
            crops.remove(image)
            # Create source
            source = _create_source('max-width: %spx' % breakpoint[0], image,
                breakpoint[0])
            # Append source
            sources.append(source)
    else:
        for crop in crops:
            # Create source
            source = _create_source('max-width: %spx' % crop['width'], crop,
                crop['width'])
            # Append source
            sources.append(source)


    # Update the last source to be min-width instead of max-width
    if len(sources) > 1:
        sources[-1]['media'] = 'min-width: %spx' % sources[-2]['breakpoint_width']
    else:
        sources[-1]['media'] = 'min-width: %spx' % sources[-1]['breakpoint_width']

    return sources



def fake_portrait_crop():
    '''Returns a fake portrait crop'''

    return create_fake_crop(content_type='image/png', orientation='portrait',
        height=600, public_url=image_fail_portrait(None), width=400)



def fake_landscape_crop():
    '''Returns a fake landscape crop'''

    return create_fake_crop(content_type='image/png', orientation='landscape',
        height=400, public_url=image_fail_landscape(None), width=600)



def fake_square_crop():
    '''Returns a fake square crop'''

    return create_fake_crop(content_type='image/png', orientation='square',
        height=400, public_url=image_fail_square(None), width=400)



def get_article_date(article):
    '''Returns the string date to use for the article. If no display_date is
       set, use the published_date'''

    if article['display_date']:
        return article['display_date']
    return article['published_date']


def get_card_switcher_sources(sources, switch_width=600):

    def _sort_sources(a, b):
        a_orientation = get_image_orientation(a['image'])
        b_orientation = get_image_orientation(b['image'])

        if a_orientation == 'square' and b_orientation == 'square':
            return a['breakpoint_width'] - b['breakpoint_width']

        if a_orientation == 'square':
            return -1

        if b_orientation == 'square':
            return 1

        return 0

    def _filter_squares(source):
        if not get_image_orientation(source['image']) == 'square':
            return True
        return source['breakpoint_width'] <= switch_width

    # Remove any squares bigger than 600
    sources = filter(_filter_squares, sources)

    # Sort everything so squares first then landscapes
    sources = sorted(sources, cmp=_sort_sources)

    # Set last square to change at 600
    for index, source in enumerate(reversed(sources)):
        if get_image_orientation(source['image']) == 'square':
            sources[len(sources) - 1 - index]['breakpoint_width'] = switch_width
            sources[len(sources) - 1 - index]['media'] = 'max-width: {}px'.format(switch_width)
            break

    # Update landscapes to start at 600
    landscapes = filter(lambda source:
        get_image_orientation(source['image']) == 'landscape', sources)
    if len(landscapes) > 1:
        above_600 = None
        for source in reversed(landscapes):
            if not above_600 or source['breakpoint_width'] > switch_width:
                above_600 = source
            else:
                sources.remove(source)

    return sources


def get_image_at_size(image, width, orientation=None):
    '''Returns an image object at the specified size'''

    accepted_orientations = ['landscape', 'portrait', 'square']
    original_orientation = image['original']['orientation']

    # If orientation is None, use the image orientation
    if not orientation or not orientation in accepted_orientations:
        orientation = original_orientation

    # If the image width is the same as original width, just use the original
    if (orientation == original_orientation and
        width == image['original']['width']):
        return create_fake_crop(orientation=orientation,
            width=image['original']['width'],
            height=image['original']['height'],
            public_url=image['original']['public_url'])

    # Build the image url
    url_format = {
        'base_url': IMAGE_API_URL,
        'uid': image['uid'],
        'width': width
    }
    url_structure = '{base_url}/image/{uid}/crop/{width}'
    if not original_orientation == orientation:
        if orientation == 'landscape':
            url_structure = '{base_url}/image/{uid}/crop/{width}/landscape/top'
        elif orientation == 'portrait':
            if original_orientation == 'landscape':
                url_structure = '{base_url}/image/{uid}/crop/{width}/portrait/middle'
            elif original_orientation == 'square':
                url_structure = '{base_url}/image/{uid}/crop/{width}/portrait/middle'
        elif orientation == 'square':
            if original_orientation == 'landscape':
                url_structure = '{base_url}/image/{uid}/crop/{width}/square/middle'
            elif original_orientation == 'portrait':
                url_structure = '{base_url}/image/{uid}/crop/{width}/square/top'
    public_url = url_structure.format(**url_format)

    # Build and return the fake image crop
    return create_fake_crop(orientation=orientation, width=width,
        height=_get_height(width, orientation), public_url=public_url)


def get_image_orientation(image):
    '''Returns a string stating the orientation of the image'''

    if 'orientation' in image:
        return image['orientation']
    if 'crop_type' in image:
        return image['crop_type']
    if image['width'] == image['height']:
        return 'square'
    if image['width'] > image['height']:
        return 'landscape'
    return 'portrait'


def get_image_srcset(crops):
    '''Creates a string for srcet'''
    crops = map(lambda crop: '%s %sw' % (crop['public_url'], crop['width']),
        crops)
    return ', '.join(crops)



def get_key(x, y):
    if x is None or y not in x:
        return None
    return x[y]



def get_landscape_crops(image, retina=False, max_width=None):

    landscape_crops = []

    try:
        crops = SIZES_LANDSCAPE[:]
        if retina:
            crops += SIZES_LANDSCAPE_RETINA

        for crop in crops:
            # Check if the original image is big enough to make the size
            if _is_too_big(image['original']['width'], crop[0], max_width):
                continue
            # Check if the current crop is bigger than the max width
            if max_width and crop[0] > max_width:
                continue
            # Make the image
            landscape_crops.append(get_image_at_size(image, crop[0],
                'landscape'))

        # We might have no crops because image is too small for the max width
        # or the max width is smaller that the smallest crop. If its the latter,
        # make the smallest crop
        if len(landscape_crops) == 0:
            if max_width and image['original']['width'] >= max_width:
                landscape_crops.append(get_image_at_size(image, max_width,
                    'landscape'))
            else:
                landscape_crops.append(get_image_at_size(image,
                    image['original']['width'], 'landscape'))

    except Exception, e:
        print e
        landscape_crops = [fake_landscape_crop()]

    return landscape_crops


def get_portrait_crops(image, retina=False, max_width=None):

    portrait_crops = []

    try:
        crops = SIZES_PORTRAIT[:]
        if retina:
            crops += SIZES_PORTRAIT_RETINA

        for crop in crops:
            # Check if the original image is big enough to make the size
            if _is_too_big(image['original']['width'], crop[0], max_width):
                continue
            # Check if the current crop is bigger than the max width
            if max_width and crop[0] > max_width:
                continue
            # Make the image
            portrait_crops.append(get_image_at_size(image, crop[0], 'portrait'))

        if len(portrait_crops) == 0:
            if max_width and image['original']['width'] >= max_width:
                portrait_crops.append(get_image_at_size(image, max_width,
                    'portrait'))
            else:
                portrait_crops.append(get_image_at_size(image,
                    image['original']['width'], 'portrait'))

    except:
        portrait_crops = [fake_portrait_crop()]

    return portrait_crops


def get_month_from_int(month):
    '''Gets a string month from an integer'''
    return ALL_MONTHS_INDEX[month]


def get_square_crops(image, retina=False, max_width=None):

    square_crops = []
    try:
        crops = SIZES_SQUARE[:]
        if retina:
            crops += SIZES_SQUARE_RETINA


        for crop in crops:
            # Check if the original image is big enough to make the size
            if _is_too_big(image['original']['width'], crop[0], max_width):
                continue
            # Check if the current crop is bigger than the max width
            if max_width and crop[0] > max_width:
                continue
            # Make the image
            square_crops.append(get_image_at_size(image, crop[0], 'square'))

        if len(square_crops) == 0:
            if max_width and image['original']['width'] >= max_width:
                square_crops.append(get_image_at_size(image, max_width,
                    'square'))
            else:
                square_crops.append(get_image_at_size(image,
                    image['original']['width'], 'square'))

    except:
        square_crops = [fake_square_crop()]

    return square_crops



def get_primary_tag(article):
    '''Returns the primary tag on the article. At the moment it just returns
       the first item in the tags list but it might end up needing more logic'''

    primary_tag = None

    # If the article is video, use the series
    if 'type' in article and article['type'] == 'video':
        primary_tag = article['series']

    # If video and no series set and theres no tags, quit early
    if not primary_tag and (not 'tags' in article or
        len(article['tags']) < 1):
        return None

    # If we haven't found anything, just use the first tag
    if not primary_tag:
        primary_tag = article['tags'][0]

    # Temp fix for search items
    if not 'title' in primary_tag:
        return {
            'title': primary_tag
        }

    return primary_tag


def get_relative_time(dt_string, dt_input_format='%Y-%m-%d %H:%M:%S'):

    TIME_SECOND = 1
    TIME_MINUTE = TIME_SECOND * 60
    TIME_HOUR = TIME_MINUTE * 60
    TIME_SIX_HOURS = TIME_HOUR*6

    try:
        article_dt = datetime.strptime(dt_string[:-05], dt_input_format)
        today_dt = datetime.utcnow()
        yesterday_dt = today_dt - timedelta(days=1)
        time_diff = today_dt - article_dt

        # If the article's display date is more than 5 calendar days from the
        # current date, display the date as DD MMM YYYY (eg 12 Feb 2016)
        if time_diff.days > 5:
            return article_dt.strftime('%d %b %Y')
        # If the article's display date is between 2 and 5 calendar days of the
        # current date, display the date as 'X days ago', where X is the number
        # of calendar days since the article was published.
        elif time_diff.days >= 2 and time_diff.days <= 5:
            return '{} days ago'.format(time_diff.days)
        # If the article was published more than 6 hours ago and the date is
        # yesterday's date, the date is displayed as '1 day ago'
        elif (time_diff.seconds >= TIME_SIX_HOURS and
            article_dt.day == yesterday_dt.day):
            return '1 day ago'
        # If the article was published more than 6 hours ago and the date is
        # today's date, use the article's published time (HH:MM PM)
        elif (time_diff.seconds > TIME_SIX_HOURS and
            article_dt.day == today_dt.day):
            return article_dt.strftime('%I:%M %p')
        # If the date is more than one hour old but within the last 6 hours,
        # show the date as 'X hours ago', where X is the number of hours
        # (rounded normally) since the article was published.
        elif (time_diff.seconds > TIME_HOUR and
            time_diff.seconds <= TIME_SIX_HOURS):
            return '{} hours ago'.format(round(time_diff.seconds/TIME_HOUR))
        # If the date is within the last 60 minutes, display the date as
        # 'X minutes ago'
        else:
            return '{} minutes ago'.format(round(time_diff.seconds/TIME_MINUTE))
    except:
        return dt_string


def get_article_sponsor(article):
    '''Gets the sponsor of the article'''

    def _is_brand_tag(tag):
        return tag['type'] == 'brand'

    if (
        'is_sponsored' in article and article['is_sponsored'] and
            'tags' in article and article['tags']):
        brand_tags = filter(_is_brand_tag, article['tags'])
        if brand_tags:
            return brand_tags[0]

    return None


def image_fail(value, default):
    '''Helper function for image_fail_landscape, image_fail_portrait and
       image_fail_square'''
    if value:
        return value
    return default



def image_fail_landscape(value):
    '''Checks if value is truthy, if not, give the fail landscape image'''
    return image_fail(value, '/static/img/gq_missing_landscape.png')



def image_fail_portrait(value):
    '''Checks if value is truthy, if not, give the fail portrait image'''
    return image_fail(value, '/static/img/gq_missing_portrait.png')



def image_fail_square(value):
    '''Checks if value is truthy, if not, give the fail square image'''
    return image_fail(value, '/static/img/gq_missing_square.png')



@returns_markup
def import_svg_icon(svg_filename, svg_classes=None):
    filename_format = {
        'base': path.dirname(path.realpath(__file__)),
        'filename': svg_filename,
        'relative': '../static'
    }
    svg_string = _import_svg_icon("{base}/{relative}/{filename}".format(
        **filename_format))

    if svg_classes:
        # TODO: log error but silent shhhhh
        assert svg_string[:4] == '<svg'
        svg_string = '<svg class="{classes}"{svg}'.format(
            classes=svg_classes,
            # We use 4 as <svg is 4 characters :)
            svg=svg_string[4:]
        )

    return svg_string



def try_image_crops(images, image_types):
    for image_type in image_types:
        if image_type in images:
            return images[image_type]
    return None


def get_meta_value(object_with_meta, meta_key, lower=True):
    '''Get a value for a given meta key from an object with meta info'''

    if not 'meta' in object_with_meta:
        return

    if lower:
        meta_key = meta_key.lower()

    try:
        meta_array = filter(lambda x: x['key'] == meta_key, object_with_meta['meta'])
        return meta_array[0]['value']
    except IndexError:
        pass


def get_review_information(article):
    '''
        Gets the review information from the meta and creates a structured
        object

        # The rating of the product
        review_score
        # The max rating of the product
        review_maxscore
        # Price information about the product (NOT TO BE USED WITH SCHEMA)
        review_price_text
        # Number price (NOT IMPLEMENTED)
        review_price
        # Positive review points
        review_conclusion_positive
        # Negative review points
        review_conslusion_negative
        # Schema.org thing
        review_thing
    '''

    if not 'meta' in article or not article['meta']:
        return {}

    review_information = {
        'conclusion_negative': get_meta_value(
            article, 'review_conclusion_negative'),
        'conclusion_positive': get_meta_value(
            article, 'review_conclusion_positive'),
        'maxscore': int(get_meta_value(article, 'review_maxscore') or 0),
        'price_text': get_meta_value(article, 'review_price_text'),
        'score': int(get_meta_value(article, 'review_score') or 0),
        'thing': get_meta_value(article, 'review_thing')
    }

    return review_information


@returns_markup
def get_event_datetime(event,
    start_time_format="%Y-%m-%d %H:%M:%S",
    end_time_format="%Y-%m-%d %H:%M:%S",
    short_months=True, output='raw'):

    start_time = get_meta_value(event, 'event_date_start')
    end_time = get_meta_value(event, 'event_date_end')
    if not end_time:
        end_time = start_time

    TIME_SECOND = 1
    TIME_MINUTE = TIME_SECOND * 60
    TIME_HOUR = TIME_MINUTE * 60
    TIME_DAY = TIME_HOUR * 24
    TIME_WEEK = TIME_DAY * 7

    start_time_dt = datetime.strptime(start_time[:-05], start_time_format)
    end_time_dt = datetime.strptime(end_time[:-05], end_time_format)

    diff_time_dt = end_time_dt - start_time_dt
    diff_time_seconds = diff_time_dt.total_seconds()

    month = '%b' if short_months else '%B'

    # Same day
    if start_time_dt.day == end_time_dt.day and diff_time_seconds < TIME_DAY:
        event_datetime = '%s' % dt_format(start_time, "%-d " + month + ", %Y")
        if output == 'raw':
            return event_datetime
        else:
            event_datetime_html = u'<time datetime="' + dt_format(start_time, "%Y-" + str(start_time_dt.month) + "-%d") + '">' + event_datetime + '</time>'
            return event_datetime_html

    # Different days - same month
    elif start_time_dt.month == end_time_dt.month and diff_time_seconds < TIME_WEEK * 4:
        event_datetime = '%s - %s' % (dt_format(start_time, "%-d"), dt_format(end_time, "%-d " + month + ", %Y"))
        if output == 'raw':
            return event_datetime
        else:
            event_datetime_html = u'<time datetime="' + dt_format(start_time, "%Y-" + str(start_time_dt.month) + "-%d") + '">' + dt_format(start_time, "%d") + '</time>' + ' - ' + u'<time datetime="' + dt_format(end_time, "%Y-" + str(end_time_dt.month) + "-%d") + '">' + dt_format(end_time, "%-d " + month + " %Y") + '</time>'
            return event_datetime_html

    # Different months - same year
    elif start_time_dt.month != end_time_dt.month and start_time_dt.year == end_time_dt.year:
        event_datetime = '%s - %s' % (dt_format(start_time, "%-d " + month), dt_format(end_time, "%-d " + month + ", %Y"))
        if output == 'raw':
            return event_datetime
        else:
            event_datetime_html = u'<time datetime="' + dt_format(start_time, "%Y-" + str(start_time_dt.month) + "-%d") + '">' + dt_format(start_time, "%d " + month) + '</time>' + ' - ' + u'<time datetime="' + dt_format(end_time, "%Y-" + str(end_time_dt.month) + "-%d") + '">' + dt_format(end_time, "%-d " + month + " %Y") + '</time>'
            return event_datetime_html

    # Different months - different year
    else:
        event_datetime = '%s - %s' % (dt_format(start_time, "%-d " + month + ", %Y"), dt_format(end_time, "%-d " + month + ", %Y"))
        if output == 'raw':
            return event_datetime
        else:
            event_datetime_html = u'<time datetime="' + dt_format(start_time, "%Y-" + str(start_time_dt.month) + "-%d") + '">' + dt_format(start_time, "%-d " + month + ", %Y") + '</time>' + ' - ' + u'<time datetime="' + dt_format(end_time, "%Y-" + str(end_time_dt.month) + "-%d") + '">' + dt_format(end_time, "%-d " + month + ", %Y") + '</time>'
            return event_datetime_html








def _source_tuple_to_dict(source):
    '''Splits the source tuple into a dict'''

    source_len = len(source)
    d = {
        'min-width': 0,
        'orientation': None
    }
    if type(source) is str:
        d['orientation'] = source
    elif source_len == 1:
        d['orientation'] = source[0]
    elif source_len == 2:
        d['min-width'] = source[0]
        d['orientation'] = source[1]
    return d


def get_image_source_crops(image, sources, retina=False, max_width=None):
    image_crops = []
    next_min_width = None

    for source in reversed(sources):
        # Tuple to dict
        _source = _source_tuple_to_dict(source)
        # Image crops for tuple
        if _source['orientation'] == 'landscape':
            _image_crops = get_landscape_crops(
                image, retina=retina, max_width=max_width)
        elif _source['orientation'] == 'portrait':
            _image_crops = get_portrait_crops(
                image, retina=retina, max_width=max_width)
        elif _source['orientation'] == 'square':
            _image_crops = get_square_crops(
                image, retina=retina, max_width=max_width)
        # Filter out sizes - small sizes
        _filtered_crops = filter(
            lambda crop: crop['width'] >= _source['min-width'], _image_crops)
        # Filter out sizes - outside next min width
        if next_min_width:
            last_index = None
            for index, crop in reversed(list(enumerate(_filtered_crops))):
                if crop['width'] < next_min_width:
                    last_index = index
                    break
            if last_index and last_index != len(_filtered_crops) - 1:
                _filtered_crops = _filtered_crops[0:last_index+1]
        # If there are no sizes, use the largest
        if len(_filtered_crops) == 0:
            _filtered_crops = [_image_crops[-1]]
        # Update the next min width
        next_min_width = _source['min-width']
        # Update image_crops
        image_crops = _filtered_crops + image_crops

    return image_crops


def get_image_source_orientations(image_sources):
    orientations = set()
    for source in image_sources:
        _source = _source_tuple_to_dict(source)
        orientations.add(_source['orientation'])
    return list(orientations)


def get_breakpoints_from_image_sources(image_crops, image_sources):
    breakpoints = []
    _image_sources = image_sources[:]
    _current_source = None

    for image_crop in image_crops:
        if not _current_source:
            _current_source = _source_tuple_to_dict(_image_sources.pop(0))
            breakpoints.append((_current_source['min-width'], image_crop['width']))
        elif image_crop['width'] > _current_source['min-width']:
            breakpoints.append((image_crop['width'], image_crop['width']))
            _current_source = None
        else:
            breakpoints.append((image_crop['width'], image_crop['width']))

    return breakpoints









FILTERS = {
    'create_crops': create_crops,
    'get_article_date': get_article_date,
    'get_card_switcher_sources': get_card_switcher_sources,
    'create_picture_sources': create_picture_sources,
    'get_image_srcset': get_image_srcset,
    'get_key': get_key,
    'get_meta_value': get_meta_value,
    'get_month_from_int': get_month_from_int,
    'get_primary_tag': get_primary_tag,
    'get_relative_time': get_relative_time,
    'get_review_information': get_review_information,
    'get_article_sponsor': get_article_sponsor,
    'import_svg_icon': import_svg_icon,
    'try_image_crops': try_image_crops,
    'get_event_datetime': get_event_datetime,
    'get_image_source_orientations': get_image_source_orientations,
    'get_breakpoints_from_image_sources': get_breakpoints_from_image_sources
}
