# Copyright (c) 2017  <NAME>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
"""
This module implements the structural representation of an API documentation
in separate documents and symbolic names. The final documentation is rendered
from this structured representation.
"""

from __future__ import print_function
import os


class Section(object):
    """
    A section represents a part of a #Document. It contains Markdown-formatted
    content that will be rendered into a file at some point.

    # Attributes
    doc (Document): The document that the section belongs to.
    identifier (str, None): The globally unique identifier of the section. This
      identifier usually matches the name of the element that the section
      describes (eg. a class or function) and will be used for cross-referencing.
    title (str, None): The title of the section. If specified, it will be
      rendered before `section.content` and the header-size will depend on
      the `section.depth`.
    depth (int): The depth of the section, defaults to 1. Currently only affects
      the header-size that is rendered for the `section.title`.
    content (str): The Markdown-formatted content of the section.
    """

    def __init__(self, doc, identifier=None, title=None, depth=1, content=None, header_type='html'):
        self.doc = doc
        self.identifier = identifier
        self.title = title
        self.depth = depth
        self.content = content if content is not None else '*Nothing to see here.*'
        self.header_type = header_type

    def render(self, stream):
        """
        Render the section into *stream*.
        """

        if self.header_type == 'html':
            print('<h{depth} id="{id}">{title}</h{depth}>\n'
                  .format(depth=self.depth, id=self.identifier, title=self.title),
                  file=stream)
        elif self.header_type == 'markdown':
            print('\n' + ('#' * self.depth), self.title, file=stream)
        else:
            raise ValueError('Invalid header type: %s' % self.header_type)
        print(self.content, file=stream)

    @property
    def index(self):
        """
        Returns the #Index that this section is associated with, accessed via
        `section.document`.
        """

        return self.document.index


class Document(object):
    """
    Represents a single document that may contain several #Section#s. Every
    document *must* have a relative URL associated with it.

    # Attributes
    index (Index): The index that the document belongs to.
    url (str): The relative URL of the document.
    """

    def __init__(self, index, url):
        self.index = index
        self.url = url
        self.sections = []


class Index(object):
    """
    The index manages all documents and sections globally. It keeps track of
    the symbolic names allocated for the sections to be able to link to them
    from other sections.

    # Attributes
    documents (dict):
    sections (dict):
    """

    def __init__(self):
        self.documents = {}
        self.sections = {}

    def new_document(self, filename, url=None):
        """
        Create a new document.

        # Arguments
        filename (str): The filename of the document. Must end with `.md`.
        url (str): The relative URL of the document. If omitted, will be
          automatically deduced from *filename* (same without the `.md` suffix).

        # Raises
        ValueError: If *filename* does not end with `.md`.
        ValueError: If *filename* is not a relative path.
        ValueError: If a document with the specified *filename* already exists.
        """

        if not filename.endswith('.md'):
            raise ValueError('filename must end with `.md`')
        if os.path.isabs(filename):
            raise ValueError('filename must be relative')
        if filename in self.documents:
            raise ValueError(
                'document filename {!r} already used'.format(filename))
        if not url:
            url = filename[:-3]

        doc = Document(self, url)
        self.documents[filename] = doc
        return doc

    def new_section(self, doc, *args, **kwargs):
        """
        Create a new section in the specified document. The arguments for this
        method match the parameters for the #Section constructor.

        # Raises
        ValueError: If the section identifier is already used.
        """

        section = Section(doc, *args, **kwargs)
        if section.identifier:
            if section.identifier in self.sections:
                raise ValueError('section identifier {!r} already used'
                                 .format(section.identifier))
            self.sections[section.identifier] = section
        doc.sections.append(section)
        return section
