class ApiException(BaseException):
    def __init__(self, value, status='500 Internal Server Error', json_data=None):
        super(ApiException, self).__init__()
        self.value = value
        self.status = status
        self.json_data = json_data

    def __str__(self):
        return self.value class ValidationException(Exception):
    def __init__(self, issues):
        super(ValidationException, self).__init__()
        self.issues = issues

    def __str__(self):
        string = ''
        issues = self.issues
        first = True

        for issue in issues:
            if not first:
                string += '\n'

            issue_id = issue[0]
            errors = issue[1]['errors']
            warnings = issue[1]['warnings']

            string += 'For identifier %s:\n' % issue_id
            for e in errors:
                string += '    Error  : %s\n' % e
            for w in warnings:
                string += '    Warning: %s' % w

            first = False

        return string class GameDetail(str):
    def __new__(cls, value):
        if not value:
            value = ''
        return str.__new__(cls, value.strip())

    def is_set(self):
        return self.__str__() != ''

    def is_correct(self):
        return self.is_set() class PathDetail(GameDetail):
    def __new__(cls, value):
        return GameDetail.__new__(cls, value)

    def is_correct(self):
        try:
            abs_path = get_absolute_path(self.__str__())
            return access(abs_path, W_OK)
        except (AttributeError, TypeError):
            # TODO: These are thrown by get_absolute_path when called on None and probably shouldn't be needed
            return False class SlugDetail(str):
    def __new__(cls, value=None):
        if not value:
            value = 'new-game'
        else:
            value = slugify(value)
        return str.__new__(cls, value)

    def is_set(self):
        return self.__str__() != ''

    def is_correct(self):
        return slugify(self.__str__()) == self.__str__() class ImageDetail(object):
    def __init__(self, game, image_path):
        self.game = game
        self.image_path = image_path

        if self.is_correct():
            self.image_path = image_path
        else:
            self.image_path = ''

    def is_correct(self):
        try:
            path = get_absolute_path(self.game.path)
            path = join_path(path, self.image_path)
        except (AttributeError, TypeError):
            # TODO: These are thrown by get_absolute_path when called on None and probably shouldn't be needed
            return None
        else:
            return access(path, R_OK)

    def __repr__(self):
        return '/%s/%s' % (self.game.slug, self.image_path) class ListDetail(object):
    def __init__(self, src):
        if isinstance(src, basestring):
            items = src.splitlines()
            items = [item.strip() for item in items]
            items = [item.encode('utf8') for item in items if item]
        else:
            items = src
        self.items = items

    def is_set(self):
        return len(self.items) > 0

    def is_correct(self):
        return self.is_set()

    def __repr__(self):
        return '\n'.join(self.items)

    def getlist(self):
        return self.items class EngineDetail(str):
    def __new__(cls, value):
        if not value:
            value = ENGINEVERSION
        else:
            value = str(value).strip()
        return str.__new__(cls, value)

    def is_set(self):
        return self.__str__() != ''

    def is_correct(self):
        if ENGINEVERSION_PATTERN.match(self.__str__()):
            # Existence of the particular engine referenced should be
            # checked on the Hub
            return True
        else:
            return False class AspectRatioDetail(str):
    def __new__(cls, value):
        if not value:
            value = DEFAULT_ASPECT_RATIO
        return str.__new__(cls, value.strip())

    def is_set(self):
        return self.__str__() != ''

    def is_correct(self):
        if ASPECT_RATIO_PATTERN.match(self.__str__()):
            return True
        else:
            return False class MetricsSession(object):

    keys = ['file', 'ext', 'size', 'type', 'time', 'status']

    _slug_sessions = {}
    _last_timestamp = 0

    def __init__(self, slug):

        self.entries = [ ]
        self.slug = slug

        # make sure we have a unique timestamp
        timestamp = '%.8f' % time()
        if MetricsSession._last_timestamp == timestamp:
            timestamp += '1'
        MetricsSession._last_timestamp = timestamp
        self.timestamp = timestamp

        # work out the filename to be used
        self.file_name = MetricsSession.get_file_name(slug, timestamp)

        LOG.info('New metrics session started timestamp %s', timestamp)

    def __del__(self):
        self.finish()

    def append(self, file_name, file_size, file_type, file_status):
        _, file_ext = splitext(file_name)

        entry = {
            "file": file_name,
            "ext": file_ext,
            "size": int(file_size),
            "type": file_type,
            "time": time(),
            "status": file_status
        }

        self.entries.append(entry)

    def get_file_path(self):
        return self.file_name

    def finish(self):

        # make sure path exists
        folder_name = MetricsSession.get_folder_name(self.slug)

        if not exists(folder_name):
            # Due to race conditions we still need the try/except
            try:
                makedirs(folder_name)
            except OSError, e:
                if e.errno != errno.EEXIST:
                    LOG.error(str(e))
                    raise

        LOG.info('Metrics session saving to %s', self.file_name)

        session = { 'entries': self.entries }

        try:
            f = open(self.file_name, mode='w')
            json.dump(session, f, indent=4)
            f.close()
        except IOError, e:
            LOG.error(str(e))

    @classmethod
    def get_folder_name(cls, slug):
        return join(config['metrics.base_path'], slug)

    @classmethod
    def get_file_name(cls, slug, timestamp):
        return join(cls.get_folder_name(slug), '%s.json' % timestamp)

    @classmethod
    def get_csv_file_name(cls, slug, timestamp):
        return join(cls.get_folder_name(slug), '%s.csv' % timestamp)

    @classmethod
    def get_data(cls, slug, timestamp):
        file_name = cls.get_file_name(slug, timestamp)
        try:
            f = open(file_name, mode='r')
            session_data = json.load(f)
            f.close()
        except (IOError, json.JSONDecodeError):
            session_data = ''
        return session_data

    @classmethod
    def get_data_as_csv(cls, slug, timestamp):
        csv_buffer = StringIO()
        file_name = cls.get_file_name(slug, timestamp)

        keys = MetricsSession.keys
        try:
            with open(file_name, 'r') as f:
                session_data = json.load(f)
                rows = session_data['entries']
        except IOError:
            return None
        else:
            writer = csv.DictWriter(csv_buffer, keys)
            writer.writerow(dict(zip(keys, keys)))
            for row in rows:
                writer.writerow(row)

            csv_data = csv_buffer.getvalue()
            csv_buffer.close()

            return csv_data

    @classmethod
    def get_data_as_json(cls, slug, timestamp):
        file_name = cls.get_file_name(slug, timestamp)
        try:
            f = open(file_name, 'r')
            session_data = f.read()
            f.close()
        except IOError:
            return None
        else:
            return session_data

    @classmethod
    def delete(cls, slug, timestamp):
        if timestamp:
            file_name = cls.get_file_name(slug, timestamp)
            try:
                remove(file_name)
            except OSError:
                return False
        else:
            rmtree(cls.get_folder_name(slug), True)
        return True

    @classmethod
    def rename(cls, old_slug, new_slug):
        old_folder = cls.get_folder_name(old_slug)
        new_folder =  cls.get_folder_name(new_slug)

        # delete the new folder is necessary, otherwise the
        # old one will just end up inside of it
        try:
            rmtree(new_folder)
        except OSError:
            pass
        try:
            rename(old_folder, new_folder)
        except OSError:
            pass

    @classmethod
    def stop_recording(cls, slug):
        try:
            del cls._slug_sessions[slug]
        except KeyError:
            return False
        else:
            return True

    @classmethod
    def has_metrics(cls, slug):
        if slug in cls._slug_sessions:
            return True

        folder_name = cls.get_folder_name(slug)
        if isdir(folder_name):
            for f in listdir(folder_name):
                if f.endswith('.json'):
                    return True
        return False

    @classmethod
    def get_metrics(cls, slug):
        cls.stop_recording(slug)

        folder_name = cls.get_folder_name(slug)
        if isdir(folder_name):
            timestamps = [f[:-5] for f in listdir(folder_name) if f.endswith('.json')]
            timestamps.sort()
            return [{'timestamp': t, 'entries': cls.get_data(slug, t)['entries']} for t in timestamps]
        else:
            return [ ]

    @classmethod
    def get_sessions(cls, slug):
        try:
            slug_sessions = cls._slug_sessions[slug]
        except KeyError:
            slug_sessions = {}
            cls._slug_sessions[slug] = slug_sessions
        return slug_sessions class GameBadges(object):
    badges = []
    userbadges = {}
    userbadges_path = None

    def __init__(self, game):
        self.lock = Lock()

        self.game = game
        self.userbadges_path = None

        self.abs_game_path = get_absolute_path(game.path)

        try:
            self.lock.acquire()
            yaml_path = norm_path(get_absolute_path(join_path(game.path, 'badges.yaml')))
            if not access(yaml_path, R_OK):
                raise BadgesUnsupportedException()
            f = open(unicode(yaml_path), 'r')
            try:
                self.badges = yaml.load(f)

            finally:
                f.close()
        except IOError as e:
            LOG.error('Failed loading badges: %s', str(e))
            raise ApiException('Failed loading badges.yaml file %s' % str(e))
        finally:
            self.lock.release()


    def validate(self):
        result = []

        count = 0
        for badge in self.badges:
            count += 1
            errors = []
            # collect keys that are missing from the badge or are not filled in
            for key in REQUIRED_BADGE_KEYS:
                if not badge.get(key, False):
                    errors.append('missing key: "%s"' % key)

            icon_path = badge.get('imageresource', {}).get('icon', False)

            warnings = []
            if not icon_path:
                warnings.append('missing key: "imageresource.icon"')

            if icon_path:
                icon_path = join_path(self.abs_game_path, icon_path)
                if not access(icon_path, R_OK):
                    errors.append('icon "%s" couldn\'t be accessed.' % icon_path)

            identifier = badge.get('title', badge.get('key', 'Badge #%i' % count))

            if errors or warnings:
                result.append((identifier, {'errors': errors, 'warnings': warnings}))

        return result

    def _set_userbadges_path(self):
        if not self.userbadges_path:
            try:
                path = config['userbadges_db']
            except KeyError:
                LOG.error('badges_db path config variable not set')
                return

            if not create_dir(path):
                LOG.error('Game badges path \"%s\" could not be created.', path)
            self.userbadges_path = norm_path(join_path(get_absolute_path(path), self.game.slug) + '.yaml')

    def upsert_badge(self, ub):
        self._set_userbadges_path()
        self.lock.acquire()
        try:
            with open(unicode(self.userbadges_path), 'r') as f:
                self.userbadges = yaml.load(f)
        except IOError:
            pass

        try:
            #upsert the badge under the key of the user and the badgename
            if not ub['username'] in self.userbadges:
                self.userbadges[ub['username']] = {}
            self.userbadges[ub['username']][ub['badge_key']] = ub

            with open(unicode(self.userbadges_path), 'w') as f:
                yaml.dump(self.userbadges, f, default_flow_style=False)
        except IOError as e:
            LOG.error('Failed writing userbadges file "%s": %s', self.userbadges_path, str(e))
            raise Exception('Failed writing userbadge file %s %s' % (self.userbadges_path, str(e)))
        finally:
            self.lock.release()

    def find_userbadges_by_user(self, username):
        self._set_userbadges_path()
        self.lock.acquire()
        try:
            self.userbadges = {}

            f = open(unicode(self.userbadges_path), 'r')
            try:
                self.userbadges = yaml.load(f)
                f.close()

                return self.userbadges[username]

            except KeyError:
                return {}
            finally:
                f.close()
        except IOError:
            return {}
        finally:
            self.lock.release()

    def get_userbadge(self, username, key):
        self._set_userbadges_path()
        self.lock.acquire()
        try:
            self.userbadges = {}

            f = open(unicode(self.userbadges_path), 'r')
            try:
                self.userbadges = yaml.load(f)
                f.close()

                return self.userbadges[username][key]

            except (KeyError, TypeError):
                return {}
            finally:
                f.close()
        except IOError:
            return {}
        finally:
            self.lock.release()

    def get_badge(self, key):
        for badge in self.badges:
            if badge['key'] == key:
                return badge
        return None class BadgesUnsupportedException(Exception):
    pass class LeaderboardsList(object):
    game_leaderboards = {}

    @classmethod
    def load(cls, game):
        game_leaderboard = GameLeaderboards(game)
        cls.game_leaderboards[game.slug] = game_leaderboard
        return game_leaderboard


    @classmethod
    def get(cls, game):
        try:
            return cls.game_leaderboards[game.slug]
        except KeyError:
            return cls.load(game)

    # for testing only
    @classmethod
    def reset(cls):
        cls.game_leaderboards = {} class LeaderboardError(Exception):
    def __init__(self, value, response_code=400):
        super(LeaderboardError, self).__init__()
        self.value = value
        self.response_code = response_code

    def __str__(self):
        return self.value class LeaderboardsUnsupported(LeaderboardError):
    def __init__(self):
        super(LeaderboardsUnsupported, self).__init__('This game does not support leaderboards', 404) class GameNotificationKeysList(object):

    notification_key_dict = {}

    ## do some lazy loading here

    @classmethod
    def load(cls, game):
        keys = GameNotificationKeys(game)
        cls.notification_key_dict[game.slug] = keys
        return keys


    @classmethod
    def get(cls, game):
        return cls.notification_key_dict.get(game.slug) or cls.load(game)


    @classmethod
    def reset(cls):
        cls.notification_key_dict = {} class GameNotificationsUnsupportedException(Exception):
    pass class StoreList(object):
    game_stores = {}

    @classmethod
    def load(cls, game):
        game_store = GameStoreItems(game)
        cls.game_stores[game.slug] = game_store
        return game_store


    @classmethod
    def get(cls, game):
        try:
            return cls.game_stores[game.slug]
        except KeyError:
            return cls.load(game)

    @classmethod
    def reset(cls):
        cls.game_stores = {} class StoreError(Exception):
    def __init__(self, value, response_code=400):
        super(StoreError, self).__init__()
        self.value = value
        self.response_code = response_code

    def __str__(self):
        return self.value class StoreUnsupported(StoreError):
    def __init__(self):
        super(StoreUnsupported, self).__init__('This game does not support a store', 404) def get_absolute_path(directory):
    """
    Return absolute path by working out path relative to games root
    """
    if isabs(directory):
        return directory

    return realpath(normpath(join(config['games_root'], directory))) def create_dir(directory):
    """
    Create the directory structure if necessary. return 'True' if it was created
    successfully and can be written into, 'False' otherwise.
    """
    if directory.strip() == '':
        return False
    else:
        absDir = get_absolute_path(directory)
        try:
            os.makedirs(absDir)
        except OSError as e:
            if e.errno != EEXIST:
                LOG.error('Failed creating meta dir: %s', str(e))
                return False
        return os.access(absDir, os.W_OK) def load_json_asset(json_path):
    # Load mapping table
    try:
        json_handle = open(get_absolute_path(json_path))
        j = json.load(json_handle)
    except IOError as e:
        LOG.error(str(e))
    except ValueError as e:
        LOG.error(str(e))
    else:
        return j
    return None
import logging
import os
import json
import yaml
from os import listdir, access, R_OK
from os.path import join as join_path
from time import time, localtime, strftime
from paste.deploy.converters import asbool
from turbulenz_local.lib.exceptions import ApiException
from turbulenz_local.lib.validation import ValidationException
from turbulenz_local.models.gamedetails import GameDetail, PathDetail, SlugDetail, ImageDetail, ListDetail, \
                                                   EngineDetail, AspectRatioDetail
from turbulenz_local.models.metrics import MetricsSession
from turbulenz_local.models.apiv1.badges import GameBadges, BadgesUnsupportedException
from turbulenz_local.models.apiv1.leaderboards import LeaderboardsList, LeaderboardError, \
                                                      LeaderboardsUnsupported
from turbulenz_local.models.apiv1.gamenotifications import GameNotificationKeysList, \
                                                           GameNotificationsUnsupportedException
from turbulenz_local.models.apiv1.store import StoreList, StoreError, \
                                                   StoreUnsupported
from turbulenz_local.tools import get_absolute_path, create_dir, load_json_asset

# pylint: disable=F0401
# pylint: enable=F0401




LOG = logging.getLogger(__name__)

class GameError(Exception):
    pass

class GameNotFoundError(GameError):
    pass

class GameDuplicateSlugError(GameError):
    pass

class GameSlugNotSpecifiedError(GameError):
    pass

class GamePathError(GameError):
    pass

class GamePathNotFoundError(GameError):
    pass

#######################################################################################################################

def read_manifest(game_path, manifest_name):
    """
    Try reading manifest game data in dictionary form from game_path.
    """
    try:
        game_path = get_absolute_path(game_path)
        game_path = join_path(game_path, manifest_name)
        f = open(unicode(game_path), 'r')
        try:
            data = yaml.load(f)
        finally:
            f.close()
    except IOError as e:
        LOG.error('Failed loading manifest: %s', str(e))
        raise GameError
    else:
        return data

def write_manifest(data, manifest_name):
    """
    Write the game metadata to a YAML manifest file
    """
    path = data.get('path')
    if not path:
        raise GamePathError('No path found in game data.\nData=' +\
                            '\n'.join(['%s:\t%s' % (k, v) for k, v in data.iteritems()]))
    path = get_absolute_path(path)

    # write the YAML data
    try:
        f = open(join_path(path, manifest_name), 'w')
        try:
            yaml.dump(data, f, default_flow_style=False)
        finally:
            f.close()
    except IOError as e:
        LOG.error('Failed writing manifest: %s', str(e))
        raise GamePathError('Failed writing manifest file.')

#######################################################################################################################

# pylint: disable=R0902
class Game(object):

    _executable_extensions = ('.html', '.htm', '.tzjs', '.canvas.js', '.swf')

    def __init__(self, game_list, game_path=None, slug=None, games_root=None, deploy_enable=False, manifest_name=None):
        self.game_list = game_list
        self.slug = None
        self.title = None
        self.path = None
        self.cover_art = ImageDetail(self, 'cover_art.jpg')
        self.title_logo = ImageDetail(self, 'title_logo.jpg')
        self.modified = None
        self.deployed = None
        self.is_temporary = True
        self.plugin_main = None
        self.canvas_main = None
        self.flash_main = None
        self.mapping_table = None
        self.deploy_files = None
        self.has_mapping_table = None
        self.engine_version = EngineDetail('')
        self.is_multiplayer = False
        self.aspect_ratio = AspectRatioDetail('')
        # if game_path is set, load data,
        # otherwise create a temporary game
        if manifest_name is None:
            self.manifest_name = 'manifest.yaml'
        else:
            self.manifest_name = manifest_name
        if game_path is not None:
            self.load(game_path, self.manifest_name)
        elif slug is not None:
            self.update({'slug': slug})
        self.games_root = games_root
        self.deploy_enable = deploy_enable

    def update(self, data):
        """
        Update the game object with the values supplied
        """
        self._set_slug(data)
        self._set_path(data)
        self._set_title(data)
        self._set_images(data)
        self._set_dates(data)
        self.plugin_main = GameDetail(data.get('plugin_main'))
        self.canvas_main = GameDetail(data.get('canvas_main'))
        self.flash_main = GameDetail(data.get('flash_main'))
        self.mapping_table = GameDetail(data.get('mapping_table'))

        self.deploy_files = ListDetail(data.get('deploy_files', []))