import re, inspect, textwrap, pydoc
import sphinx
from docscrape import NumpyDocString, FunctionDoc, ClassDoc


class SphinxDocString(NumpyDocString):
    def __init__(self, docstring, config={}):
        self.use_plots = config.get("use_plots", False)
        NumpyDocString.__init__(self, docstring, config=config)

    # string conversion routines
    def _str_header(self, name, symbol="`"):
        return [".. rubric:: " + name, ""]

    def _str_field_list(self, name):
        return [":" + name + ":"]

    def _str_indent(self, doc, indent=4):
        out = []
        for line in doc:
            out += [" " * indent + line]
        return out

    def _str_signature(self):
        return [""]
        if self["Signature"]:
            return ["``%s``" % self["Signature"]] + [""]
        else:
            return [""]

    def _str_summary(self):
        return self["Summary"] + [""]

    def _str_extended_summary(self):
        return self["Extended Summary"] + [""]

    def _str_param_list(self, name):
        out = []
        if self[name]:
            out += self._str_field_list(name)
            out += [""]
            for param, param_type, desc in self[name]:
                out += self._str_indent(
                    ["**%s** : %s" % (param.strip(), param_type)]
                )
                out += [""]
                out += self._str_indent(desc, 8)
                out += [""]
        return out

    @property
    def _obj(self):
        if hasattr(self, "_cls"):
            return self._cls
        elif hasattr(self, "_f"):
            return self._f
        return None

    def _str_member_list(self, name):
        """
        Generate a member listing, autosummary:: table where possible,
        and a table where not.

        """
        out = []
        if self[name]:
            out += [".. rubric:: %s" % name, ""]
            prefix = getattr(self, "_name", "")

            if prefix:
                prefix = "~%s." % prefix

            autosum = []
            others = []
            for param, param_type, desc in self[name]:
                param = param.strip()
                if not self._obj or hasattr(self._obj, param):
                    autosum += ["   %s%s" % (prefix, param)]
                else:
                    others.append((param, param_type, desc))

            if autosum:
                out += [".. autosummary::", "   :toctree:", ""]
                out += autosum

            if others:
                maxlen_0 = max([len(x[0]) for x in others])
                maxlen_1 = max([len(x[1]) for x in others])
                hdr = "=" * maxlen_0 + "  " + "=" * maxlen_1 + "  " + "=" * 10
                fmt = "%%%ds  %%%ds  " % (maxlen_0, maxlen_1)
                n_indent = maxlen_0 + maxlen_1 + 4
                out += [hdr]
                for param, param_type, desc in others:
                    out += [fmt % (param.strip(), param_type)]
                    out += self._str_indent(desc, n_indent)
                out += [hdr]
            out += [""]
        return out

    def _str_section(self, name):
        out = []
        if self[name]:
            out += self._str_header(name)
            out += [""]
            content = textwrap.dedent("\n".join(self[name])).split("\n")
            out += content
            out += [""]
        return out

    def _str_see_also(self, func_role):
        out = []
        if self["See Also"]:
            see_also = super(SphinxDocString, self)._str_see_also(func_role)
            out = [".. seealso::", ""]
            out += self._str_indent(see_also[2:])
        return out

    def _str_warnings(self):
        out = []
        if self["Warnings"]:
            out = [".. warning::", ""]
            out += self._str_indent(self["Warnings"])
        return out

    def _str_index(self):
        idx = self["index"]
        out = []
        if len(idx) == 0:
            return out

        out += [".. index:: %s" % idx.get("default", "")]
        for section, references in idx.iteritems():
            if section == "default":
                continue
            elif section == "refguide":
                out += ["   single: %s" % (", ".join(references))]
            else:
                out += ["   %s: %s" % (section, ",".join(references))]
        return out

    def _str_references(self):
        out = []
        if self["References"]:
            out += self._str_header("References")
            if isinstance(self["References"], str):
                self["References"] = [self["References"]]
            out.extend(self["References"])
            out += [""]
            # Latex collects all references to a separate bibliography,
            # so we need to insert links to it
            if sphinx.__version__ >= "0.6":
                out += [".. only:: latex", ""]
            else:
                out += [".. latexonly::", ""]
            items = []
            for line in self["References"]:
                m = re.match(r".. \[([a-z0-9._-]+)\]", line, re.I)
                if m:
                    items.append(m.group(1))
            out += ["   " + ", ".join(["[%s]_" % item for item in items]), ""]
        return out

    def _str_examples(self):
        examples_str = "\n".join(self["Examples"])

        if (
            self.use_plots
            and "import matplotlib" in examples_str
            and "plot::" not in examples_str
        ):
            out = []
            out += self._str_header("Examples")
            out += [".. plot::", ""]
            out += self._str_indent(self["Examples"])
            out += [""]
            return out
        else:
            return self._str_section("Examples")

    def __str__(self, indent=0, func_role="obj"):
        out = []
        out += self._str_signature()
        out += self._str_index() + [""]
        out += self._str_summary()
        out += self._str_extended_summary()
        for param_list in (
            "Parameters",
            "Returns",
            "Other Parameters",
            "Raises",
            "Warns",
        ):
            out += self._str_param_list(param_list)
        out += self._str_warnings()
        out += self._str_see_also(func_role)
        out += self._str_section("Notes")
        out += self._str_references()
        out += self._str_examples()
        for param_list in ("Attributes", "Methods"):
            out += self._str_member_list(param_list)
        out = self._str_indent(out, indent)
        return "\n".join(out)


class SphinxFunctionDoc(SphinxDocString, FunctionDoc):
    def __init__(self, obj, doc=None, config={}):
        self.use_plots = config.get("use_plots", False)
        FunctionDoc.__init__(self, obj, doc=doc, config=config)


class SphinxClassDoc(SphinxDocString, ClassDoc):
    def __init__(self, obj, doc=None, func_doc=None, config={}):
        self.use_plots = config.get("use_plots", False)
        ClassDoc.__init__(self, obj, doc=doc, func_doc=None, config=config)


class SphinxObjDoc(SphinxDocString):
    def __init__(self, obj, doc=None, config={}):
        self._f = obj
        SphinxDocString.__init__(self, doc, config=config)


def get_doc_object(obj, what=None, doc=None, config={}):
    if what is None:
        if inspect.isclass(obj):
            what = "class"
        elif inspect.ismodule(obj):
            what = "module"
        elif callable(obj):
            what = "function"
        else:
            what = "object"
    if what == "class":
        return SphinxClassDoc(
            obj, func_doc=SphinxFunctionDoc, doc=doc, config=config
        )
    elif what in ("function", "method"):
        return SphinxFunctionDoc(obj, doc=doc, config=config)
    else:
        if doc is None:
            doc = pydoc.getdoc(obj)
        return SphinxObjDoc(obj, doc, config=config)
