# -*- coding: utf-8 -*-
"""
CSS plugins
"""


from hyde.plugin import CLTransformer, Plugin
from hyde.exceptions import HydeException

import os
import re
import subprocess
import sys

from fswrap import File

#
# Less CSS
#


class LessCSSPlugin(CLTransformer):

    """
    The plugin class for less css
    """

    def __init__(self, site):
        super(LessCSSPlugin, self).__init__(site)
        self.import_finder = \
            re.compile('^\\s*@import\s+(?:\'|\")([^\'\"]*)(?:\'|\")\s*\;\s*$',
                       re.MULTILINE)

    @property
    def executable_name(self):
        return "lessc"

    def _should_parse_resource(self, resource):
        """
        Check user defined
        """
        return resource.source_file.kind == 'less' and \
            getattr(resource, 'meta', {}).get('parse', True)

    def _should_replace_imports(self, resource):
        return getattr(resource, 'meta', {}).get('uses_template', True)

    def begin_site(self):
        """
        Find all the less css files and set their relative deploy path.
        """
        for resource in self.site.content.walk_resources():
            if self._should_parse_resource(resource):
                new_name = resource.source_file.name_without_extension + ".css"
                target_folder = File(resource.relative_deploy_path).parent
                resource.relative_deploy_path = target_folder.child(new_name)

    def begin_text_resource(self, resource, text):
        """
        Replace @import statements with {% include %} statements.
        """

        if not self._should_parse_resource(resource) or \
           not self._should_replace_imports(resource):
            return text

        def import_to_include(match):
            if not match.lastindex:
                return ''
            path = match.groups(1)[0]
            afile = File(resource.source_file.parent.child(path))
            if len(afile.kind.strip()) == 0:
                afile = File(afile.path + '.less')
            ref = self.site.content.resource_from_path(afile.path)
            if not ref:
                raise HydeException(
                    "Cannot import from path [%s]" % afile.path)
            ref.is_processable = False
            return self.template.get_include_statement(ref.relative_path)
        text = self.import_finder.sub(import_to_include, text)
        return text

    @property
    def plugin_name(self):
        """
        The name of the plugin.
        """
        return "less"

    def text_resource_complete(self, resource, text):
        """
        Save the file to a temporary place and run less compiler.
        Read the generated file and return the text as output.
        Set the target path to have a css extension.
        """
        if not self._should_parse_resource(resource):
            return

        supported = [
            "verbose",
            ("silent", "s"),
            ("compress", "x"),
            "O0",
            "O1",
            "O2",
            "include-path="
        ]

        less = self.app
        source = File.make_temp(text)
        target = File.make_temp('')
        args = [unicode(less)]
        args.extend(self.process_args(supported))
        args.extend([unicode(source), unicode(target)])
        try:
            self.call_app(args)
        except subprocess.CalledProcessError:
            HydeException.reraise(
                "Cannot process %s. Error occurred when "
                "processing [%s]" % (self.app.name, resource.source_file),
                sys.exc_info())

        return target.read_all()


#
# Stylus CSS
#

class StylusPlugin(CLTransformer):

    """
    The plugin class for stylus css
    """

    def __init__(self, site):
        super(StylusPlugin, self).__init__(site)
        self.import_finder = \
            re.compile('^\\s*@import\s+(?:\'|\")([^\'\"]*)(?:\'|\")\s*\;?\s*$',
                       re.MULTILINE)

    def begin_site(self):
        """
        Find all the styl files and set their relative deploy path.
        """
        for resource in self.site.content.walk_resources():
            if resource.source_file.kind == 'styl':
                new_name = resource.source_file.name_without_extension + ".css"
                target_folder = File(resource.relative_deploy_path).parent
                resource.relative_deploy_path = target_folder.child(new_name)

    def begin_text_resource(self, resource, text):
        """
        Replace @import statements with {% include %} statements.
        """

        if not resource.source_file.kind == 'styl':
            return

        def import_to_include(match):
            """
            Converts a css import statement to include statement.
            """
            if not match.lastindex:
                return ''
            path = match.groups(1)[0]
            first_child = resource.source_file.parent.child(path)
            afile = File(File(first_child).fully_expanded_path)
            if len(afile.kind.strip()) == 0:
                afile = File(afile.path + '.styl')

            ref = self.site.content.resource_from_path(afile.path)

            if not ref:
                try:
                    include = self.settings.args.include
                except AttributeError:
                    include = False
                if not include:
                    raise HydeException(
                        "Cannot import from path [%s]" % afile.path)
            else:
                ref.is_processable = False
                return "\n" + \
                    self.template.get_include_statement(ref.relative_path) + \
                    "\n"
            return '@import "' + path + '"\n'

        text = self.import_finder.sub(import_to_include, text)
        return text

    @property
    def defaults(self):
        """
        Returns `compress` if not in development mode.
        """
        try:
            mode = self.site.config.mode
        except AttributeError:
            mode = "production"

        defaults = {"compress": ""}
        if mode.startswith('dev'):
            defaults = {}
        return defaults

    @property
    def plugin_name(self):
        """
        The name of the plugin.
        """
        return "stylus"

    def text_resource_complete(self, resource, text):
        """
        Save the file to a temporary place and run stylus compiler.
        Read the generated file and return the text as output.
        Set the target path to have a css extension.
        """
        if not resource.source_file.kind == 'styl':
            return
        stylus = self.app
        source = File.make_temp(text.strip())
        supported = [("compress", "c"), ("include", "I")]

        args = [unicode(stylus)]
        args.extend(self.process_args(supported))
        args.append(unicode(source))
        try:
            self.call_app(args)
        except subprocess.CalledProcessError:
            HydeException.reraise(
                "Cannot process %s. Error occurred when "
                "processing [%s]" % (stylus.name, resource.source_file),
                sys.exc_info())
        target = File(source.path + '.css')
        return target.read_all()


#
# Clever CSS
#

class CleverCSSPlugin(Plugin):

    """
    The plugin class for CleverCSS
    """

    def __init__(self, site):
        super(CleverCSSPlugin, self).__init__(site)
        try:
            import clevercss
        except ImportError, e:
            raise HydeException('Unable to import CleverCSS: ' + e.message)
        else:
            self.clevercss = clevercss

    def _should_parse_resource(self, resource):
        """
        Check user defined
        """
        return resource.source_file.kind == 'ccss' and \
            getattr(resource, 'meta', {}).get('parse', True)

    def _should_replace_imports(self, resource):
        return getattr(resource, 'meta', {}).get('uses_template', True)

    def begin_site(self):
        """
        Find all the clevercss files and set their relative deploy path.
        """
        for resource in self.site.content.walk_resources():
            if self._should_parse_resource(resource):
                new_name = resource.source_file.name_without_extension + ".css"
                target_folder = File(resource.relative_deploy_path).parent
                resource.relative_deploy_path = target_folder.child(new_name)

    def begin_text_resource(self, resource, text):
        """
        Replace @import statements with {% include %} statements.
        """

        if not self._should_parse_resource(resource) or \
           not self._should_replace_imports(resource):
            return text

        import_finder = re.compile(
            '^\\s*@import\s+(?:\'|\")([^\'\"]*)(?:\'|\")\s*\;\s*$',
            re.MULTILINE)

        def import_to_include(match):
            if not match.lastindex:
                return ''
            path = match.groups(1)[0]
            afile = File(resource.source_file.parent.child(path))
            if len(afile.kind.strip()) == 0:
                afile = File(afile.path + '.ccss')
            ref = self.site.content.resource_from_path(afile.path)
            if not ref:
                raise HydeException(
                    "Cannot import from path [%s]" % afile.path)
            ref.is_processable = False
            return self.template.get_include_statement(ref.relative_path)
        text = import_finder.sub(import_to_include, text)
        return text

    def text_resource_complete(self, resource, text):
        """
        Run clevercss compiler on text.
        """
        if not self._should_parse_resource(resource):
            return

        return self.clevercss.convert(text, self.settings)

#
# Sassy CSS
#


class SassyCSSPlugin(Plugin):

    """
    The plugin class for SassyCSS
    """

    def __init__(self, site):
        super(SassyCSSPlugin, self).__init__(site)
        try:
            import scss
        except ImportError, e:
            raise HydeException('Unable to import pyScss: ' + e.message)
        else:
            self.scss = scss

    def _should_parse_resource(self, resource):
        """
        Check user defined
        """
        return resource.source_file.kind == 'scss' and \
            getattr(resource, 'meta', {}).get('parse', True)

    @property
    def options(self):
        """
        Returns options depending on development mode
        """
        try:
            mode = self.site.config.mode
        except AttributeError:
            mode = "production"

        debug = mode.startswith('dev')
        opts = {'compress': not debug, 'debug_info': debug}
        site_opts = self.settings.get('options', {})
        opts.update(site_opts)
        return opts

    @property
    def vars(self):
        """
        Returns scss variables.
        """
        return self.settings.get('vars', {})

    @property
    def includes(self):
        """
        Returns scss load paths.
        """
        return self.settings.get('includes', [])

    def begin_site(self):
        """
        Find all the sassycss files and set their relative deploy path.
        """
        self.scss.STATIC_URL = self.site.content_url('/')
        self.scss.STATIC_ROOT = self.site.config.content_root_path.path
        self.scss.ASSETS_URL = self.site.media_url('/')
        self.scss.ASSETS_ROOT = self.site.config.deploy_root_path.child(
            self.site.config.media_root)

        for resource in self.site.content.walk_resources():
            if self._should_parse_resource(resource):
                new_name = resource.source_file.name_without_extension + ".css"
                target_folder = File(resource.relative_deploy_path).parent
                resource.relative_deploy_path = target_folder.child(new_name)

    def text_resource_complete(self, resource, text):
        """
        Run sassycss compiler on text.
        """
        if not self._should_parse_resource(resource):
            return

        includes = [resource.node.path] + self.includes
        includes = [path.rstrip(os.sep) + os.sep for path in includes]
        options = self.options
        if 'load_paths' not in options:
            options['load_paths'] = []
        options['load_paths'].extend(includes)
        scss = self.scss.Scss(scss_opts=options, scss_vars=self.vars)
        return scss.compile(text)


#
# Sass CSS
#


class SassPlugin(Plugin):

    """
    The plugin class for LibSass
    """

    def __init__(self, site):
        super(SassPlugin, self).__init__(site)
        try:
            import sass
        except ImportError, e:
            raise HydeException('Unable to import libsass: ' + e.message)
        else:
            self.sass = sass
        self.resources = []

    def _should_parse_resource(self, resource):
        """
        Check user defined
        """
        files = self.site.config.get("sass", {}).get("files", [])
        return resource.source_file.kind == 'scss' and \
            resource.relative_path in files

    @property
    def options(self):
        """
        Returns options depending on development mode
        """
        try:
            mode = self.site.config.mode
        except AttributeError:
            mode = "production"

        if 'sass' in self.site.config and \
                'output_style' in self.site.config.sass:
            output_style = self.site.config.sass.output_style
        else:
            debug = mode.startswith('dev')
            output_style = 'compressed' if not debug else 'nested'

        opts = {'output_style': output_style}
        site_opts = self.settings.get('options', {})
        opts.update(site_opts)
        return opts

    @property
    def includes(self):
        """
        Returns scss load paths.
        """
        return self.settings.get('includes', [])

    def begin_site(self):
        """
        Find all the sassycss files and set their relative deploy path.
        """
        for resource in self.site.content.walk_resources():
            if self._should_parse_resource(resource):
                new_name = resource.source_file.name_without_extension + ".css"
                target_folder = File(resource.relative_deploy_path).parent
                resource.relative_deploy_path = target_folder.child(new_name)
                self.resources.append(resource.relative_path)

    def text_resource_complete(self, resource, text):
        """
        Run sassycss compiler on text.
        """
        if resource.relative_path not in self.resources:
            return

        includes = [resource.node.path] + self.includes
        includes = [path.rstrip(os.sep) + os.sep for path in includes]
        options = self.options
        if 'include_paths' not in options:
            options['include_paths'] = []
        options['include_paths'].extend(includes)
        self.logger.error(resource)
        try:
            return self.sass.compile(string=text, **options)
        except Exception, exc:
            self.logger.error(exc)
            raise
