import importlib
import warnings
import re

from docutils.parsers.rst import Directive
from docutils import nodes, utils
from sphinx import addnodes
from recommonmark.parser import CommonMarkParser


def type_description(schema):
    """Return a concise type description for the given schema"""
    if not schema or not isinstance(schema, dict) or schema.keys() == {'description'}:
        return 'any'
    elif "$ref" in schema:
        return ":class:`{}`".format(schema['$ref'].split('/')[-1])
    elif 'enum' in schema:
        return "[{}]".format(', '.join(repr(s) for s in schema['enum']))
    elif 'type' in schema:
        if isinstance(schema['type'], list):
            return '[{}]'.format(', '.join(schema['type']))
        elif schema['type'] == 'array':
            return 'array({})'.format(type_description(schema.get('items', {})))
        elif schema['type'] == 'object':
            return 'dict'
        else:
            return "`{}`".format(schema['type'])
    elif 'anyOf' in schema:
        return "anyOf({})".format(', '.join(type_description(s)
                                            for s in schema['anyOf']))
    else:
        warnings.warn('cannot infer type for schema with keys {}'
                    ''.format(schema.keys()))
        return '--'


def prepare_table_header(titles, widths):
    """Build docutil empty table """
    ncols = len(titles)
    assert len(widths) == ncols

    tgroup = nodes.tgroup(cols=ncols)
    for width in widths:
        tgroup += nodes.colspec(colwidth=width)
    header = nodes.row()
    for title in titles:
        header += nodes.entry('', nodes.paragraph(text=title))
    tgroup += nodes.thead('', header)

    tbody = nodes.tbody()
    tgroup += tbody

    return nodes.table('', tgroup), tbody


reClassDef = re.compile (r":class:`([^`]+)`")
reCode = re.compile (r"`([^`]+)`")

def add_class_def (node, classDef):
    """Add reference on classDef to node """
    
    ref = addnodes.pending_xref( 

        reftarget=classDef,
        reftype="class",
        refdomain="py",  # py:class="None" py:module="altair" refdoc="user_guide/marks"
        refexplicit=False,
        # refdoc="",
        refwarn=False
    )
    ref["py:class"]="None"
    ref["py:module"]="altair"
    
    ref += nodes.literal(text = classDef,  classes=["xref", "py", "py-class"])
    node += ref
    return node

def add_text (node, text):
    """Add text with inline code to node """
    is_text = True
    for part in reCode.split(text):
        if part:
            if is_text:
                node += nodes.Text(part,part)
            else:
                node +=nodes.literal(part, part ) 
     
        is_text = not is_text

    return node


def build_row(item):
    """Return nodes.row with property description"""

    prop, propschema, required = item
    row = nodes.row()

    # Property 
    
    row += nodes.entry('', nodes.paragraph(text=prop), classes=["vl-prop"])

    # Type
    str_type = type_description(propschema) 
    par_type = nodes.paragraph()

    is_text = True
    for part in reClassDef.split(str_type):
        if part:
            if is_text:
                add_text(par_type, part)
            else:
                add_class_def(par_type, part)
        is_text = not is_text

    # row += nodes.entry('')
    row += nodes.entry('', par_type) #, classes=["vl-type-def"]

    # Description
    md_parser = CommonMarkParser()
    #str_descr = "***Required.*** " if required else ""
    str_descr = ""
    str_descr += propschema.get('description', ' ')
    doc_descr = utils.new_document("schema_description")   
    md_parser.parse(str_descr, doc_descr)   
    
    # row += nodes.entry('', *doc_descr.children, classes="vl-decsr")
    row += nodes.entry('', *doc_descr.children, classes=["vl-decsr"])

    return row


def build_schema_tabel(items):
    """Return schema table of items (iterator of prop, schema.item, requred)"""
    table, tbody = prepare_table_header(
        ["Property", "Type", "Description"],
        [10, 20, 50]
    )
    for item in items:
        tbody += build_row(item)

    return table

  
def select_items_from_schema(schema, props=None):
    """Return iterator  (prop, schema.item, requred) on prop, return all in None"""
    properties = schema.get('properties', {})
    required = schema.get('required', [])
    if not props:
        for prop, item in properties.items():
            yield prop, item, prop in required
    else:
        for prop in props:
            try:
                yield prop, properties[prop], prop in required
            except KeyError:
                warnings.warn("Can't find property:", prop)
  

def prepare_schema_tabel(schema, props=None):

    items = select_items_from_schema(schema, props)
    return build_schema_tabel(items)


class AltairObjectTableDirective(Directive):
    """
    Directive for building a table of attribute descriptions.

    Usage:

    .. altair-object-table:: altair.MarkConfig

    """
    has_content = False
    required_arguments = 1

    def run(self):
     
        objectname = self.arguments[0]
        modname, classname = objectname.rsplit('.', 1)
        module = importlib.import_module(modname)
        cls = getattr(module, classname)
        schema = cls.resolve_references(cls._schema)

        # create the table from the object
        table = prepare_schema_tabel(schema)
        return [table]

def setup(app):
    app.add_directive('altair-object-table', AltairObjectTableDirective)
