#!/usr/bin/env python
# vim:ts=4:sw=4:ft=python:fileencoding=utf-8
"""Python Template.

This is a template for python.
NOTE: The __future__ imports that make it more v3.x ready were added in v2.6.
Thus this template will only work in python v2.6 or higher.

Because of requirements I'd recommend setting up a virtualenv for this.

Requirements:
    Python v2.6 or higher.
    yaml: On ubuntu this is available with `python-yaml`
    requests: On ubuntu this is available with `python-requests`

"""

from __future__ import absolute_import, print_function
from __future__ import unicode_literals, division
import os
import sys
import logging
import time
import json
# configparser and collections are used by get_config() function
#import ConfigParser
#import collections

import yaml
import requests


# Follow http://semver.org/ versioning conventions
__version__ = '0.1.0-dev'


# Logger config
# DEBUG, INFO, WARNING, ERROR, or CRITICAL
# This will set log level from the environment variable LOGLEVEL or default
# to warning. You can also just hardcode the error if this is simple.
_LOGLEVEL = getattr(logging, os.getenv('LOGLEVEL', 'WARNING').upper())
_LOGFORMAT = "%(asctime)s - %(levelname)s - %(name)s - %(message)s"
logging.basicConfig(level=_LOGLEVEL, format=_LOGFORMAT)


# Examples on documenting functions and classes. You can also look in to making
# documentation that can be generated by sphinx. See http://sphinx-doc.org
#
# A lot of this came from
# http://google-styleguide.googlecode.com/svn/trunk/pyguide.html
class GW2(object):
    """GuildWars 2 API wrapper

    Provides utlity functions mostly dealing with retrieving and caching the
    files that have the list of server names and event status.

    Attributes:
        world_id: The numeric world_id. Required
        cache_dir: Cache directory. defaults to ./cache
        cache_age: How long, in seconds, to cache the file. Setting to 0
            disables the cache. Defaults to 7 days (7 * 24 * 60 * 60)
        api_count: This increments every time a call to api is made

    """
    def __init__(self, world_id, cache_dir='./cache', cache_age=None):
        """Setup the needed variables."""
        script_dir = os.path.dirname(os.path.realpath(__file__))
        self.cache_dir = os.path.normpath('%s/%s' % (script_dir, cache_dir))
        if cache_age is not None:
            self.cache_age = cache_age
        else:
            self.cache_age = 7 * 24 * 60 * 60
        self.baseurl = 'https://api.guildwars2.com/v1'
        self.api_count = 0
        self.world_id = world_id
        self._log = logging.getLogger('%s.GW2' % __name__)

    def _download_endpoint(self, endpoint, world_id=None, event_id=None):
        """Fetches endpoint from api and updates cache file."""
        # pylint: disable=E1103
        filename = '%s.json' % endpoint
        cache_file = os.path.normpath('%s/%s' % (self.cache_dir, filename))
        url = '%s/%s' % (self.baseurl, filename)
        payload = {}
        payload['world_id'] = world_id
        payload['event_id'] = event_id
        self._log.info('Fetching %s' % url)
        self.api_count = self.api_count + 1
        req = requests.get(url, params=payload)
        req.raise_for_status()
        content = req.content
        self._log.info('Updating cache file: %s' % cache_file)
        with open(cache_file, 'w') as fh:
            fh.write(content)
        return content

    @staticmethod
    def _get_name_by_id(haystack, needle):
        """Searches list on 'id' and return 'name' value.

        This is when you want the name from a given id. This assumes there will
        only be a single result so it returns the first item in result.

        Arg:
            haystack: The list to search
            needle: The value to look for

        """
        result = [item['name'] for item in haystack if item['id'] == needle]
        if result:
            return result[0]
        else:
            return None

    @staticmethod
    def _get_event_status_by_id(haystack, needle):
        """Searches list on 'id' and return entire line.

        Used to get the status of an event. It returns the entire matching
        dict object back. Again just the first result though.

        Arg:
            haystack: The list to search
            needle: The value to look for

        """
        #self._log.debug('_get_event_status_by_id: haystack: %s needle: %s'
        #    % (haystack, needle))
        result = [item
            for item
            in haystack['events']
            if item['event_id'] == needle]
        if result:
            return result[0]
        else:
            return None

    def get_endpoint(self, endpoint, world_id=None, cache_age=None):
        """Loads an endpoint either from cache or download."""
        if cache_age is None:
            cache_age = self.cache_age
        filename = '%s.json' % endpoint
        cache_file = os.path.normpath('%s/%s' % (self.cache_dir, filename))
        try:
            filestat = os.stat(cache_file)
        # pylint: disable=W0621
        except OSError as err:
            self._log.info('Cache file not found, will download: %s' % err)
            content = self._download_endpoint(endpoint, world_id)
        # pylint: enable=W0621
        else:
            file_age = time.time() - filestat.st_mtime
            self._log.info('Cache file %s is %d seconds old'
                % (cache_file, file_age))
            if file_age > cache_age:
                self._log.info('Cache file too old, fetching new one')
                content = self._download_endpoint(endpoint, world_id)
            else:
                self._log.info('Using cached file')
                with open(cache_file, 'r') as fh:
                    content = fh.read()
        return json.loads(content)

    def lookup_name(self, endpoint, endpoint_id):
        """Searches endpoint for the specified id and returns name.

        Args:
            endpoint: The endpoint to lookup (like world_id)
            endpoint_id: The numeric id you want the name of

        """
        results = self.get_endpoint(endpoint)
        name = self._get_name_by_id(results, str(endpoint_id))
        return name

    def get_event_status(self, event_id, cache_age=10):
        """Gets the status of the specified event_id.

        only keep event status around for 10 seconds. Idea being if I don't
        do that in the currently implemenation it would download the file
        for each event. Need to look into a memoize function.

        """
        events = self.get_endpoint('events', world_id=self.world_id,
            cache_age=cache_age)
        details = self._get_event_status_by_id(events, event_id)
        return details


def get_config():
    """Load options from an ini-style config.

    This loads the config file from several standard locations. This tries to be
    compatible with the XDG base directory standard and uses those defaults if
    it doesn't see the environment variables.

    This is thrown together as an idea but could probably use some rework. Also
    if you plan on using a lot of XDG standards there are several python modules
    that implement it. None in standard library though.

    Args:
        appname: The name of this script, this will be used for the config
            location in $XDG_CONFIG_HOME. This should be unique for this project
            to ensure you don't accidentally load some other apps config. This
            is required.

    Returns:
        config object

    """
    #xdg_data_home = os.environ.get('XDG_DATA_HOME',
    #    os.path.expanduser('~/.config'))
    #xdg_user_config = os.path.join(xdg_data_home, appname, 'config.ini')
    #config = ConfigParser.SafeConfigParser(dict_type=collections.OrderedDict)
    #config.read(['config.ini', xdg_user_config])
    # TODO: only reads from one location
    # This gets the directory of this script.
    # TODO: move to a more standard location like ~/.config/
    script_dir = os.path.dirname(os.path.realpath(__file__))
    with open('{:1}/config.yaml'.format(script_dir)) as fh:
        config = yaml.safe_load(fh)
    return config


def main(argv=None):
    """The main function."""
    if argv is None:
        argv = sys.argv
    config = get_config()
    #loglevel = getattr(logging, config.get('general', 'loglevel').upper())
    loglevel = getattr(logging, config['loglevel'].upper())
    log = logging.getLogger(__name__)
    log.setLevel(loglevel)
    log.debug('Log level changed to %s' % logging.getLevelName(loglevel))
    #world_id = config.get('general', 'world_id')
    #sections = config.sections()
    #events = []
    #for section in sections:
    #    if section.startswith('event_'):
    #        logging.debug('Found event: %s' % config.get(section,
    #            'description'))
    #        events.append(config.items(section))
    #for event in events:
    #    for k in event:
    #        print(k)
    gw2 = GW2(config['world_id'])
    world_name = gw2.lookup_name('world_names', config['world_id'])
    print('World: %s' % world_name)
    for event in config['events']:
        print("\n%s" % event['name'])
        for event_id in event['event_ids']:
            event_status = gw2.get_event_status(event_id['id'])
            log.debug(event_status)
            is_complete = event_status['state'] in ('Success', 'Fail',
                    'Inactive', 'Warmup')
            if config['hide_complete'] and is_complete:
                continue
            if ('include_map_name' in event
                    and event['include_map_name'] is True):
                map_name = gw2.lookup_name('map_names', event_status['map_id'])
            else:
                map_name = ''
            if 'description' in event_id:
                description = event_id['description']
            elif 'default_event_description' in event:
                description = event['default_event_description']
            else:
                description = ''

            print("%-11s %-20s %s" %
                (event_status['state'], map_name, description))
    log.info('API requests made: %d' % gw2.api_count)


if __name__ == "__main__":
    try:
        # main should only return a value instead of calling sys.exit itself
        sys.exit(main())
    # Remove unless you need to do something if the user cancels.
    #except KeyboardInterrupt as e:
    #    # Ctrl-c
    #    log.error('Received keyboard interupt')
    #    raise e
    # This catches all "non system exiting" exceptions
    # pylint: disable=W0703
    except Exception as err:
        logging.critical("ERROR, UNEXPECTED EXCEPTION")
        logging.critical(str(err), exc_info=True)
        sys.exit(1)

