#! /usr/bin/env python3.3
"""Deletes Git Fusion repositories and Perforce artifacts.

During testing, we often create and destroy Git Fusion repositories.
As such, we need an easy way to clean up and try again, without
destroying the entire Perforce server and starting from scratch. In
particular, this script will:

* delete client git-fusion-<space> workspace files
* delete client git-fusion-<space>

If the --all option is given, all git-fusion-<view> clients are
found and deleted, in addition to the following:

* delete object client workspace files
* obliterate //P4GF_DEPOT/objects/...

Invoke with -h for usage information.

"""

import binascii
import os
import sys

import P4
import p4gf_env_config    # pylint: disable=W0611
import p4gf_config
import p4gf_const
import p4gf_context
import p4gf_create_p4
from   p4gf_l10n import _, NTR, log_l10n
import p4gf_log
import p4gf_lock
from   p4gf_object_type import ObjectType
import p4gf_util
import p4gf_view_dirs
import p4gf_translate

LOG = p4gf_log.for_module()

class DeletionMetrics:
    """
    DeletionMetrics captures the number of Perforce objects removed.
    """
    def __init__(self):
        self.clients = 0
        self.groups = 0
        self.files = 0
        self.counters = 0

def raise_if_homedir(homedir, view_name, rm_list):
    """If any path in rm_list is user's home directory, fail with error
    rather than delete the home directory."""
    for e in rm_list:
        if e == homedir:
            raise P4.P4Exception(_("One of view '{}'s directories is"
                                   " user's home directory!").format(view_name))

def print_verbose(args, msg):
    """If args.verbose, print msg, else NOP."""
    if args.verbose:
        print(msg)

#pylint:disable=E0602
# pylint running on python 3.2 does not know about 3.3 features
def _remove_tree(tree, contents_only=True):
    """Delete a directory tree."""
    if not os.path.exists(tree):
        return
    try:
        p4gf_util.rm_dir_contents(tree)
        if not contents_only:
            if os.path.isdir(tree) and not os.path.islink(tree):
                os.rmdir(tree)
            else:
                os.remove(tree)
    except FileNotFoundError as e:
        sys.stderr.write(_('File not found error while removing tree: {}\n').format(e))
    except PermissionError as e:
        sys.stderr.write(_('Permission error while removing tree: {}\n').format(e))
#pylint:enable=E0602

def _tree_scanner(blob):
    """Generator function that returns a series of SHA1's for each tree found
    in the given tree blob. If no trees found, returns nothing.
    The object header should not be part of the input.
    """
    # Format: [mode string] [name string]\0[20-byte-SHA1-value]... (no line seperator)
    # Mask of mode string for trees is 040000 (that is, second digit is a 4)
    # Unsure if tree entries _always_ have a file mode of 040000 (stored as '40000'),
    # so allow for leading zero when checking mode.
    idx = 0
    end = len(blob)
    while idx < end:
        nindex = blob.index(b'\x00', idx) + 21
        # Check entry mode, first non-zero digit is a 4
        if (blob[idx] == 48 and blob[idx + 1] == 52) or blob[idx] == 52:
            yield binascii.hexlify(blob[nindex - 20:nindex]).decode()
        idx = nindex

def _find_commit_files(path, client_name):
    """Generator function that walks a directory tree, returning each commit
    file found for the given client.

    Arguments:
        path -- root of directory tree to walk.
        client_name -- name of client for which to find commits.
    """
    for root, _dirs, files in os.walk(path):
        for fyle in files:
            fpath = os.path.join(root, fyle)
            # Convert the object file path to an ObjectType, but don't
            # let those silly non-P4GF objects stop us.
            ot = ObjectType.commit_from_filepath(fpath)
            if ot and ot.applies_to_view(client_name):
                yield fpath

def _tree_mirror_path(root, sha1):
    '''Construct a path to the object file.'''
    return os.path.join(root, 'trees', sha1[:2], sha1[2:4], sha1[4:])

def _fetch_tree(root, sha1):
    """
    Fetches the Git tree object as raw text, or returns None if the file is missing.
    """
    path = _tree_mirror_path(root, sha1)
    if os.path.exists(path):
        return p4gf_util.local_path_to_git_object(path)
    LOG.warn('Missing file for tree object {}'.format(path))
    return None

def _find_client_commit_objects(args, p4, view_name):
    """Finds the object cache commit files associated only with the given view.
    These are objects that can be deleted from the cache without affecting
    other Git Fusion views. This does not return the eligible tree objects.

    Arguments:
        args -- parsed command line arguments
        p4 -- P4API object, client for object cache, already connected
        view_name -- name of view for which files are to be pruned from cache

    Returns:
        List of cached commit objects to be deleted.
    """

    # Bring the workspace up to date and traverse that rather than
    # fetching large numbers of small files from Perforce.
    repo_commit_objects_path = "{0}/repos/{1}/...".format(p4gf_const.objects_root()
                ,view_name)
    repos_path = "{0}/repos/...".format(p4gf_const.P4GF_DEPOT)
    with p4.at_exception_level(P4.P4.RAISE_NONE):
        # Raises an exception when there are no files to sync?
        p4.run('sync', '-q', repo_commit_objects_path )
        p4.run('sync', '-q' , repos_path)

# TBD Optimization:
# Rather than delete batches of files based on workspace file discovery
# we could do the following -- ??could overwhelm the server or be slower??
#   r = p4.run('delete', repo_commit_objects_path)
#   count = sum([int('depotFile' in rr and rr['action'] == 'delete') for rr in r])
#   r = p4.run("submit", "-d",
#            "Deleting {0} commit objects for repo '{1}'".format(count, view_name))
#   return count

    root = os.path.join(get_p4gf_localroot(p4), 'objects')
    print_verbose(args,
            _("Selecting cached commit objects for '{}'...").format(view_name))
    paths = [os.path.join(root, 'repos', view_name, '...')]
    return paths

def _delete_files(p4, files, view_name=None):
    """
    Delete a set of files, doing so in chunks.
    """
    if view_name:
        msgstr = "Deleting {0} commit objects for repo '" + view_name + "'."
    else:
        msgstr = "Deleting {0} commit objects for all repos."
    count = 0
    bite_size = 1000
    while len(files):
        to_delete = files[:bite_size]
        files = files[bite_size:]
        r = p4.run("delete", to_delete)
        count += sum([int('depotFile' in rr and rr['action'] == 'delete') for rr in r])
        for d in to_delete:
            if os.path.isfile(d):
                os.remove(d)
        r = p4.run("submit", "-d", msgstr.format(count))
    return count

def delete_client(args, p4, client_name, metrics, prune_objs=True):
    """Delete the named Perforce client and its workspace. Raises
    P4Exception if the client is not present, or the client configuration is
    not set up as expected.

    Keyword arguments:
    args        -- parsed command line arguments
    p4          -- Git user's Perforce client
    client_name -- name of client to be deleted
    metrics     -- DeletionMetrics for collecting resulting metrics
    prune_objs  -- if True, delete associated objects from cache

    """
    # pylint: disable=R0912,R0915
    group_list = [p4gf_const.P4GF_GROUP_VIEW_PULL, p4gf_const.P4GF_GROUP_VIEW_PUSH]
    p4.user = p4gf_const.P4GF_USER

    print_verbose(args, _("Checking for client '{}'...").format(client_name))
    if not p4gf_util.spec_exists(p4, 'client', client_name):
        raise P4.P4Exception(_("No such client '{}' defined")
                             .format(client_name))
    view_name = p4gf_util.client_to_view_name(client_name)
    p4gf_dir = p4gf_util.p4_to_p4gf_dir(p4)
    view_dirs = p4gf_view_dirs.from_p4gf_dir(p4gf_dir, view_name)
    p4gf_util.ensure_spec_values(p4, 'client', client_name, {'Root': view_dirs.p4root})

    view_lock = None  # We're clobbering and deleting. Overrule locks.
    with p4gf_context.create_context(view_name, view_lock) as ctx:
        command_path = ctx.client_view_path()

        homedir = os.path.expanduser('~')
        raise_if_homedir(homedir, view_name, view_dirs.view_container)

        # Scan for objects associated only with this view so we can remove them.
        objects_to_delete = []
        if prune_objs:
            objects_to_delete = _find_client_commit_objects(args, p4, view_name)

        # Do we have a repo config file to delete?
        config_file = p4gf_config.depot_path_repo(view_name) + '*'
        config_file_exists = p4gf_util.depot_file_exists(p4, config_file)

        # What counters shall we delete?
        counter_list = []
        counter_list.append(p4gf_context.calc_last_copied_change_counter_name(
                            view_name, p4gf_util.get_server_id()))
        for spec in p4.run('counters', '-u', '-e', "git-fusion-index-last-{},*"
                                                   .format(view_name)):
            counter_list.append(spec['counter'])
        for spec in p4.run('counters', '-u', '-e', "git-fusion-index-branch-{},*"
                                                   .format(view_name)):
            counter_list.append(spec['counter'])

        if not args.delete:
            print(NTR('p4 sync -f {}#none').format(command_path))
            print(NTR('p4 client -f -d {}').format(client_name))
            print(NTR('rm -rf {}').format(view_dirs.view_container))
            print(NTR('Deleting {} objects from //{}/objects/...').format(
                len(objects_to_delete), p4gf_const.P4GF_DEPOT))
            for group_template in group_list:
                group = group_template.format(view=view_name)
                print(NTR('p4 group -a -d {}').format(group))
            for c in counter_list:
                print(NTR('p4 counter -u -d {}').format(c))

            if config_file_exists:
                print(NTR('p4 sync -f {}').format(config_file))
                print(NTR('p4 delete  {}').format(config_file))
                print(NTR('p4 submit -d "Delete repo config for {view_name}" {config_file}')
                      .format(view_name=view_name, config_file=config_file))
        else:
            print_verbose(args, NTR('Removing client files for {}...').format(client_name))
            ctx.p4.run('sync', '-fq', command_path + '#none')
            print_verbose(args, NTR('Deleting client {}...').format(client_name))
            p4.run('client', '-df', client_name)
            metrics.clients += 1
            print_verbose(args, NTR("Deleting repo {0}'s directory {1}...").format(view_name,
                view_dirs.view_container))
            _remove_tree(view_dirs.view_container, contents_only=False)
            metrics.files += _delete_files(p4, objects_to_delete, view_name)
            for group_template in group_list:
                _delete_group(args, p4, group_template.format(view=view_name), metrics)
            for c in counter_list:
                _delete_counter(p4, c, metrics)

            if config_file_exists:
                p4gf_util.p4run_logged(p4, ['sync', '-fq', config_file])
                with p4gf_util.NumberedChangelist(
                        p4=p4, description=_("Delete repo config for '{}'")
                                           .format(view_name)) as nc:
                    nc.p4run(["delete", config_file])
                    nc.submit()
    # pylint: enable=R0912,R0915

def _delete_counter(p4, name, metrics):
    """Attempt to delete counter. Report and continue on error."""
    try:
        p4.run('counter', '-u', '-d', name)
        metrics.counters += 1
    except P4.P4Exception as e:
                        ### NONONO Look for specific message ID not US English text.
        if str(e).find(NTR("No such counter")) < 0:
            LOG.info('failed to delete counter {ctr}: {e}'.
                     format(ctr=name, e=str(e)))

def get_p4gf_localroot(p4):
    """Calculate the local root for the object client."""
    if p4.client != p4gf_util.get_object_client_name():
        raise RuntimeError(_('incorrect p4 client'))
    client = p4.fetch_client()
    rootdir = client["Root"]
    if rootdir.endswith(os.sep):
        rootdir = rootdir[:len(rootdir) - 1]
    client_map = P4.Map(client["View"])
    lhs = client_map.lhs()
    if len(lhs) > 1:
        # not a conforming Git Fusion client, ignore it
        return None
    rpath = client_map.translate(lhs[0])
    localpath = p4gf_context.client_path_to_local(rpath, p4.client, rootdir)
    localroot = p4gf_context.strip_wild(localpath)
    localroot = localroot.rstrip('/')
    return localroot

def _delete_group(args, p4, group_name, metrics):
    """Delete one group, if it exists and it's ours."""
    LOG.debug("_delete_group() {}".format(group_name))
    r = p4.fetch_group(group_name)
    if r and r.get('Owners') and p4gf_const.P4GF_USER in r.get('Owners'):
        print_verbose(args, _("Deleting group '{}'...").format(group_name))
        p4.run('group', '-a', '-d', group_name)
        metrics.groups += 1
    else:
        print_verbose(args, _("Not deleting group '{group}':"
                              " Does not exist or '{user}' is not an owner.")
                            .format(group=group_name, user=p4gf_const.P4GF_USER))

def _delete_cache(args, p4, metrics):
    """Delete all of the Git Fusion cached objects."""
    if not args.no_obliterate:
        print_verbose(args, _('Obliterating object cache...'))
        r = p4.run('obliterate', '-y', '//{}/objects/...'.format(p4gf_const.P4GF_DEPOT))
        results = p4gf_util.first_dict_with_key(r, 'revisionRecDeleted')
        if results:
            metrics.files += int(results['revisionRecDeleted'])

def delete_clients(args, p4, metrics):
    """Delete all of the Git Fusion clients, except the object cache clients.
    """
    views = p4gf_util.view_list(p4)
    if not views:
        print(_('No Git Fusion clients found.'))
        return
    for view in views:
        client = p4gf_util.view_to_client_name(view)
        try:
            delete_client(args, p4, client, metrics, False)
        except P4.P4Exception as e:
            sys.stderr.write(str(e) + '\n')
            sys.exit(1)

def _remove_local_root(localroot):
    """
    Remove the contents of the P4GF local workspace, disregarding whether
    the root is a symbolic link.
    Save and re-write the server-id file after removing contents.
    """
    LOG.debug2("_remove_local_root(): {}".format(localroot))
    # get the server_id
    server_id = p4gf_util.get_server_id()
    _remove_tree(localroot)
    # re-write server_id
    p4gf_util.write_server_id_to_file(server_id)

def _lock_all_repos(p4):
    """
    Quickly acquire locks on all Git Fusion repositories, failing immediately
    (raises RuntimeError) if any repos are currently locked. Waiting would
    only increase the chance of getting blocked on another repo, so scan and
    fail fast instead.

    Returns a list of the CounterLock instances acquired.
    """
    locks = []
    views = p4gf_util.view_list(p4)
    if not views:
        print(_('No Git Fusion clients found.'))
    else:
        for view in views:
            lock = p4gf_lock.view_lock(p4, view)
            lock.acquire(-1)
            # If that didn't raise an error, then add to the list of locks acquired.
            locks.append(lock)
    return locks

def _release_locks(locks):
    """
    Release all of the given locks, reporting any errors to the log.
    """
    for lock in locks:
        # pylint:disable=W0703
        try:
            lock.release()
        except Exception as e:
            LOG.error("Error releasing lock {}: {}".format(lock.counter_name(), e))
        # pylint:enable=W0703

def _prevent_access(p4):
    """
    Prevent further access to Git Fusion while deleting everything.
    Return the previous value of the counter so it can be restored later.
    """
    result = p4.run('counter', '-u', p4gf_const.P4GF_COUNTER_PREVENT_NEW_SESSIONS)
    old_value = None
    if result:
        old_value = p4gf_util.first_value_for_key(result, 'value')
    p4.run('counter', '-u', p4gf_const.P4GF_COUNTER_PREVENT_NEW_SESSIONS, 'true')
    return old_value

#pylint:disable=R0912
# Pylint, would you rather have several functions taking 6+ args, or one complex func?
def delete_all(args, p4, metrics):
    """Find all git-fusion-* clients and remove them, as well as
    the entire object cache (//P4GF_DEPOT/objects/...).

    Keyword arguments:
        args -- parsed command line arguments
        p4   -- Git user's Perforce client
    """
    p4.user = p4gf_const.P4GF_USER
    group_list = [p4gf_const.P4GF_GROUP_PULL, p4gf_const.P4GF_GROUP_PUSH]
    print(_('Connected to {P4PORT}').format(P4PORT=p4.port))
    print_verbose(args, _('Scanning for Git Fusion clients...'))
    client_name = p4gf_util.get_object_client_name()
    locks = _lock_all_repos(p4)
    was_prevented = _prevent_access(p4)
    delete_clients(args, p4, metrics)
    # Retrieve the names of the initialization/upgrade "lock" counters.
    counters = []
    counter_names = ['git-fusion-init-started*',
                     'git-fusion-init-complete*',
                     'git-fusion-upgrade-started*',
                     'git-fusion-upgrade-complete*',
                     'git-fusion-index-*']
    for counter_name in counter_names:
        r = p4.run('counters', '-u', '-e', counter_name)
        for spec in r:
            counters.append(spec['counter'])
    localroot = get_p4gf_localroot(p4)
    if not args.delete:
        if localroot:
            print(NTR('p4 sync -f {}...#none').format(localroot))
            if not args.no_obliterate:
                print(NTR('p4 client -f -d {}').format(client_name))
                print(NTR('rm -rf {}').format(localroot))
        if not args.no_obliterate:
            print(NTR('p4 obliterate -y //{}/objects/...').format(p4gf_const.P4GF_DEPOT))
        for counter in counters:
            print(NTR('p4 counter -u -d {}').format(counter))
        for group in group_list:
            print(NTR('p4 group -a -d {}').format(group))
    else:
        if localroot and not args.no_obliterate:
            print_verbose(args, _("Removing client files for '{}'...").format(client_name))
            p4.run('sync', '-fq', localroot + '/...#none')
            # Need this in order to use --gc later on
            print_verbose(args, _("Deleting client '{}'...").format(client_name))
            p4.run('client', '-df', client_name)
            metrics.clients += 1
            print_verbose(args, _("Deleting client '{}'s workspace...").format(client_name))
            _remove_local_root(localroot)
        _delete_cache(args, p4, metrics)
        print_verbose(args, _('Removing initialization counters...'))
        for counter in counters:
            _delete_counter(p4, counter, metrics)
        for group in group_list:
            _delete_group(args, p4, group, metrics)
    _release_locks(locks)
    if was_prevented and was_prevented != '0':
        p4.run('counter', '-u', p4gf_const.P4GF_COUNTER_PREVENT_NEW_SESSIONS, was_prevented)
    else:
        p4.run('counter', '-u', '-d', p4gf_const.P4GF_COUNTER_PREVENT_NEW_SESSIONS)

def main():
    """
    Process command line arguments and call functions to do the real
    work of cleaning up the Git mirror and Perforce workspaces.
    """
    log_l10n()
    p4gf_util.has_server_id_or_exit()

                        # pylint:disable=C0301
                        # Line too long? Too bad. Keep tabular code tabular.
    # Set up argument parsing.
    parser = p4gf_util.create_arg_parser(
        _('Deletes Git Fusion repositories and workspaces.'))
    parser.add_argument('-a',   '--all',            action='store_true',    help=_('remove all known Git mirrors'))
    parser.add_argument('-y',   '--delete',         action='store_true',    help=_('perform the deletion'))
    parser.add_argument('-v',   '--verbose',        action='store_true',    help=_('print details of deletion process'))
    parser.add_argument('-N',   '--no-obliterate',  action='store_true',    help=_('with the --all option, do not obliterate object cache'))
    parser.add_argument(NTR('views'), metavar=NTR('view'), nargs='*',       help=_('name of view to be deleted'))
    args = parser.parse_args()
                        # pylint:enable=C0301

    # Check that either --all, or 'views' was specified.
    if not args.all and len(args.views) == 0:
        sys.stderr.write(_('Missing view names; try adding --all option.\n'))
        sys.exit(2)

    # Check that --no-obliterate occurs only with --all
    if not args.all and args.no_obliterate:
        sys.stderr.write(_('--no-obliterate permitted only with the --all option.\n'))
        sys.exit(2)

    with p4gf_create_p4.Closer():
        p4 = p4gf_create_p4.create_p4(client=p4gf_util.get_object_client_name())
        if not p4:
            return 2
        # Sanity check the connection (e.g. user logged in?) before proceeding.
        try:
            p4.fetch_client()
        except P4.P4Exception as e:
            sys.stderr.write(_('P4 exception occurred: {}').format(e))
            sys.exit(1)

        metrics = DeletionMetrics()
        if args.all:
            try:
                delete_all(args, p4, metrics)
            except P4.P4Exception as e:
                sys.stderr.write("{}\n".format(e))
                sys.exit(1)
        else:
            # Delete the client(s) for the named view(s).
            for git_view in args.views:
                view_name = p4gf_translate.TranslateReponame.git_to_repo(git_view)
                client_name = p4gf_util.view_to_client_name(view_name)
                try:
                    with p4gf_lock.view_lock(p4, view_name, -1):
                        delete_client(args, p4, client_name, metrics)
                except P4.P4Exception as e:
                    sys.stderr.write("{}\n".format(e))
        if  not args.delete:
            print(_('This was report mode. Use -y to make changes.'))
        else:
            print(_('Deleted {:d} files, {:d} groups, {:d} clients, and {:d} counters.').format(
                metrics.files, metrics.groups, metrics.clients, metrics.counters))
            if args.all:
                print(_('Successfully deleted all repos\n'))
            else:
                print(_('Successfully deleted repos:\n{}').format("\n".join(args.views)))
#pylint:enable=R0912

if __name__ == "__main__":
    p4gf_log.run_with_exception_logger(main, write_to_stderr=True)


"""
Model for rolling horizon used in dispatch.
"""

from andes.core import (ModelData, NumParam, DataParam)  # NOQA
from andes.models.timeseries import (str_list_iconv, str_list_oconv)  # NOQA
from ams.core.model import Model  # NOQA

class TimeSlot(ModelData, Model):
    """
    Time slot data for rolling horizon.
    """

    def __init__(self, system=None, config=None):
        ModelData.__init__(self)
        Model.__init__(self, system, config)
        self.group = 'Horizon'

        self.sd = NumParam(info='zonal load scaling factor',
                           tex_name=r's_{d}',
                           iconvert=str_list_iconv,
                           oconvert=str_list_oconv,
                           vtype=float,
                           )

class EDTSlot(TimeSlot):
    """
    Time slot model for ED.
    """

    def __init__(self, system=None, config=None):
        TimeSlot.__init__(self, system, config)

class UCTSlot(TimeSlot):
    """
    Time slot model for UC.
    """

    def __init__(self, system=None, config=None):
        TimeSlot.__init__(self, system, config)


#!/usr/bin/env python
# -*- coding: utf8 -*-
# *****************************************************************
# **       PTS -- Python Toolkit for working with SKIRT          **
# **       © Astronomical Observatory, Ghent University          **
# *****************************************************************

## \package pts.core.simulation.output Contains the SimulationOutput class.

# -----------------------------------------------------------------

# Ensure Python 3 compatibility
from __future__ import absolute_import, division, print_function

# Import standard modules
from collections import defaultdict

# Import the relevant PTS classes and modules
from ..tools import filesystem as fs
from ..basics.map import Map
from ..tools.utils import lazyproperty

# -----------------------------------------------------------------

# The various output types
output_types = Map()
output_types.isrf = "isrf"
output_types.absorption = "abs"
output_types.temperature = "temp"
output_types.seds = "sed"
output_types.images = "image"
output_types.total_images = "image-total"
output_types.direct_images = "image-direct"
output_types.transparent_images = "image-transparent"
output_types.scattered_images = "image-scattered"
output_types.dust_images = "image-dust"
output_types.dust_scattered_images = "image-dustscattered"
output_types.cell_temperature = "celltemp"
output_types.logfiles = "log"
output_types.wavelengths = "wavelengths"
output_types.grid = "grid"
output_types.gdensity = "grho"
output_types.tdensity = "trho"
output_types.tree = "tree"
output_types.convergence = "convergence"
output_types.dust_mass = "mass"
output_types.dust_mix_properties = "mean"
output_types.dust_optical_properties = "optical"
output_types.dust_grain_sizes = "size"
output_types.parameters = "parameters"

# -----------------------------------------------------------------

# Description of the output types
output_type_choices = dict()
output_type_choices[output_types.isrf] = "interstellar radiation field strength"
output_type_choices[output_types.absorption] = "absorption luminosities"
output_type_choices[output_types.temperature] = "temperature"
output_type_choices[output_types.seds] = "all SEDs"
output_type_choices[output_types.images] = "all datacubes"
output_type_choices[output_types.total_images] = "datacubes of total emission"
output_type_choices[output_types.direct_images] = "datacubes of direct emission"
output_type_choices[output_types.transparent_images] = "datacubes of transparent emission"
output_type_choices[output_types.scattered_images] = "datacubes of scattered emission"
output_type_choices[output_types.dust_images] = "datacubes of dust emission"
output_type_choices[output_types.dust_scattered_images] = "datacubes of scattered dust emission"
output_type_choices[output_types.cell_temperature] = "temperature per dust cell"
output_type_choices[output_types.logfiles] = "log files"
output_type_choices[output_types.wavelengths] = "wavelength files"
output_type_choices[output_types.grid] = "grid files"
output_type_choices[output_types.gdensity] = "grid dust density"
output_type_choices[output_types.tdensity] = "theoretical dust density"
output_type_choices[output_types.tree] = "dust grid tree data file"
output_type_choices[output_types.convergence] = "convergence file"
output_type_choices[output_types.dust_mass] = "dust population masses"
output_type_choices[output_types.dust_mix_properties] = "combined dust mix properties"
output_type_choices[output_types.dust_optical_properties] = "optical dust population properties"
output_type_choices[output_types.dust_grain_sizes] = "dust grain size information"
output_type_choices[output_types.parameters] = "parameter files"

# -----------------------------------------------------------------

def get_output_type(filename):

    """
    This function ...
    :param filename:
    :return:
    """

    ## ISRF
    if filename.endswith("_ds_isrf.dat"): return output_types.isrf

    ## Absorption
    elif filename.endswith("_ds_abs.dat"): return output_types.absorption

    ## Temperature
    elif "_ds_temp" in filename and filename.endswith(".fits"): return output_types.temperature

    ## SED
    elif filename.endswith("_sed.dat"): return output_types.seds

    ## Total datacubes
    elif filename.endswith("_total.fits"): return output_types.total_images

    ## Direct datacubes
    elif filename.endswith("_direct.fits"): return output_types.direct_images

    ## Transparent datacubes
    elif filename.endswith("_transparent.fits"): return output_types.transparent_images

    ## Scattered datacubes
    elif filename.endswith("_scattered.fits"): return output_types.scattered_images

    ## Dust datacubes
    elif filename.endswith("_dust.fits"): return output_types.dust_images

    ## Dust scattered datacubes
    elif filename.endswith("_dustscattered.fits"): return output_types.dust_scattered_images

    ## Cell temperature data
    elif filename.endswith("_ds_celltemps.dat"): return output_types.cell_temperature

    ## Log files
    elif "_log" in filename and filename.endswith(".txt"): return output_types.logfiles

    ## Wavelength files
    elif filename.endswith("_wavelengths.dat"): return output_types.wavelengths

    ## Grid structure data
    elif "_ds_grid" in filename and filename.endswith(".dat"): return output_types.grid

    ## Grid dust density
    elif "_ds_grho" in filename and filename.endswith(".fits"): return output_types.gdensity

    ## Theoretical dust density
    elif "_ds_trho" in filename and filename.endswith(".fits"): return output_types.tdensity

    ## Dust grid onvergence
    elif filename.endswith("_ds_convergence.dat"): return output_types.convergence

    ## Dust grid tree data
    elif "_ds_tree" in filename and filename.endswith(".dat"): return output_types.tree

    ## Dust mass
    elif "ds_mix_" in filename and "_mass" in filename: return output_types.dust_mass

    ## Dust mix properties
    elif "ds_mix_" in filename and "_mean" in filename: return output_types.dust_mix_properties

    ## Dust optical properties
    elif "ds_mix_" in filename and "_opti" in filename: return output_types.dust_optical_properties

    ## Dust grain sizes
    elif "ds_mix_" in filename and "_size" in filename: return output_types.dust_grain_sizes

    ## Parameter files
    elif filename.endswith("_parameters.xml") or filename.endswith("_parameters.tex"): return output_types.parameters

    # No match
    return None

# -----------------------------------------------------------------

parent_output_types = dict()
parent_output_types[output_types.total_images] = output_types.images
parent_output_types[output_types.direct_images] = output_types.images
parent_output_types[output_types.transparent_images] = output_types.images
parent_output_types[output_types.scattered_images] = output_types.images
parent_output_types[output_types.dust_images] = output_types.images
parent_output_types[output_types.dust_scattered_images] = output_types.images

# -----------------------------------------------------------------

def get_parent_type(output_type):

    """
    This function ...
    :param output_type:
    :return:
    """

    if output_type in parent_output_types: return parent_output_types[output_type]
    else: return output_type

# -----------------------------------------------------------------

other_name = "other"

# -----------------------------------------------------------------

class SimulationOutput(object):

    """
    This class ...
    """

    def __init__(self, *args):

        """
        The constructor ...
        :param args:
        """

        # The simulation prefix
        self.prefix = None

        # Dictionary of filepaths
        self.paths = defaultdict(list)

        # Loop over the filepaths, categorize
        for filepath in args:
            filename = fs.name(filepath)
            prefix = filename.split("_")[0]
            if self.prefix is None: self.prefix = prefix
            elif self.prefix != prefix: raise ValueError("Cannot add files with different simulation prefixes")
            output_type = get_output_type(filename)
            if output_type is None: self.paths[other_name].append(filepath)
            else: self.paths[output_type].append(filepath)

    # -----------------------------------------------------------------

    @classmethod
    def from_paths(cls, paths):

        """
        This function ...
        :param paths:
        :return:
        """

        return cls(*paths)

    # -----------------------------------------------------------------

    @classmethod
    def from_cwd(cls, prefix=None):

        """
        This function ...
        :param prefix:
        :return:
        """

        return cls.from_directory(fs.cwd(), prefix=prefix)

    # -----------------------------------------------------------------

    @classmethod
    def from_directory(cls, path, prefix=None):

        """
        This function ...
        :param path:
        :param prefix:
        :return:
        """

        return cls.from_paths(fs.files_in_path(path, startswith=prefix, not_extension="ski"))

    # -----------------------------------------------------------------

    @classmethod
    def from_remote_directory(cls, path, remote, prefix=None):

        """
        This function ...
        :param path:
        :param remote:
        :param prefix:
        :return:
        """

        return cls.from_paths(remote.files_in_path(path, startswith=prefix, not_extension="ski"))

    # -----------------------------------------------------------------

    def __iter__(self):

        """
        This function ...
        :return:
        """

        for path in self.isrf: yield path
        for path in self.absorption: yield path
        for path in self.temperature: yield path
        for path in self.seds: yield path
        for path in self.total_images: yield path
        for path in self.direct_images: yield path
        for path in self.transparent_images: yield path
        for path in self.scattered_images: yield path
        for path in self.dust_images: yield path
        for path in self.dust_scattered_images: yield path
        for path in self.cell_temperature: yield path
        for path in self.logfiles: yield path
        for path in self.wavelengths: yield path
        for path in self.grid: yield path
        for path in self.gdensity: yield path
        for path in self.tdensity: yield path
        for path in self.tree: yield path
        for path in self.convergence: yield path
        for path in self.dust_mass: yield path
        for path in self.dust_mix_properties: yield path
        for path in self.dust_optical_properties: yield path
        for path in self.dust_grain_sizes: yield path
        for path in self.parameters: yield path
        for path in self.other: yield path

    # -----------------------------------------------------------------

    def __len__(self):

        """
        This function ...
        :return:
        """

        total = 0
        if self.has_isrf: total += self.nisrf
        if self.has_absorption: total += self.nabsorption
        if self.has_temperature: total += self.ntemperature
        if self.has_seds: total += self.nseds
        if self.has_total_images: total += self.ntotal_images
        if self.has_direct_images: total += self.ndirect_images
        if self.has_transparent_images: total += self.ntransparent_images
        if self.has_scattered_images: total += self.nscattered_images
        if self.has_dust_images: total += self.ndust_images
        if self.has_dust_scattered_images: total += self.ndust_scattered_images
        if self.has_cell_temperature: total += self.ncell_temperature
        if self.has_logfiles: total += self.nlogfiles
        if self.has_wavelengths: total += self.nwavelengths
        if self.has_grid: total += self.ngrid
        if self.has_gdensity: total += self.ngdensity
        if self.has_tdensity: total += self.ntdensity
        if self.has_tree: total += self.ntree
        if self.has_convergence: total += self.nconvergence
        if self.has_dust_mass: total += self.ndust_mass
        if self.has_dust_mix_properties: total += self.ndust_mix_properties
        if self.has_dust_optical_properties: total += self.ndust_optical_properties
        if self.has_dust_grain_sizes: total += self.ndust_grain_sizes
        if self.has_parameters: total += self.nparameters
        if self.has_other: total += self.nother
        return total

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def nisrf(self):

        """
        This function ...
        :return:
        """

        return len(self.paths[output_types.isrf])

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def has_isrf(self):

        """
        This function ...
        :return:
        """

        return output_types.isrf in self.paths and self.nisrf > 0

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def isrf(self):

        """
        This function ...
        :return:
        """

        if not self.has_isrf: return []
        return self.paths[output_types.isrf]

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def nabsorption(self):

        """
        This function ...
        :return:
        """

        return len(self.paths[output_types.absorption])

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def has_absorption(self):

        """
        This function ...
        :return:
        """

        return output_types.absorption in self.paths and self.nabsorption > 0

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def absorption(self):

        """
        This function ...
        :return:
        """

        if not self.has_absorption: return []
        return self.paths[output_types.absorption]

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def ntemperature(self):

        """
        This function ...
        :return:
        """

        return len(self.paths[output_types.temperature])

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def has_temperature(self):

        """
        This function ...
        :return:
        """

        return output_types.temperature in self.paths and self.ntemperature > 0

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def temperature(self):

        """
        This function ...
        :return:
        """

        if not self.has_temperature: return []
        return self.paths[output_types.temperature]

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def nseds(self):

        """
        This function ...
        :return:
        """

        return len(self.paths[output_types.seds])

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def has_seds(self):

        """
        This function ...
        :return:
        """

        return output_types.seds in self.paths and self.nseds > 0

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def seds(self):

        """
        This function ...
        :return:
        """

        if not self.has_seds: return []
        return self.paths[output_types.seds]

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def nimages(self):

        """
        This function ...
        :return:
        """

        total = 0
        if self.has_total_images: total += self.ntotal_images
        if self.has_direct_images: total += self.ndirect_images
        if self.has_transparent_images: total += self.ntransparent_images
        if self.has_scattered_images: total += self.nscattered_images
        if self.has_dust_images: total += self.ndust_images
        if self.has_dust_scattered_images: total += self.ndust_scattered_images
        return total

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def has_images(self):

        """
        This function ...
        :return:
        """

        return self.nimages > 0

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def images(self):

        """
        This function ...
        :return:
        """

        paths = []
        if self.has_total_images: paths.extend(self.total_images)
        if self.has_direct_images: paths.extend(self.direct_images)
        if self.has_transparent_images: paths.extend(self.transparent_images)
        if self.has_scattered_images: paths.extend(self.scattered_images)
        if self.has_dust_images: paths.extend(self.dust_images)
        if self.has_dust_scattered_images: paths.extend(self.dust_scattered_images)
        return paths

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def ntotal_images(self):

        """
        Thisf unctin ...
        :return:
        """

        return len(self.paths[output_types.total_images])

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def has_total_images(self):

        """
        This function ...
        :return:
        """

        return output_types.total_images in self.paths and self.ntotal_images > 0

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def total_images(self):

        """
        This function ...
        :return:
        """

        if not self.has_total_images: return []
        else: return self.paths[output_types.total_images]

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def ndirect_images(self):

        """
        This function ...
        :return:
        """

        return len(self.paths[output_types.direct_images])

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def has_direct_images(self):

        """
        This function ...
        :return:
        """

        return output_types.direct_images in self.paths and self.ndirect_images > 0

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def direct_images(self):

        """
        This function ...
        :return:
        """

        if not self.has_direct_images: return []
        else: return self.paths[output_types.direct_images]

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def ntransparent_images(self):

        """
        This function ...
        :return:
        """

        return len(self.paths[output_types.transparent_images])

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def has_transparent_images(self):

        """
        This function ...
        :return:
        """

        return output_types.transparent_images in self.paths and self.ntransparent_images > 0

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def transparent_images(self):

        """
        This function ...
        :return:
        """

        if not self.has_transparent_images: return []
        else: return self.paths[output_types.transparent_images]

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def nscattered_images(self):

        """
        This function ...
        :return:
        """

        return len(self.paths[output_types.scattered_images])

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def has_scattered_images(self):

        """
        This function ...
        :return:
        """

        return output_types.scattered_images in self.paths and self.nscattered_images > 0

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def scattered_images(self):

        """
        This function ...
        :return:
        """

        if not self.has_scattered_images: return []
        else: return self.paths[output_types.scattered_images]

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def ndust_images(self):

        """
        This function ...
        :return:
        """

        return len(self.paths[output_types.dust_images])

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def has_dust_images(self):

        """
        This function ...
        :return:
        """

        return output_types.dust_images in self.paths and self.ndust_images > 0

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def dust_images(self):

        """
        This function ...
        :return:
        """

        if not self.has_dust_images: return []
        else: return self.paths[output_types.dust_images]

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def ndust_scattered_images(self):

        """
        This function ...
        :return:
        """

        return len(self.paths[output_types.dust_scattered_images])

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def has_dust_scattered_images(self):

        """
        This function ...
        :return:
        """

        return output_types.dust_scattered_images in self.paths and self.ndust_scattered_images > 0

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def dust_scattered_images(self):

        """
        This function ...
        :return:
        """

        if not self.has_dust_scattered_images: return []
        else: return self.paths[output_types.dust_scattered_images]

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def ncell_temperature(self):

        """
        This function ...
        :return:
        """

        return len(self.paths[output_types.cell_temperature])

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def has_cell_temperature(self):

        """
        This function ...
        :return:
        """

        return output_types.cell_temperature in self.paths and self.ncell_temperature > 0

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def cell_temperature(self):

        """
        This function ...
        :return:
        """

        if not self.has_cell_temperature: return []
        else: return self.paths[output_types.cell_temperature]

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def nlogfiles(self):

        """
        This function ...
        :return:
        """

        return len(self.paths[output_types.logfiles])

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def has_logfiles(self):

        """
        This funtion ...
        :return:
        """

        return output_types.logfiles in self.paths and self.nlogfiles > 0

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def logfiles(self):

        """
        This function ...
        :return:
        """

        if not self.has_logfiles: return []
        else: return self.paths[output_types.logfiles]

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def nwavelengths(self):

        """
        This function ...
        :return:
        """

        return len(self.paths[output_types.wavelengths])

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def has_wavelengths(self):

        """
        This function ...
        :return:
        """

        return output_types.wavelengths in self.paths and self.nwavelengths > 0

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def wavelengths(self):

        """
        This function ...
        :return:
        """

        if not self.has_wavelengths: return []
        else: return self.paths[output_types.wavelengths]

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def ngrid(self):

        """
        This function ...
        :return:
        """

        return len(self.paths[output_types.grid])

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def has_grid(self):

        """
        Thisf unction ...
        :return:
        """

        return output_types.grid in self.paths and self.ngrid > 0

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def grid(self):

        """
        This function ...
        :return:
        """

        if not self.has_grid: return []
        else: return self.paths[output_types.grid]

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def ngdensity(self):

        """
        This function ...
        :return:
        """

        return len(self.paths[output_types.gdensity])

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def has_gdensity(self):

        """
        This function ...
        :return:
        """

        return output_types.gdensity in self.paths and self.ngdensity > 0

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def gdensity(self):

        """
        This fucntion ...
        :return:
        """

        if not self.has_gdensity: return []
        else: return self.paths[output_types.gdensity]

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def ntdensity(self):

        """
        This function ...
        :return:
        """

        return len(self.paths[output_types.tdensity])

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def has_tdensity(self):

        """
        This function ...
        :return:
        """

        return output_types.tdensity in self.paths and self.ntdensity > 0

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def tdensity(self):

        """
        This function ...
        :return:
        """

        if not self.has_tdensity: return []
        else: return self.paths[output_types.tdensity]

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def ntree(self):

        """
        This function ...
        :return:
        """

        return len(self.paths[output_types.tree])

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def has_tree(self):

        """
        Thisf unction ...
        :return:
        """

        return output_types.tree in self.paths and self.ntree > 0

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def tree(self):

        """
        This function ...
        :return:
        """

        if not self.has_tree: return []
        else: return self.paths[output_types.tree]

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def nconvergence(self):

        """
        This function ...
        :return:
        """

        return len(self.paths[output_types.convergence])

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def has_convergence(self):

        """
        This function ...
        :return:
        """

        return output_types.convergence in self.paths and self.nconvergence > 0

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def convergence(self):

        """
        This function ...
        :return:
        """

        if not self.has_convergence: return []
        else: return self.paths[output_types.convergence]

    # -----------------------------------------------------------------

    @lazyproperty
    def ndust_mass(self):

        """
        This function ...
        :return:
        """

        return len(self.paths[output_types.dust_mass])

    # -----------------------------------------------------------------

    @lazyproperty
    def has_dust_mass(self):

        """
        This function ...
        :return:
        """

        return output_types.dust_mass in self.paths and self.ndust_mass > 0

    # -----------------------------------------------------------------

    @lazyproperty
    def dust_mass(self):

        """
        This function ...
        :return:
        """

        if not self.has_dust_mass: return []
        else: return self.paths[output_types.dust_mass]

    # -----------------------------------------------------------------

    @lazyproperty
    def ndust_mix_properties(self):

        """
        This function ...
        :return:
        """

        return len(self.paths[output_types.dust_mix_properties])

    # -----------------------------------------------------------------

    @lazyproperty
    def has_dust_mix_properties(self):

        """
        This function ...
        :return:
        """

        return output_types.dust_mix_properties in self.paths and self.ndust_mix_properties > 0

    # -----------------------------------------------------------------

    @lazyproperty
    def dust_mix_properties(self):

        """
        This function ...
        :return:
        """

        if not self.has_dust_mix_properties: return []
        else: return self.paths[output_types.dust_mix_properties]

    # -----------------------------------------------------------------

    @lazyproperty
    def ndust_optical_properties(self):

        """
        This function ...
        :return:
        """

        return len(self.paths[output_types.dust_optical_properties])

    # -----------------------------------------------------------------

    @lazyproperty
    def has_dust_optical_properties(self):

        """
        This function ...
        :return:
        """

        return output_types.dust_optical_properties in self.paths and self.ndust_optical_properties > 0

    # -----------------------------------------------------------------

    @lazyproperty
    def dust_optical_properties(self):

        """
        This function ...
        :return:
        """

        if not self.has_dust_optical_properties: return []
        else: return self.paths[output_types.dust_optical_properties]

    # -----------------------------------------------------------------

    @lazyproperty
    def ndust_grain_sizes(self):

        """
        This function ...
        :return:
        """

        return len(self.paths[output_types.dust_grain_sizes])

    # -----------------------------------------------------------------

    @lazyproperty
    def has_dust_grain_sizes(self):

        """
        This function ...
        :return:
        """

        return output_types.dust_grain_sizes in self.paths and self.ndust_grain_sizes > 0

    # -----------------------------------------------------------------

    @lazyproperty
    def dust_grain_sizes(self):

        """
        This function ...
        :return:
        """

        if not self.has_dust_grain_sizes: return []
        else: return self.paths[output_types.dust_grain_sizes]

    # -----------------------------------------------------------------

    @lazyproperty
    def nparameters(self):

        """
        This function ...
        :return:
        """

        return len(self.paths[output_types.parameters])

    # -----------------------------------------------------------------

    @lazyproperty
    def has_parameters(self):

        """
        This function ...
        :return:
        """

        return output_types.parameters in self.paths and self.nparameters > 0

    # -----------------------------------------------------------------

    @lazyproperty
    def parameters(self):

        """
        This function ...
        :return:
        """

        if not self.has_parameters: return []
        else: return self.paths[output_types.parameters]

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def nother(self):

        """
        This function ...
        :return:
        """

        return len(self.paths[other_name])

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def has_other(self):

        """
        This function ...
        :return:
        """

        return other_name in self.paths and self.nother > 0

    # -----------------------------------------------------------------

    #@property
    @lazyproperty
    def other(self):

        """
        This function ...
        :return:
        """

        if not self.has_other: return []
        else: return self.paths[other_name]

    # -----------------------------------------------------------------

    @lazyproperty
    def directory(self):

        """
        This function ...
        :return:
        """

        path = None
        for filepath in self.isrf:
            if path is None: path = fs.directory_of(filepath)
            elif path != fs.directory_of(filepath): raise ValueError("Output files are not in the same directory")
        for filepath in self.absorption:
            if path is None: path = fs.directory_of(filepath)
            elif path != fs.directory_of(filepath): raise ValueError("Output files are not in the same directory")
        for filepath in self.temperature:
            if path is None: path = fs.directory_of(filepath)
            elif path != fs.directory_of(filepath): raise ValueError("Output files are not in the same directory")
        for filepath in self.seds:
            if path is None: path = fs.directory_of(filepath)
            elif path != fs.directory_of(filepath): raise ValueError("Output files are not in the same directory")
        for filepath in self.total_images:
            if path is None: path = fs.directory_of(filepath)
            elif path != fs.directory_of(filepath): raise ValueError("Output files are not in the same directory")
        for filepath in self.direct_images:
            if path is None: path = fs.directory_of(filepath)
            elif path != fs.directory_of(filepath): raise ValueError("Output files are not in the same directory")
        for filepath in self.transparent_images:
            if path is None: path = fs.directory_of(filepath)
            elif path != fs.directory_of(filepath): raise ValueError("Output files are not in the same directory")
        for filepath in self.scattered_images:
            if path is None: path = fs.directory_of(filepath)
            elif path != fs.directory_of(filepath): raise ValueError("Output files are not in the same directory")
        for filepath in self.dust_images:
            if path is None: path = fs.directory_of(filepath)
            elif path != fs.directory_of(filepath): raise ValueError("Output files are not in the same directory")
        for filepath in self.dust_scattered_images:
            if path is None: path = fs.directory_of(filepath)
            elif path != fs.directory_of(filepath): raise ValueError("Output files are not in the same directory")
        for filepath in self.cell_temperature:
            if path is None: path = fs.directory_of(filepath)
            elif path != fs.directory_of(filepath): raise ValueError("Output files are not in the same directory")
        for filepath in self.logfiles:
            if path is None: path = fs.directory_of(filepath)
            elif path != fs.directory_of(filepath): raise ValueError("Output files are not in the same directory")
        for filepath in self.wavelengths:
            if path is None: path = fs.directory_of(filepath)
            elif path != fs.directory_of(filepath): raise ValueError("Output files are not in the same directory")
        for filepath in self.grid:
            if path is None: path = fs.directory_of(filepath)
            elif path != fs.directory_of(filepath): raise ValueError("Output files are not in the same directory")
        for filepath in self.gdensity:
            if path is None: path = fs.directory_of(filepath)
            elif path != fs.directory_of(filepath): raise ValueError("Output files are not in the same directory")
        for filepath in self.tdensity:
            if path is None: path = fs.directory_of(filepath)
            elif path != fs.directory_of(filepath): raise ValueError("Output files are not in the same directory")
        for filepath in self.tree:
            if path is None: path = fs.directory_of(filepath)
            elif path != fs.directory_of(filepath): raise ValueError("Output files are not in the same directory")
        for filepath in self.convergence:
            if path is None: path = fs.directory_of(filepath)
            elif path != fs.directory_of(filepath): raise ValueError("Output files are not in the same directory")
        for filepath in self.dust_mass:
            if path is None: path = fs.directory_of(filepath)
            elif path != fs.directory_of(filepath): raise ValueError("Output files are not in the same directory")
        for filepath in self.dust_mix_properties:
            if path is None: path = fs.directory_of(filepath)
            elif path != fs.directory_of(filepath): raise ValueError("Output files are not in the same directory")
        for filepath in self.dust_optical_properties:
            if path is None: path = fs.directory_of(filepath)
            elif path != fs.directory_of(filepath): raise ValueError("Output files are not in the same directory")
        for filepath in self.dust_grain_sizes:
            if path is None: path = fs.directory_of(filepath)
            elif path != fs.directory_of(filepath): raise ValueError("Output files are not in the same directory")
        for filepath in self.parameters:
            if path is None: path = fs.directory_of(filepath)
            elif path != fs.directory_of(filepath): raise ValueError("Output files are not in the same directory")
        for filepath in self.other:
            if path is None: path = fs.directory_of(filepath)
            elif path != fs.directory_of(filepath): raise ValueError("Output files are not in the same directory")

        # Return the directory path
        return path

    # -----------------------------------------------------------------

    def relative_path(self, path):

        """
        Thisf unction ...
        :param path:
        :return:
        """

        return fs.relative_to(path, self.directory)

    # -----------------------------------------------------------------

    def __str__(self):

        """
        This function ...
        :return:
        """

        return self.to_string()

    # -----------------------------------------------------------------

    def to_string(self, line_prefix=""):

        """
        This function ...
        :param line_prefix:
        :return:
        """

        from ..tools import formatting as fmt

        lines = []

        # ISRF
        if self.has_isrf:
            lines.append(line_prefix)

            # Add title
            title = fmt.green + fmt.underlined + output_type_choices[output_types.isrf].capitalize() + fmt.reset + " (" + str(self.nisrf) + "):"
            lines.append(line_prefix + title)
            lines.append(line_prefix)

            # Add paths
            for path in self.isrf: lines.append(line_prefix + " - " + self.relative_path(path))

        # Absorption
        if self.has_absorption:
            lines.append(line_prefix)

            # Add title
            title = fmt.green + fmt.underlined + output_type_choices[output_types.absorption].capitalize() + fmt.reset + " (" + str(self.nabsorption) + "):"
            lines.append(line_prefix + title)
            lines.append(line_prefix)

            # Add paths
            for path in self.absorption: lines.append(line_prefix + " - " + self.relative_path(path))

        # Temperature
        if self.has_temperature:
            lines.append(line_prefix)

            # Add title
            title = fmt.green + fmt.underlined + output_type_choices[output_types.temperature].capitalize() + fmt.reset + " (" + str(self.ntemperature) + "):"
            lines.append(line_prefix + title)
            lines.append(line_prefix)

            # Add paths
            for path in self.temperature: lines.append(line_prefix + " - " + self.relative_path(path))

        # SEDs
        if self.has_seds:
            lines.append(line_prefix)

            # Add title
            title = fmt.green + fmt.underlined + output_type_choices[output_types.seds].capitalize() + fmt.reset + " (" + str(self.nseds) + "):"
            lines.append(line_prefix + title)
            lines.append(line_prefix)

            # Add paths
            for path in self.seds: lines.append(line_prefix + " - " + self.relative_path(path))

        # Total images
        if self.has_total_images:
            lines.append(line_prefix)

            # Add title
            title = fmt.green + fmt.underlined + output_type_choices[output_types.total_images].capitalize() + fmt.reset + " (" + str(self.ntotal_images) + "):"
            lines.append(line_prefix + title)
            lines.append(line_prefix)

            # Add paths
            for path in self.total_images: lines.append(line_prefix + " - " + self.relative_path(path))

        # Direct images
        if self.has_direct_images:
            lines.append(line_prefix)

            # Add title
            title = fmt.green + fmt.underlined + output_type_choices[output_types.direct_images].capitalize() + fmt.reset + " (" + str(self.ndirect_images) + "):"
            lines.append(line_prefix + title)
            lines.append(line_prefix)

            # Add paths
            for path in self.direct_images: lines.append(line_prefix + " - " + self.relative_path(path))

        # Transparent images
        if self.has_transparent_images:
            lines.append(line_prefix)

            # Add title
            title = fmt.green + fmt.underlined + output_type_choices[output_types.transparent_images].capitalize() + fmt.reset + " (" + str(self.ntransparent_images) + "):"
            lines.append(line_prefix + title)
            lines.append(line_prefix)

            # Add paths
            for path in self.transparent_images: lines.append(line_prefix + " - " + self.relative_path(path))

        # Scattered images
        if self.has_scattered_images:
            lines.append(line_prefix)

            # Add title
            title = fmt.green + fmt.underlined + output_type_choices[output_types.scattered_images].capitalize() + fmt.reset + " (" + str(self.nscattered_images) + "):"
            lines.append(line_prefix + title)
            lines.append(line_prefix)

            # Add paths
            for path in self.scattered_images: lines.append(line_prefix + " - " + self.relative_path(path))

        # Dust images
        if self.has_dust_images:
            lines.append(line_prefix)

            # Add title
            title = fmt.green + fmt.underlined + output_type_choices[output_types.dust_images].capitalize() + fmt.reset + " (" + str(self.ndust_images) + "):"
            lines.append(line_prefix + title)
            lines.append(line_prefix)

            # Add paths
            for path in self.dust_images: lines.append(line_prefix + " - " + self.relative_path(path))

        # Dust scattered images
        if self.has_dust_scattered_images:
            lines.append(line_prefix)

            # Add title
            title = fmt.green + fmt.underlined + output_type_choices[output_types.dust_scattered_images].capitalize() + fmt.reset + " (" + str(self.ndust_scattered_images) + "):"
            lines.append(line_prefix + title)
            lines.append(line_prefix)

            # Add paths
            for path in self.dust_scattered_images: lines.append(line_prefix + " - " + self.relative_path(path))

        # Cell temperature
        if self.has_cell_temperature:
            lines.append(line_prefix)

            # Add title
            title = fmt.green + fmt.underlined + output_type_choices[output_types.cell_temperature].capitalize() + fmt.reset + " (" + str(self.ncell_temperature) + "):"
            lines.append(line_prefix + title)
            lines.append(line_prefix)

            # Add paths
            for path in self.cell_temperature: lines.append(line_prefix + " - " + self.relative_path(path))

        # Logfiles
        if self.has_logfiles:
            lines.append(line_prefix)

            # Add title
            title = fmt.green + fmt.underlined + output_type_choices[output_types.logfiles].capitalize() + fmt.reset + " (" + str(self.nlogfiles) + "):"
            lines.append(line_prefix + title)
            lines.append(line_prefix)

            # Add paths
            for path in self.logfiles: lines.append(line_prefix + " - " + self.relative_path(path))

        # Wavelengths
        if self.has_wavelengths:
            lines.append(line_prefix)

            # Add title
            title = fmt.green + fmt.underlined + output_type_choices[output_types.wavelengths].capitalize() + fmt.reset + " (" + str(self.nwavelengths) + "):"
            lines.append(line_prefix + title)
            lines.append(line_prefix)

            # Add paths
            for path in self.wavelengths: lines.append(line_prefix + " - " + self.relative_path(path))

        # Grid
        if self.has_grid:
            lines.append(line_prefix)

            # Add title
            title = fmt.green + fmt.underlined + output_type_choices[output_types.grid].capitalize() + fmt.reset + " (" + str(self.ngrid) + "):"
            lines.append(line_prefix + title)
            lines.append(line_prefix)

            # Add path
            for path in self.grid: lines.append(line_prefix + " - " + self.relative_path(path))

        # Density
        if self.has_gdensity:
            lines.append(line_prefix)

            # Add title
            title = fmt.green + fmt.underlined + output_type_choices[output_types.gdensity].capitalize() + fmt.reset + " (" + str(self.ngdensity ) + "):"
            lines.append(line_prefix + title)
            lines.append(line_prefix)

            # Add path
            for path in self.gdensity: lines.append(line_prefix + " - " + self.relative_path(path))

        # Density
        if self.has_tdensity:
            lines.append(line_prefix)

            # Add title
            title = fmt.green + fmt.underlined + output_type_choices[output_types.tdensity].capitalize() + fmt.reset + " (" + str(self.ntdensity) + "):"
            lines.append(line_prefix + title)
            lines.append(line_prefix)

            # Add path
            for path in self.tdensity: lines.append(line_prefix + " - " + self.relative_path(path))

        # Tree
        if self.has_tree:
            lines.append(line_prefix)

            # Add title
            title = fmt.green + fmt.underlined + output_type_choices[output_types.tree].capitalize() + fmt.reset + " (" + str(self.ntree) + "):"
            lines.append(line_prefix + title)
            lines.append(line_prefix)

            # Add paths
            for path in self.tree: lines.append(line_prefix + " - " + self.relative_path(path))

        # Convergence
        if self.has_convergence:
            lines.append(line_prefix)

            # Add title
            title = fmt.green + fmt.underlined + output_type_choices[output_types.convergence].capitalize() + fmt.reset + " (" + str(self.nconvergence) + "):"
            lines.append(line_prefix + title)
            lines.append(line_prefix)

            # Add paths
            for path in self.convergence: lines.append(line_prefix + " - " + self.relative_path(path))

        # Dust mass
        if self.has_dust_mass:
            lines.append(line_prefix)

            # Add title
            title = fmt.green + fmt.underlined + output_type_choices[output_types.dust_mass].capitalize() + fmt.reset + " (" + str(self.ndust_mass) + "):"
            lines.append(line_prefix + title)
            lines.append(line_prefix)

            # Add paths
            for path in self.dust_mass: lines.append(line_prefix + " - " + self.relative_path(path))

        # Dust mix properties
        if self.has_dust_mix_properties:
            lines.append(line_prefix)

            # Add title
            title = fmt.green + fmt.underlined + output_type_choices[output_types.dust_mix_properties].capitalize() + fmt.reset + " (" + str(self.ndust_mix_properties) + "):"
            lines.append(line_prefix + title)
            lines.append(line_prefix)

            # Add paths
            for path in self.dust_mix_properties: lines.append(line_prefix + " - " + self.relative_path(path))

        # Dust optical properties
        if self.has_dust_optical_properties:
            lines.append(line_prefix)

            # Add title
            title = fmt.green + fmt.underlined + output_type_choices[output_types.dust_optical_properties].capitalize() + fmt.reset + " (" + str(self.ndust_optical_properties) + "):"
            lines.append(line_prefix + title)
            lines.append(line_prefix)

            # Add paths
            for path in self.dust_optical_properties: lines.append(line_prefix + " - " + self.relative_path(path))

        # Dust grain sizes
        if self.has_dust_grain_sizes:
            lines.append(line_prefix)

            # Add title
            title = fmt.green + fmt.underlined + output_type_choices[output_types.dust_grain_sizes].capitalize() + fmt.reset + " (" + str(self.ndust_grain_sizes) + "):"
            lines.append(line_prefix + title)
            lines.append(line_prefix)

            # Add paths
            for path in self.dust_grain_sizes: lines.append(line_prefix + " - " + self.relative_path(path))

        # Parameters
        if self.has_parameters:
            lines.append(line_prefix)

            # Add title
            title = fmt.green + fmt.underlined + output_type_choices[output_types.parameters].capitalize() + fmt.reset + " (" + str(self.nparameters) + "):"
            lines.append(line_prefix + title)
            lines.append(line_prefix)

            # Add paths
            for path in self.parameters: lines.append(line_prefix + " - " + self.relative_path(path))

        # Other
        if self.has_other:
            lines.append(line_prefix)

            # Add title
            title = fmt.green + fmt.underlined + "Other output" + fmt.reset + " (" + str(self.nother) + "):"
            lines.append(line_prefix + title)
            lines.append(line_prefix)

            # Add paths
            for path in self.other: lines.append(line_prefix + " - " + self.relative_path(path))

        # Add new line
        lines.append(line_prefix)

        # Return
        return "\n".join(lines)

    # -----------------------------------------------------------------

    def show(self, line_prefix=""):

        """
        This function ...
        :param line_prefix:
        :return:
        """

        print(self.to_string(line_prefix=line_prefix))

# -----------------------------------------------------------------


# create variables and ask user for input.
hrs = input("Enter your hours: ")
pay = input("Enter your pay: ")

# convert string inputs to floats for calculation later.
fhrs = float(hrs)
fpay = float(pay)

# if statement to recognise when overtime has been accured and adjusts formula to account for that in end pay.
if fhrs > 40:
    over = fhrs - 40
    gross = (fpay * fhrs) + (1.5 * fpay * over)
else:
    gross = fpay * fhrs

# Print result.
print(gross)


import concurrent
import fire

from streamrlib import *
from streamrdb import *

from collections import OrderedDict

from playhouse.shortcuts import model_to_dict, dict_to_model

from pycoingecko import CoinGeckoAPI

from polygonapi import PolygonApi
from evmtoken import EVMTOKENS

def host(action, name, address="", user="", port=22, sshkey=""):
    h = {
        "name": name,
        "address": address,
        "user": user,
        "port": port,
        "sshkey": sshkey,
    }

    if str.lower(action) == 'add':
        host("del", name)
        try:
            default_sshkey = Conf.get(Conf.key == 'sshkey').value
            if h['sshkey'] == '':
                h['sshkey'] = default_sshkey
            Host.create(**h)
        except Exception as e:
            print(f"error: {e}")

        print(h)
    elif str.lower(action) == 'del':
        try:
            try:
                h = Host.get(Host.name == h['name'])
            except:
                h = None
            if h:
                h.delete_instance()
        except Exception as e:
            print(f"error: {e}")
    else:
        print(f"Unknown action {action}, use `add` or `del`")

def miner(action, pubkey, name="", host="", container="", command="auto"):
    m = {
        'pubkey': pubkey,
        'name': name,
        'host': host,
        'container': container,
        'command': command
    }
    if str.lower(action) == 'add':
        miner("del", pubkey, host=host)
        try:
            Miner.create(**m)
        except Exception as e:
            print(f"miner add error: {e}")
        print(f"{m}")
    elif str.lower(action) == 'del':
        try:
            try:
                m = Miner.get(Miner.pubkey == pubkey)
            except:
                m = None
            if m:
                m.delete_instance()
        except Exception as e:
            print(f"miner del error: {e}")
    else:
        print(f"Unknown action {action}, use `add` or `del`")

def cfg2db(conffile):
    create_tables()

    conf = load_json_file(conffile)

    try:
        Conf.create(key='sshkey', value=conf['sshkey'])
        Conf.create(key='dingtalkkey', value=conf['dingtalkkey'])
        Conf.create(key='notifyts', value=f"{int(time.time()) - 40 * 60}")
    except:
        pass

    for obj in conf["hosts"]:
        if obj == {}:
            continue
        host('add', **obj)

    for obj in conf["miners"]:
        if obj == {}:
            continue
        miner('add', **obj)

def db2cfg(conffile):
    conf = {}
    save_json_file(conf, conffile)

def fetch_miners_info(max_workers = 10):
    sapi = StreamrApi()
    params = [m.pubkey for m in Miner.select()]
    results = {}
    with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = {executor.submit(sapi.miner, a): (a) for a in params}
        for future in concurrent.futures.as_completed(futures):
            pubkey = futures[future]
            res = future.result()
            results[pubkey] = {
                "data": 0,
                "secondsSinceLastClaim": 0,
                "lastClaimTime": "unknown",
                "receivedRewards": 0,
            }
            if res['DATA']:
                results[pubkey]['data'] = res['DATA']
                results[pubkey]['receivedRewards'] = res['STATS']['receivedRewards']
                results[pubkey]['secondsSinceLastClaim'] = res['STATS']['secondsSinceLastClaim']
                results[pubkey]['lastClaimTime'] = res['STATS']['lastClaimTime']
    print(json.dumps(results, indent=4))

    return results

def keeper():
    force_notify = False
    total_rewards = 0
    received_rewards = 0
    message = ""

    cg = CoinGeckoAPI()
    ret = cg.get_price(ids='streamr,matic-network,helium,bitcoin,dimo,', vs_currencies='usd')
    print(ret)
    DATA_Price = ret['streamr']['usd']
    MATIC_Price = ret['matic-network']['usd']
    HNT_Price = ret['helium']['usd']
    BTC_Price = ret['bitcoin']['usd']
    DIMO_Price = ret['dimo']['usd']

    papi = PolygonApi()
    token = "DATA"
    papi.set_contract(token=EVMTOKENS[token]['address'], abi=EVMTOKENS[token]['abi'])

    results = fetch_miners_info()

    totalBalanceMATIC = 0
    totalBalanceDATA = 0.0
    message_dict = OrderedDict()
    for m in Miner.select():
        if results.get(m.pubkey, '') == '':
            continue

        r = results[m.pubkey]
        sta = "OK"

        matic = papi.balance(m.pubkey)
        balance = papi.token_balance(m.pubkey)
        totalBalanceMATIC += matic
        totalBalanceDATA += balance

        seconds_since_last_fix = 0
        if m.fixtime != 0:
            seconds_since_last_fix = int(time.time() - m.fixtime)

        if r['secondsSinceLastClaim'] > 2 * 60 *60:
            sta = "**NG**"
            if seconds_since_last_fix == 0 or seconds_since_last_fix > 1.5 * 60 * 60:
                force_notify = True
                cmd = m.command
                if m.command == 'auto':
                    cmd = f"sudo docker restart {m.container}"
                try:
                    h = model_to_dict(Host.get(Host.name == m.host_id))
                    SSHClient(**h).exec_cmd_and_close(cmd)
                    m.fixtime = int(time.time())
                    m.save()
                except Exception as e:
                    print(f"Fail to restart streamr node {m.name}")
                    print(e)
            else:
                print(f"Skip restart device ID{m.name}")
        urewards = r['data'] - r['receivedRewards']
        msg = f"{sta}, ID{m.name}, {int(r['data']):d}, {int(r['receivedRewards']):d}, {int(urewards):d}, {r['secondsSinceLastClaim']/60/60:.1f}h, {int(seconds_since_last_fix/60/60)}h\n"
        message_dict[f'ID{m.name}'] = msg
        total_rewards += r['data']
        received_rewards += r['receivedRewards']

    for _, v in sorted(message_dict.items()):
        message += v
    # print(message_dict)

    rate = received_rewards / (totalBalanceDATA/10**18 - total_rewards)

    message = f'''
BTC Price: {BTC_Price:.0f}U
DATA Price: {DATA_Price:.6f}U
HNT Price: {HNT_Price:.2f}U
DIMO Price: {DIMO_Price:.6f}U
MATIC Price: {MATIC_Price:.6f}U
---
Miner: {len(Miner.select())}
Total: {total_rewards:.0f} DATA
Received: {received_rewards:.0f} / {totalBalanceDATA/10**18/1000:.0f}K DATA ({totalBalanceDATA / 10**18 * DATA_Price:.0f}U {rate * 100:.1f}%)
Unclaimed: {total_rewards - received_rewards:.0f} DATA
{message}'''
    print(message)

    try:
        c = Conf.get(Conf.key == 'dingtalkkey')
        n = Conf.get(Conf.key == 'notifyts')
        if c.value != "" :

            if (time.time() - int(n.value)) > 40 * 60:
                force_notify = True
            else:
                print(f"{40 * 60 - (time.time() - int(n.value)):.0f}s to notify")

            if force_notify:
                dingtalk = DingTalk(c.value)
                dingtalk.push("$STREAMR", message)
                n.value = f"{int(time.time())}"
                n.save()
    except:
        pass

def test():
    sapi = StreamrApi()
    pass

if __name__ == '__main__':
    create_tables()

    fire.Fire({
        "cfg2db": cfg2db,
        "db2cfg": db2cfg,

        "host": host,
        "miner": miner,

        "keeper": keeper,

        "test": test,
    })

    close_db()


class Solution(object):
    def dailyTemperatures(self, T):
        """
        :type T: List[int]
        :rtype: List[int]
        """
        stack = []
        res = [0 for _ in T]
        for i, this_t in enumerate(T):
            while stack and T[stack[-1]] < this_t:
                idx = stack.pop()
                res[idx] = i - idx
            stack.append(i)
        return res


# https://requests.readthedocs.io/en/master/user/install/#install
#import is bringing in libaries to help us use more simple coding teq.
import requests
import json
import mysql.connector
from datetime import datetime
#cnx (connection short hand for the above lib. this allows python to attach to mySQL server)
cnx = mysql.connector.connect(user='admin', password='',
                              host='127.0.0.1',
                              database='sys')
#Being connected to mySQL clear out table data
cursor = cnx.cursor()
cursor.execute('TRUNCATE sys.marketdataamarr')

#no key is needed to grab esi data as this is a public API

region = 10000043
#while loop in python, len(length)page == (is equal to) 1000
n = 1
page = []

#template for insert
insert="insert into sys.marketdataamarr values(%(duration)s,%(is_buy_order)s,%(issued)s,%(location_id)s,%(min_volume)s,%(order_id)s,%(price)s,%(range)s,%(system_id)s,%(type_id)s,%(volume_remain)s,%(volume_total)s)"

while(n == 1 or len(page) == 1000):
  url = f'https://esi.evetech.net/latest/markets/{region}/orders/?datasource=tranquility&order_type=all&page={n}'
  headers = {
    "Accept": "application/json"
  }
  response = requests.get(url,headers=headers)
  page = json.loads(response.content)
  print(f'retrieved page {n} with {len(page)} items')
  #for each item in page assign it to it and run and execute "it"
  for it in page:
    cursor.execute(insert,it)
  n=n+1

cnx.commit()
cursor.close()
cnx.close()
dateTimeObj = datetime.now()
print(dateTimeObj)


import pygame, sys
from pygame.locals import *
from inventaire import Slot, checkMoveInShop, drawItemOverlay
from constants import *
from tools import draw_text, to_gold
from menu import *

def shopScreen(screen, fpsClock, player, merchant):
    # liste des slots
    playerslotslist = pygame.sprite.Group()
    merchantslotslist = pygame.sprite.Group()
    # liste des items
    playeritemlist = pygame.sprite.Group()
    merchantitemlist = pygame.sprite.Group()

    # liste de tout les sprites
    all_sprites = pygame.sprite.LayeredUpdates()

    ######
    ######	INVENTAIRE
    ######

    # On change les coordonée des slots et items du joueur
    player.inventaire.createSlots(60,180)
    player.inventaire.move_all_items()
    # on ajoute les slots a la liste des slots du joueur
    for slot in player.inventaire.slots:
        playerslotslist.add(slot)
    # On change les coordonée des slots et items du marchand
    merchant.inventaire.createSlots(SCREEN_WIDTH-50-505+10, 180)
    merchant.inventaire.move_all_items()
    # on ajoute les slots a la liste des slots du marchand
    for slot in merchant.inventaire.slots:
        merchantslotslist.add(slot)

    # de meme avec les items du joueur
    for item in player.inventaire.items:
        if item != False:
            playeritemlist.add(item)
    # de meme avec les items du marchand
    for item in merchant.inventaire.items:
        if item != False:
            merchantitemlist.add(item)

    # en couche la plus basse on met les slots puis les items (pour qu'ils soit recouverts par les items)
    all_sprites.add(playerslotslist)
    all_sprites.add(merchantslotslist)

    all_sprites.add(playeritemlist)
    all_sprites.add(merchantitemlist)

    shop_on = True
    click = False
    playerItemTaken = False
    locked = False
    spritePosBeforeLock = 0
    spriteLocked = -1
    quit_button = Bouton(SCREEN_WIDTH//2, SCREEN_HEIGHT//2, 'Retour', (164, 131, 80), True, 120, 35, font_size=25)
    while shop_on:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    player.inventaire.createSlots(700,180)
                    player.inventaire.move_all_items()
                    shop_on = False
            if event.type == MOUSEBUTTONDOWN:
                if event.button == 1:
                    click = True

        # Detection du clic gauche la souris et de sa position
        activeMouse = pygame.mouse.get_pressed()
        mx, my = pygame.mouse.get_pos()
        if quit_button.hoover(mx, my) and click:
            player.inventaire.createSlots(700,180)
            player.inventaire.move_all_items()
            shop_on = False

        # si on clique
        if activeMouse[0] == True:
            # on regarde pour chaque item du joueur
            for item in player.inventaire.items:
                if item != False:
                    # si on est dessus et qu'on ne bouge pas déja un
                    if item.hoover(mx, my) and not locked:
                        # on le prend
                        locked = True
                        spriteLocked = item
                        spritePosBeforeLock = item.pos()
                        playerItemTaken = True
                    # si on en bouge un et que c'est cet item
                    if locked and spriteLocked == item:
                        # l'item suis notre souris
                        item.rect.x = mx - item.rect.width // 2
                        item.rect.y = my - item.rect.height // 2
            # on regarde pour chaque item du marchand
            for item in merchant.inventaire.items:
                if item != False:
                    # si on est dessus et qu'on ne bouge pas déja un
                    if item.hoover(mx, my) and not locked:
                        # on le prend
                        locked = True
                        spriteLocked = item
                        spritePosBeforeLock = item.pos()
                        playerItemTaken = False
                    # si on en bouge un et que c'est cet item
                    if locked and spriteLocked == item:
                        # l'item suis notre souris
                        item.rect.x = mx - item.rect.width // 2
                        item.rect.y = my - item.rect.height // 2
        # si on ne clique pas
        else:
            # si on avait un item
            if locked:
                # on verifie si c'est possible de poser l'item la ou il est. Si c'est possible on deplace l'item (ou les items si on inverse de place)
                checkMoveInShop(mx, my, spriteLocked, spritePosBeforeLock, playerslotslist, merchantslotslist, player, merchant, playerItemTaken)
                locked = False
                spritePosBeforeLock = 0
                spriteLocked = -1

        # Appelle la méthode update() de tous les Sprites
        all_sprites.update()

        screen.fill(WHITE)

        drawShop(screen, player, merchant)
        quit_button.draw(screen, mx, my)

        # Dessine tous les sprites (les blits sur screen)
        all_sprites.draw(screen)

        # si besoin on dessine l'overlay de l'item
        if activeMouse[0] == False:
            drawItemOverlay(screen, mx, my, playeritemlist, player.inventaire)
            drawItemOverlay(screen, mx, my, merchantitemlist, merchant.inventaire)

        click = False
        pygame.display.update()
        fpsClock.tick(FPS)

def drawShop(screen, player, merchant):
    # on dessine les background
    # fond inventaire player
    pygame.draw.rect(screen, (164, 131, 80), (50, 40, 505, 635))
    # bords inventaire player
    pygame.draw.rect(screen, (100, 64, 31), (50, 40, 505, 635), 5)
    # fond inventaire marchand
    pygame.draw.rect(screen, (164, 131, 80), (SCREEN_WIDTH-50-505, 40, 505, 635))
    # bords inventaire marchand
    pygame.draw.rect(screen, (100, 64, 31), (SCREEN_WIDTH-50-505, 40, 505, 635), 5)

    # bord bouton retour

    # puis les texts
    draw_text(screen, 'Inventaire du marchand', 'fonts/No_Color.ttf', 30, BLACK, (SCREEN_WIDTH-50-505)+505//2, 80, True)
    draw_text(screen, 'Votre inventaire', 'fonts/No_Color.ttf', 30, BLACK, 302, 80, True)
    draw_text(screen,to_gold(player.money), "fonts/SuperLegendBoy-4w8Y.ttf", 20, BLACK, 302, 120, True)
    draw_text(screen,to_gold(merchant.money), "fonts/SuperLegendBoy-4w8Y.ttf", 20, BLACK, (SCREEN_WIDTH-50-505)+505//2,120, True)


from init import init_agents_data, init_agents_pos
from tqdm import tqdm

def simulate(world, mover, data_recorder, data_modifier, params, models=None):
    for rep in tqdm(range(params.num_repeats), leave=False):
        agents_pos = init_agents_pos(params)
        agents_data = init_agents_data(params, models)
        for step in range(params.num_steps):
            new_agents_pos, agents_data = mover.step(agents_pos, agents_data, params)
            new_agents_pos = world.step(agents_pos, new_agents_pos, params)
            data_recorder.record(agents_pos, new_agents_pos, agents_data, world, mover, rep, step)
            data_modifier.modify(agents_data, params)
            agents_pos = new_agents_pos

        data_recorder.new_repeat()
        data_modifier.new_repeat(agents_data, params)

    return data_recorder.get_results()


from gramps.gen.plug import Gramplet
from gramps.gen.const import GRAMPS_LOCALE as glocale
from gramps.gen.display.name import displayer as name_displayer
from gramps.gen.lib.date import Today, Date
import gramps.gen.datehandler
from gramps.gen.config import config
from gramps.gen.plug.menu import EnumeratedListOption
try:
    _trans = glocale.get_addon_translator(__file__)
except ValueError:
    _trans = glocale.translation
_ = _trans.gettext

class BirthdaysGramplet(Gramplet):
    def init(self):
        self.set_text(_("No Family Tree loaded."))
        self.max_age = config.get('behavior.max-age-prob-alive')

    def build_options(self):
        """Build the configuration options"""
        db = self.dbstate.db

        name_ignore = _("Ignore birthdays with tag")
        name_only = _("Only show birthdays with tag")
        self.opt_ignore = EnumeratedListOption(name_ignore, self.ignore_tag)
        self.opt_only = EnumeratedListOption(name_only, self.only_tag)

        self.opt_ignore.add_item('', '')  # No ignore tag
        self.opt_only.add_item('', '')
        if db.is_open():
            for tag_handle in db.get_tag_handles(sort_handles=True):
                tag = db.get_tag_from_handle(tag_handle)
                tag_name = tag.get_name()
                self.opt_ignore.add_item(tag_name, tag_name)
                self.opt_only.add_item(tag_name, tag_name)

        self.add_option(self.opt_ignore)
        self.add_option(self.opt_only)

    def save_options(self):
        """Save gramplet configuration data"""
        self.ignore_tag = self.opt_ignore.get_value()
        self.only_tag = self.opt_only.get_value()

    def save_update_options(self, obj):
        """Save a gramplet's options to file"""
        self.save_options()
        self.gui.data = [self.ignore_tag, self.only_tag]
        self.update()

    def on_load(self):
        """Load stored configuration data"""
        if len(self.gui.data) == 2:
            self.ignore_tag = self.gui.data[0]
            self.only_tag = self.gui.data[1]
        else:
            self.ignore_tag = ''
            self.only_tag = ''

    def db_changed(self):
        """Update gramplet when database was changed"""
        self.connect(self.dbstate.db, 'person-add', self.update)
        self.connect(self.dbstate.db, 'person-delete', self.update)
        self.connect(self.dbstate.db, 'person-update', self.update)

    def main(self):
        """Main function of the Birthdays gramplet"""
        self.set_text(_("Processing..."))
        database = self.dbstate.db
        person_list = database.iter_people()
        self.result = []

        ignore_tag_handle = ""
        only_tag_handle = ""
        tag_name_ignore = self.opt_ignore.get_value()
        tag_name_only = self.opt_only.get_value()
        tag_handles = database.get_tag_handles()
        for handle in tag_handles:
            tag = database.get_tag_from_handle(handle)
            # overwrite ignore_handle and only_handle to user selection
            # if a handle is selected
            if tag_name_ignore == tag.get_name():
                ignore_tag_handle = tag.get_handle()
            if tag_name_only == tag.get_name():
                only_tag_handle = tag.get_handle()

        for person in list(person_list):
            pers_tag_handles = person.get_tag_list()
            if ignore_tag_handle in pers_tag_handles:
                pass  # ignore person
            elif only_tag_handle in pers_tag_handles or only_tag_handle == "":
                # calculate age and days until birthday
                self.__calculate(database, person)

        # Reverse sort on number of days from now:
        self.result.sort(key=lambda item: -item[0])
        self.clear_text()

        # handle text shown in gramplet
        for diff, age, date, person in self.result:
            name = person.get_primary_name()
            displayer = gramps.gen.datehandler.displayer
            self.append_text("{}: ".format(displayer.display(date)))
            self.link(name_displayer.display_name(name), "Person",
                      person.handle)
            self.append_text(" ({})\n".format(age))
        self.append_text("", scroll_to="begin")

    def __calculate(self, database, person):
        """Calculate the age and days until birthday"""
        today = Today()
        birth_ref = person.get_birth_ref()
        death_ref = person.get_death_ref()
        if (birth_ref and not death_ref):
            birth = database.get_event_from_handle(birth_ref.ref)
            birth_date = birth.get_date_object()
            if birth_date.is_regular():
                birthday_this_year = Date(today.get_year(),
                                          birth_date.get_month(),
                                          birth_date.get_day())
                next_age = birthday_this_year - birth_date
                # (0 year, months, days) between now and birthday of this
                # year (positive if passed):
                diff = today - birthday_this_year
                # about number of days the diff is:
                diff_days = diff[1] * 30 + diff[2]
                if next_age[0] < self.max_age:
                    if diff_days <= 0:  # not yet passed
                        self.result.append((diff_days, next_age, birth_date,
                                           person))
                    else:  # passed; add it for next year's birthday
                        self.result.append((diff_days - 365,
                                            next_age[0] + 1,
                                            birth_date, person))