# -*- coding: utf-8 -*-
import os
import shutil
from codecs import open
from operator import attrgetter

from jinja2 import Environment, FileSystemLoader
from jinja2.exceptions import TemplateNotFound
from feedgenerator import Atom1Feed

from pelican.utils import update_dict
from pelican.settings import read_settings
from pelican.contents import Article
from pelican.readers import read_file

## Constants ##########################################################

_TEMPLATES = ('index', 'tag', 'tags', 'article', 'category', 'categories',
              'archives')

_DIRECT_TEMPLATES = ('index', 'tags', 'categories', 'archives')


class Generator(object):
    """Base class generator"""
    
    def __init__(self, settings):
        self.settings = read_settings(settings)
    
    def _init_params(self, path=None, theme=None, output_path=None, fmt=None):
        """Initialize parameters for this object.

        :param path: the path where to find the files to parse
        :param theme: where to search for templates
        :param output_path: where to output the generated files
        :param settings: the settings file to use
        :param fmt: the format of the files to read. It's a list.
        """
        # get the settings
        self.path = path or self.settings['PATH']
        self.theme = theme or self.settings['THEME']
        output_path = output_path or self.settings['OUTPUT_PATH']
        self.output_path = os.path.realpath(output_path)
        self.format = fmt or self.settings['FORMAT']

        # get the list of files to parse
        if not path:
            raise Exception('you need to specify a path to search the docs on !')

    def generate_feed(self, elements, context, output_path=None, filename=None):
        """Generate a feed with the list of articles provided

        Return the feed. If no output_path or filename is specified, just return
        the feed object.

        :param articles: the articles to put on the feed.
        :param context: the context to get the feed metadatas.
        :param output_path: where to output the file.
        :param filename: the filename to output.
        """
        feed = Atom1Feed(
            title=context['SITENAME'],
            link=context['SITEURL'],
            feed_url='%s/%s' % (context['SITEURL'], filename),
            description=context.get('SITESUBTITLE', ''))
        for element in elements:
            feed.add_item(
                title=element.title,
                link='%s/%s' % (context['SITEURL'], element.url),
                description=element.content,
                author_name=getattr(element, 'author', 'John Doe'),
                pubdate=element.date)

        if output_path and filename:
            complete_path = os.path.join(output_path, filename)
            try:
                os.makedirs(os.path.dirname(complete_path))
            except Exception:
                pass
            fp = open(complete_path, 'w')
            feed.write(fp, 'utf-8')
            print u' ✔ writing %s' % complete_path
            
            fp.close()
        return feed

    def generate_file(self, name, template, context, **kwargs):
        """Write the file with the given informations

        :param name: name of the file to output
        :param template: template to use to generate the content
        :param context: dict to pass to the templates.
        :param **kwargs: additional variables to pass to the templates
        """
        context.update(kwargs)
        output = template.render(context)
        filename = os.sep.join((self.output_path, name))
        try:
            os.makedirs(os.path.dirname(filename))
        except Exception:
            pass
        with open(filename, 'w', encoding='utf-8') as f:
            f.write(output)
        print u' ✔ writing %s' % filename

    def get_templates(self, path=None):
        """Return the templates to use.

        :param path: the path to load the templates from
        """
        path = os.path.expanduser(os.path.join(path, 'templates'))
        env = Environment(loader=FileSystemLoader(path))
        templates = {}
        for template in _TEMPLATES:
            try:
                templates[template] = env.get_template('%s.html' % template)
            except TemplateNotFound:
                raise Exception('Unable to load %s.html from %s' % (
                    template, path))
        return templates

    def clean_output_dir(self):
        """Remove all the files from the output directory"""

        # remove all the existing content from the output folder
        try:
            shutil.rmtree(os.path.join(self.output_path))
        except:
            pass

    
class ArticlesGenerator(Generator):

    def __init__(self, settings=None):
        super(ArticlesGenerator, self).__init__(settings)
        self.articles = [] 
        self.dates = {}
        self.years = {}
        self.tags = {}
        self.categories = {} 
        
    def get_files(self, path):
        """Return the files to use to use in this generator"""
        files = []
        for root, dirs, temp_files in os.walk(path, followlinks=True):
            files.extend([os.sep.join((root, f)) for f in temp_files
                if f.endswith(self.format)])
        return files

    def process_files(self, files):
        """Process all the files and build the lists and dicts of
        articles/categories/etc.
        """
        for f in files:
            content, metadatas = read_file(f)
            if 'category' not in metadatas.keys():
                category = os.path.dirname(f).replace(
                    os.path.expanduser(self.path)+'/', '')

                if category != '':
                    metadatas['category'] = unicode(category)

            article = Article(content, metadatas, settings=self.settings)
            try:
                article.check_properties()
            except NameError as e:
                print u" ☓ Skipping %s: impossible to find informations about '%s'" % (f, e)
                continue

            update_dict(self.dates, article.date.strftime('%Y-%m-%d'), article)
            update_dict(self.years, article.date.year, article)
            update_dict(self.categories, article.category, article)
            if hasattr(article, 'tags'):
                for tag in article.tags:
                    update_dict(self.tags, tag, article)
            self.articles.append(article)

    def _get_context(self):
        """Return the context to be used in templates"""

        context = self.settings.copy()
        # put all we need in the context, to generate the output
        for item in ('articles', 'dates', 'years', 'tags', 'categories'):
            value = getattr(self, item)
            if hasattr(value, 'items'):
                value = value.items()
            context[item] = value
        return context

    def generate_feeds(self, context):
        """Generate the feeds from the current context, and output files.""" 

        if 'SITEURL' not in context:
            context['SITEURL'] = self.output_path

        self.generate_feed(self.articles, context, self.output_path, 
            context['FEED'])

        for cat, arts in self.categories.items():
            arts.sort(key=attrgetter('date'), reverse=True)
            self.generate_feed(arts, context, self.output_path,
                context['CATEGORY_FEED'] % cat)

    def generate_pages(self, context):
        """Generate the pages on the disk"""

        templates = self.get_templates(self.theme)
        generate = self.generate_file
        for template in _DIRECT_TEMPLATES:
            generate('%s.html' % template, templates[template], context, blog=True)
        for tag in self.tags:
            generate('tag/%s.html' % tag, templates['tag'], context, tag=tag)
        for cat in self.categories:
            generate('category/%s.html' % cat, templates['category'], context,
                          category=cat, articles=self.categories[cat])
        for article in self.articles:
            generate('%s' % article.url,
                          templates['article'], context, article=article,
                          category=article.category)

    def generate_static_content(self):
        """copy static paths to output"""
        
        for path in self.settings['STATIC_PATHS']:
            try:
                fromp = os.path.expanduser(os.path.join(self.theme, path))
                to = os.path.expanduser(os.path.join(self.output_path, path))
                shutil.copytree(fromp, to)
                print u' ✔ copying %s' % fromp

            except OSError:
                pass

    def generate(self, path=None, theme=None, output_path=None, fmt=None):
        """Search the given path for files, and generate a static blog in output,
        using the given theme.

        :param path: the path where to find the files to parse
        :param theme: where to search for templates
        :param output_path: where to output the generated files
        :param settings: the settings file to use
        :param fmt: the format of the files to read. It's a list.
        """

        self._init_params(path, theme, output_path, fmt)

        # build the list of articles / categories / etc.
        self.process_files(self.get_files(path))

        # sort the articles by date
        self.articles.sort(key=attrgetter('date'), reverse=True)
        # and generate the output :)
        context = self._get_context()
        self.generate_feeds(context)
        self.generate_pages(context)
        self.generate_static_content()
