import os
import fnmatch
from collections import OrderedDict

from jinja2 import Environment, FileSystemLoader, TemplateNotFound
from sphinx.util.console import darkgreen, bold
from sphinx.util.osutil import ensuredir


from ..settings import TEMPLATE_DIR


class PythonMapperBase(object):

    '''
    Base object for JSON -> Python object mapping.

    Subclasses of this object will handle their language specific JSON input,
    and map that onto this standard Python object.
    Subclasses may also include language-specific attributes on this object.

    Arguments:

    :param obj: JSON object representing this object
    :param jinja_env: A template environment for rendering this object

    Required attributes:

    :var str id: A globally unique indentifier for this object. Generally a fully qualified name, including namespace.
    :var str name: A short "display friendly" name for this object.

    Optional attributes:

    :var str docstring: The documentation for this object
    :var list imports: Imports in this object
    :var list children: Children of this object
    :var list parameters: Parameters to this object
    :var list methods: Methods on this object

    '''

    language = 'base'
    type = 'base'
    # Create a page in the output for this object.
    top_level_object = False

    def __init__(self, obj, options=None, jinja_env=None):
        self.obj = obj
        self.options = options
        if jinja_env:
            self.jinja_env = jinja_env

    def render(self, **kwargs):
        ctx = {}
        try:
            template = self.jinja_env.get_template(
                '{language}/{type}.rst'.format(language=self.language, type=self.type)
            )
        except TemplateNotFound:
            # Use a try/except here so we fallback to language specific defaults, over base defaults
            template = self.jinja_env.get_template(
                'base/{type}.rst'.format(language=self.language, type=self.type)
            )

        ctx.update(**self.get_context_data())
        ctx.update(**kwargs)
        return template.render(**ctx)

    @property
    def rendered(self):
        'Shortcut to render an object in templates.'
        return self.render()

    def get_absolute_path(self):
        return "/autoapi/{type}/{name}".format(
            type=self.type,
            name=self.name,
        )

    def get_context_data(self):
        return {
            'obj': self
        }

    def __lt__(self, other):
        '''Object sorting comparison'''
        if isinstance(other, PythonMapperBase):
            return self.id < other.id
        return super(PythonMapperBase, self).__lt__(other)

    def __str__(self):
        return '<{cls} {id}>'.format(cls=self.__class__.__name__,
                                     id=self.id)

    @property
    def short_name(self):
        '''Shorten name property'''
        return self.name.split('.')[-1]

    @property
    def ref_type(self):
        return self.type

    @property
    def ref_directive(self):
        return self.type

    @property
    def namespace(self):
        pieces = self.id.split('.')[:-1]
        if pieces:
            return '.'.join(pieces)


class SphinxMapperBase(object):

    '''Base class for mapping `PythonMapperBase` objects to Sphinx.

    :param app: Sphinx application instance

    '''

    def __init__(self, app, template_dir=None):
        self.app = app

        TEMPLATE_PATHS = [TEMPLATE_DIR]

        if template_dir:
            # Put at the front so it's loaded first
            TEMPLATE_PATHS.insert(0, template_dir)

        self.jinja_env = Environment(
            loader=FileSystemLoader(TEMPLATE_PATHS),
            # Kill this to fix rendering for now
            # trim_blocks=True,
            # lstrip_blocks=True,
        )

        # Mapping of {filepath -> raw data}
        self.paths = OrderedDict()
        # Mapping of {object id -> Python Object}
        self.objects = OrderedDict()
        # Mapping of {namespace id -> Python Object}
        self.namespaces = OrderedDict()
        # Mapping of {namespace id -> Python Object}
        self.top_level_objects = OrderedDict()

    def load(self, patterns, dir, ignore=[]):
        '''
        Load objects from the filesystem into the ``paths`` dictionary.

        '''
        for path in self.find_files(patterns=patterns, dir=dir, ignore=ignore):
            data = self.read_file(path=path)
            if data:
                self.paths[path] = data

    def find_files(self, patterns, dir, ignore):
        files_to_read = []
        for root, dirnames, filenames in os.walk(dir):
            for pattern in patterns:
                for filename in fnmatch.filter(filenames, pattern):
                    skip = False

                    # Skip ignored files
                    for ignore_pattern in ignore:
                        if fnmatch.fnmatch(os.path.join(root, filename), ignore_pattern):
                            self.app.info(bold('[AutoAPI] ') + darkgreen("Ignoring %s/%s" % (root, filename)))
                            skip = True

                    if skip:
                        continue
                    # Make sure the path is full
                    if os.path.isabs(filename):
                        files_to_read.append(filename)
                    else:
                        files_to_read.append(os.path.join(root, filename))

        for _path in self.app.status_iterator(
                files_to_read,
                '[AutoAPI] Reading files... ',
                darkgreen,
                len(files_to_read)):
            yield _path

    def read_file(self, path, **kwargs):
        '''Read file input into memory

        :param path: Path of file to read
        '''
        # TODO support JSON here
        # TODO sphinx way of reporting errors in logs?
        raise NotImplementedError

    def add_object(self, obj):
        '''
        Add object to local and app environment storage

        :param obj: Instance of a AutoAPI object
        '''
        self.objects[obj.id] = obj

    def map(self, options=None):
        '''Trigger find of serialized sources and build objects'''
        for path, data in self.paths.items():
            for obj in self.create_class(data, options=options):
                self.add_object(obj)

    def create_class(self, obj, options=None):
        '''
        Create class object.

        :param obj: Instance of a AutoAPI object
        '''
        raise NotImplementedError

    def output_rst(self, root, source_suffix):
        for id, obj in self.objects.items():

            if not obj or not obj.top_level_object:
                continue

            rst = obj.render()
            if not rst:
                continue

            try:
                filename = id.split('(')[0]
            except IndexError:
                filename = id
            filename = filename.replace('#', '-')
            detail_dir = os.path.join(root, *filename.split('.'))
            ensuredir(detail_dir)
            path = os.path.join(detail_dir, '%s%s' % ('index', source_suffix))
            with open(path, 'w+') as detail_file:
                detail_file.write(rst.encode('utf-8'))

        # Render Top Index
        top_level_index = os.path.join(root, 'index.rst')
        pages = self.objects.values()
        with open(top_level_index, 'w+') as top_level_file:
            content = self.jinja_env.get_template('index.rst')
            top_level_file.write(content.render(pages=pages))

@@ -217,46 +217,8 @@
 r%0A%0A%0A
-from ..settings import TEMPLATE_DIR%0A%0A%0A
 clas
@@ -3351,32 +3351,76 @@
 late_dir=None):%0A
+        from ..settings import TEMPLATE_DIR%0A
         self.app

Fix circular import