import logging
import subprocess
import sys
import os
import re
from operator import itemgetter
import ftrack_api


class DJVViewAction(object):
    """Launch DJVView action."""
    identifier = "djvview-launch-action"
    # label = "DJV View"
    # icon = "http://a.fsdn.com/allura/p/djv/icon"

    def __init__(self, session):
        '''Expects a ftrack_api.Session instance'''

        self.logger = logging.getLogger(
            '{0}.{1}'.format(__name__, self.__class__.__name__)
        )

        if self.identifier is None:
            raise ValueError(
                'Action missing identifier.'
            )

        self.session = session

    def is_valid_selection(self, event):
        selection = event["data"].get("selection", [])

        if not selection:
            return

        entityType = selection[0]["entityType"]

        if entityType not in ["assetversion", "task"]:
            return False

        return True

    def discover(self, event):
        """Return available actions based on *event*. """

        if not self.is_valid_selection(event):
            return

        items = []
        applications = self.get_applications()
        applications = sorted(
            applications, key=lambda application: application["label"]
        )

        for application in applications:
            self.djv_path = application.get("path", None)
            applicationIdentifier = application["identifier"]
            label = application["label"]
            items.append({
                "actionIdentifier": self.identifier,
                "label": label,
                "variant": application.get("variant", None),
                "description": application.get("description", None),
                "icon": application.get("icon", "default"),
                "applicationIdentifier": applicationIdentifier
            })

        return {
            "items": items
        }

    def register(self):
        '''Registers the action, subscribing the discover and launch topics.'''
        self.session.event_hub.subscribe(
            'topic=ftrack.action.discover and source.user.username={0}'.format(
                self.session.api_user
                ), self.discover
        )

        self.session.event_hub.subscribe(
            'topic=ftrack.action.launch and data.actionIdentifier={0} and source.user.username={1}'.format(
                self.identifier,
                self.session.api_user
            ),
            self.launch
        )
        print("----- action - <" + self.__class__.__name__ + "> - Has been registered -----")

    def get_applications(self):
        applications = []

        label = "DJVView {version}"
        versionExpression = re.compile(r"(?P<version>\d+.\d+.\d+)")
        applicationIdentifier = "djvview"
        description = "DJV View Launcher"
        icon = "http://a.fsdn.com/allura/p/djv/icon"
        expression = []
        if sys.platform == "win32":
            expression = ["C:\\", "Program Files", "djv-\d.+",
                          "bin", "djv_view.exe"]

        elif sys.platform == "darwin":
            expression = ["Application", "DJV.app", "Contents", "MacOS", "DJV"]
        # Linuxs
        else:
            expression = ["usr", "local", "djv", "djv_view"]

        pieces = expression[:]
        start = pieces.pop(0)

        if sys.platform == 'win32':
            # On Windows C: means current directory so convert roots that look
            # like drive letters to the C:\ format.
            if start and start[-1] == ':':
                start += '\\'

        if not os.path.exists(start):
            raise ValueError(
                'First part "{0}" of expression "{1}" must match exactly to an '
                'existing entry on the filesystem.'
                .format(start, expression)
            )


        expressions = list(map(re.compile, pieces))
        expressionsCount = len(expression)-1

        for location, folders, files in os.walk(start, topdown=True, followlinks=True):
            level = location.rstrip(os.path.sep).count(os.path.sep)
            expression = expressions[level]

            if level < (expressionsCount - 1):
                # If not yet at final piece then just prune directories.
                folders[:] = [folder for folder in folders
                              if expression.match(folder)]
            else:
                # Match executable. Note that on OSX executable might equate to
                # a folder (.app).
                for entry in folders + files:
                    match = expression.match(entry)
                    if match:
                        # Extract version from full matching path.
                        path = os.path.join(start, location, entry)
                        versionMatch = versionExpression.search(path)
                        if versionMatch:
                            version = versionMatch.group('version')

                            applications.append({
                                'identifier': applicationIdentifier.format(
                                    version=version
                                ),
                                'path': path,
                                'version': version,
                                'label': label.format(version=version),
                                'icon': icon,
                                # 'variant': variant.format(version=version),
                                'description': description
                            })
                        else:
                            self.logger.debug(
                                'Discovered application executable, but it '
                                'does not appear to o contain required version '
                                'information: {0}'.format(path)
                            )

                # Don't descend any further as out of patterns to match.
                del folders[:]

        return applications

    def translate_event(self, session, event):
        '''Return *event* translated structure to be used with the API.'''

        selection = event['data'].get('selection', [])

        entities = list()
        for entity in selection:
            entities.append(
                (session.get(self.get_entity_type(entity), entity.get('entityId')))
            )

        return entities

    def get_entity_type(self, entity):
        entity_type = entity.get('entityType').replace('_', '').lower()

        for schema in self.session.schemas:
            alias_for = schema.get('alias_for')

            if (
                alias_for and isinstance(alias_for, str) and
                alias_for.lower() == entity_type
            ):
                return schema['id']

        for schema in self.session.schemas:
            if schema['id'].lower() == entity_type:
                return schema['id']

        raise ValueError(
            'Unable to translate entity type: {0}.'.format(entity_type)
        )

    def launch(self, event):
        """Callback method for DJVView action."""
        session = self.session
        entities = self.translate_event(session, event)

        # Launching application
        if "values" in event["data"]:
            filename = event['data']['values']['path']
            file_type = filename.split(".")[-1]

            # TODO Is this proper way?
            try:
                fps = int(entities[0]['custom_attributes']['fps'])
            except:
                fps = 24

            # TODO issequence is probably already built-in validation in ftrack
            isseq = re.findall('%[0-9]*d', filename)
            if len(isseq) > 0:
                if len(isseq) == 1:
                    frames = []
                    padding = re.findall('%[0-9]*d', filename).pop()
                    index = filename.find(padding)

                    full_file = filename[0:index-1]
                    file = full_file.split(os.sep)[-1]
                    folder = os.path.dirname(full_file)

                    for fname in os.listdir(path=folder):
                        if fname.endswith(file_type) and file in fname:
                            frames.append(int(fname.split(".")[-2]))

                    if len(frames) > 0:
                        start = min(frames)
                        end = max(frames)

                        range = (padding % start) + '-' + (padding % end)
                        filename = re.sub('%[0-9]*d', range, filename)
                else:
                    print("")
                    return {
                        'success': False,
                        'message': 'DJV View - Filename has more than one seqence identifier.'
                    }

            cmd = []
            # DJV path
            cmd.append(os.path.normpath(self.djv_path))
            # DJV Options Start ##############################################
            # cmd.append('-file_layer (value)') #layer name
            cmd.append('-file_proxy 1/2')  # Proxy scale: 1/2, 1/4, 1/8
            cmd.append('-file_cache True')  # Cache: True, False.
            # cmd.append('-window_fullscreen') #Start in full screen
            # cmd.append("-window_toolbar False") # Toolbar controls: False, True.
            # cmd.append("-window_playbar False") # Window controls: False, True.
            # cmd.append("-view_grid None") # Grid overlay: None, 1x1, 10x10, 100x100.
            # cmd.append("-view_hud True") # Heads up display: True, False.
            cmd.append("-playback Forward")  # Playback: Stop, Forward, Reverse.
            # cmd.append("-playback_frame (value)") # Frame.
            cmd.append("-playback_speed " + str(fps))
            # cmd.append("-playback_timer (value)") # Timer: Sleep, Timeout. Value: Sleep.
            # cmd.append("-playback_timer_resolution (value)") # Timer resolution (seconds): 0.001.
            cmd.append("-time_units Frames")  # Time units: Timecode, Frames.
            # DJV Options End ################################################

            # PATH TO COMPONENT
            cmd.append(os.path.normpath(filename))

            # Run DJV with these commands
            subprocess.Popen(' '.join(cmd))

            return {
                'success': True,
                'message': 'DJV View started.'
            }

        if 'items' not in event["data"]:
            event["data"]['items'] = []

        try:
            for entity in entities:
                versions = []
                allowed_types = ["img", "mov", "exr"]

                if entity.entity_type.lower() == "assetversion":
                    if entity['components'][0]['file_type'] in allowed_types:
                        versions.append(entity)

                if entity.entity_type.lower() == "task":
                    # AssetVersions are obtainable only from shot!
                    shotentity = entity['parent']

                    for asset in shotentity['assets']:
                        for version in asset['versions']:
                            # Get only AssetVersion of selected task
                            if version['task']['id'] != entity['id']:
                                continue
                            # Get only components with allowed type
                            if version['components'][0]['file_type'] in allowed_types:
                                versions.append(version)

                # Raise error if no components were found
                if len(versions) < 1:
                    raise ValueError('There are no Asset Versions to open.')

                for version in versions:
                    for component in version['components']:
                        label = "v{0} - {1} - {2}"

                        label = label.format(
                            str(version['version']).zfill(3),
                            version['asset']['type']['name'],
                            component['name']
                        )

                        try:
                            # TODO This is proper way to get filepath!!!
                            # THIS WON'T WORK RIGHT NOW
                            location = component['component_locations'][0]['location']
                            file_path = location.get_filesystem_path(component)
                            # if component.isSequence():
                            #     if component.getMembers():
                            #         frame = int(component.getMembers()[0].getName())
                            #         file_path = file_path % frame
                        except:
                            # This works but is NOT proper way
                            file_path = component['component_locations'][0]['resource_identifier']

                        event["data"]["items"].append(
                            {"label": label, "value": file_path}
                        )

        except Exception as e:
            return {
                'success': False,
                'message': str(e)
            }

        return {
            "items": [
                {
                    "label": "Items to view",
                    "type": "enumerator",
                    "name": "path",
                    "data": sorted(
                        event["data"]['items'],
                        key=itemgetter("label"),
                        reverse=True
                    )
                }
            ]
        }


def register(session, **kw):
    """Register hooks."""
    if not isinstance(session, ftrack_api.session.Session):
        return

    action = DJVViewAction(session)
    action.register()


def main(arguments=None):
    '''Set up logging and register action.'''
    if arguments is None:
        arguments = []

    parser = argparse.ArgumentParser()
    # Allow setting of logging level from arguments.
    loggingLevels = {}
    for level in (
        logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING,
        logging.ERROR, logging.CRITICAL
    ):
        loggingLevels[logging.getLevelName(level).lower()] = level

    parser.add_argument(
        '-v', '--verbosity',
        help='Set the logging output verbosity.',
        choices=loggingLevels.keys(),
        default='info'
    )
    namespace = parser.parse_args(arguments)

    # Set up basic logging
    logging.basicConfig(level=loggingLevels[namespace.verbosity])

    session = ftrack_api.Session()
    register(session)

    # Wait for events
    logging.info(
        'Registered actions and listening for events. Use Ctrl-C to abort.'
    )
    session.event_hub.wait()


if __name__ == '__main__':
    raise SystemExit(main(sys.argv[1:]))
