from wizart.desktop.app_launch import Application, WebLinkApplication
from wizart.desktop import app_registry
from collections import OrderedDict
import sys
import yaml
import re
import os
from enum import Enum
import logging

from wizart.desktop.utils import set_show_inactive_projects
log = logging.getLogger("wizart_desktop")

_mapping_tag = yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG

def dict_constructor(loader, node):
    return OrderedDict(loader.construct_pairs(node))

yaml.add_constructor(_mapping_tag, dict_constructor)
yaml.add_constructor('!regexp', lambda l, n: re.compile(l.construct_scalar(n)))

class Project(object):
    icon = ":proj_dev.png"
    label = "Dev"
    name = "dev"
    root_path = "$W/projects/dev"
    is_series = False
    apps = []
    env = {} #environment variables will be set BEFORE REZ
    prodtrack_config = {}
    active = True


class ProjectParseStatus(Enum):
    Success = 0
    Failed = 1
    NotScanned = 2
class ProjectsAggregator:
    projects = []
    status = ProjectParseStatus.NotScanned

    @classmethod
    def clear_cache(cls):
        cls.projects = []
        cls.status = ProjectParseStatus.NotScanned
    
    @classmethod
    def get_projects(cls):
        if cls.status == ProjectParseStatus.NotScanned:
            cls.create_projects()

        return cls.projects

    @classmethod
    def get_project_by_name(cls, name):
        if cls.status == ProjectParseStatus.NotScanned:
            cls.create_projects()

        for proj in cls.projects:
            if proj.name == name:
                return proj

    @classmethod
    def get_project_by_id(cls, project_id):
        if cls.status == ProjectParseStatus.NotScanned:
            cls.create_projects()
        if project_id:
            for proj in cls.projects:
                if proj.prodtrack_config.get('project_id') == project_id:
                    return proj
        log.error("No project found with id '%s'" % project_id)

    @classmethod
    def create_projects(cls):
        from wizart.desktop import utils

        if hasattr(sys, "freelance_build"):
            sync_path = utils.get_sync_path()
            if sync_path is None:
                log.warning("Wizart Root Folder is not set!")
                return []
        else:
            sync_path = os.environ.get("W","W:")
        if not os.path.exists(sync_path):
            log.warning("Wizart Root Folder pointing to folder '%s' that not exists!" % sync_path)
            return []
        projects_root = os.path.join(sync_path + "/", "projects")
        if not os.path.exists(projects_root):
            log.warning("Not found any projects in Wizart Root Folder: %s" % sync_path)
            return []
        projects_list = os.listdir(projects_root)
        configs_dirs = [os.path.join(projects_root, proj, '.config') for proj in projects_list ]
        show_inactive = utils.get_show_inactive_projects()

        dev_mode = utils.get_show_dev_projects()
        """
        Show dev sandbox projects
        they located in structure
        $W/projects/dev/users/<user_name>/<project_name>
        where "dev" is special case project name that can't be our production name
        users/<user_name> - added in case we need in future a lot user isolated sandboxes, and need filter
        by current user (for now load all dev projects from all users)
        users prefix, in case we need in future non-user sandboxes
        <project_name> in users space, in case user want several sandbox projects
        """
        if dev_mode:
            dev_project_root = os.path.join(projects_root, "dev")
            if os.path.exists(dev_project_root):
                dev_users_root = os.path.join(dev_project_root, "users")
                if os.path.exists(dev_users_root):
                    dev_users_list = os.listdir(dev_users_root)
                    for user in dev_users_list:
                        dev_user_root = os.path.join(dev_users_root, user)
                        dev_user_projects_list = os.listdir(dev_user_root)
                        for user_project in dev_user_projects_list:
                            configs_dirs.append( os.path.join(dev_user_root, user_project, ".config"))
        ### add shorts projects
        shorts_projects_root = os.path.join(projects_root, "shorts")
        if os.path.exists(shorts_projects_root):
                for short_project in os.listdir(shorts_projects_root):
                    configs_dirs.append( os.path.join(shorts_projects_root, short_project, ".config"))
        for config_dir in configs_dirs:
            project = cls.__build_project(config_dir)
            if project and (project.active or show_inactive):
                cls.projects.append(project)
            else:
                continue
            if cls.status == ProjectParseStatus.NotScanned:
                cls.status = ProjectParseStatus.Success

    @classmethod
    def __build_project(cls, config_dir):
        project_config_path = os.path.join(config_dir, "project.yml")
        if not os.path.exists(project_config_path):
            return
        project_config = cls.__get_config(project_config_path)
        if not project_config:
            cls.status = ProjectParseStatus.Failed
            return
        project = Project()
        icon_path = project_config.get('icon')
        if icon_path:
            if not icon_path.startswith(":"):
                icon_path = os.path.join(config_dir, icon_path)
            project.icon = icon_path
        project.label = project_config.get('label', u'').encode('utf8')
        project.name = project_config.get('name', u'').encode('utf8')
        project.is_series = bool(project_config.get('is_series'))
        project.root_path = project_config.get(
            'root_path', u'').encode('utf8')
        project.active = project_config.get('active', True)
        project.apps = create_project_apps(project_config.get('apps', []))
        project.env = project_config.get('env', {})
        if not hasattr(sys, "freelance_build"):
            prodtrack_conf_path = os.path.join(config_dir, "prodtrack.yml")
            if os.path.exists(prodtrack_conf_path):
                project.prodtrack_config = cls.__get_config(prodtrack_conf_path)
        return project

    @classmethod
    def __get_config(cls, path):
        data = None
        with open(path, "r") as config_file:
            doc = yaml.Loader(config_file)
            try:
                if not doc.check_data():
                    raise RuntimeError("Cannot load yaml file %s" % path)
                else:
                    data = doc.get_data()
            finally:
                doc.dispose()
        return data

    @classmethod
    def get_apps_for_project(cls, name):
        if cls.status == ProjectParseStatus.NotScanned:
            cls.create_projects()
        for proj in cls.projects:
            if proj.name == name:
                return proj.apps

        return []

def create_project_apps(apps_settings):
    apps = []
    if apps_settings:
        for name, args in apps_settings.items():
            app = None
            for app_class in Application.get_subclasses():
                if app_class.name == name:
                    app = app_class()
                    break
            if app:
                app.rez_packages = args.get('rez_packages', app.rez_packages)
                apps.append(app)
    return apps
