import GPS
import json
import os
from project_support import Project_Support
from diagram_utils import Diagram_Utils


class Mapping_File(object):
    """
    Support for the mapping file generated by qgen, which maps from source
    lines to blocks, and back. The format of this mapping file is:
       { "filename.adb": {   # repeated for each file
              "block1": {    # repeated for each block
                  "line": ["1-10", "65-70"], # lines impacted by this block
                  "symbol": ["s1", "s2"]   # variables from this block
              }
       }
    """

    def __init__(self, filename=None):
        # In the following:
        #   - `file`:  an instance of `GPS.File`
        #   - `linerange`: a tuple (start, end)
        #   - `filename`: a string

        self._blocks = {}    # block_id => set of (file,linerange)
        self._files = {}     # filename => {line => blockid}
        self._mdl = {}       # sourcefile => mdlfile
        self._symbols = {}   # block_id => set(symbols)
        self._funcinfo = {}  # funcname => (filename, endline)
        self._fileinfo = {}  # filename => [(funcname, startline-endline)]

    def load(self, mdlfile):
        """
        Load a mapping file from the disk. This cumulates with existing
        information already loaded.
        :param GPS.File mdlfile: the MDL file we start from
        """
        filename = os.path.join(
            Project_Support.get_output_dir(mdlfile),
            '%s.json' % os.path.basename(mdlfile.path))

        try:
            f = open(filename)
        except IOError:
            # Do not print an error: this is the normal case when no code
            # has been generated yet
            return

        try:
            js = json.load(f)
        except:
            GPS.Console().write('Invalid json in %s\n' % filename)
            return

        for filename, blocks in js.iteritems():
            f = GPS.File(filename)
            self._mdl[f.path] = mdlfile

            b = self._files.setdefault(f.path, {})

            for blockid, blockinfo in blocks.iteritems():
                if blockid == '@qgen_functions':
                    for func_id, funcline in blockinfo.iteritems():
                        lines = funcline.split('-')
                        if filename not in self._fileinfo:
                            self._fileinfo[filename] = [
                                (func_id, (int(lines[0]), int(lines[1])))]
                        else:
                            self._fileinfo[filename].append((func_id, (
                                int(lines[0]), int(lines[1]))))
                        self._funcinfo[func_id] = (filename, lines)
                    continue

                a = self._blocks.setdefault(blockid, set())
                for linerange in blockinfo.get('lines', []):
                    if isinstance(linerange, int):
                        rg = (linerange, linerange)
                    elif '-' in linerange:
                        s = linerange.split('-')
                        rg = (int(s[0]), int(s[1]))
                    else:
                        rg = (int(linerange), int(linerange))

                    a.add((f, rg))

                    for line in range(rg[0], rg[1] + 1):
                        b[line] = blockid

                a = self._symbols.setdefault(blockid, set())
                for symbol in blockinfo.get('symbols', []):
                    a.add(symbol)

    def get_file_funcinfos(self, filename):
        """
        Returns the list of (function_name, [line_start, line_end]) for the
        given filename
        """
        return self._fileinfo.get(filename, [])

    def get_source_ranges(self, blockid):
        """
        Returns the set of (filename, linerange) tuples for the source lines
        generated from that block.
        """
        return self._blocks.get(blockid, set())

    def get_symbols(self, blockid):
        """
        Returns the set of source code symbols (variables) for this block.
        """
        return self._symbols.get(blockid, set())

    def get_func_bounds(self, funcname):
        """
        Returns the file and lines where the function named funcname
        starts and ends
        """
        return self._funcinfo.get(funcname, (None, None))

    def get_block(self, file, line):
        """
        The block name corresponding to a given source line
        :param GPS.File filename:
        """
        a = self._files.get(file.path, {})
        return a.get(line, None)

    def get_diagram_for_item(self, diags, block):
        """
        A block will have an id of the form diagram/blockid
        that allows to compute the diagram to fetch and look for
        the item inside it, instead of fetching all diagrams
        using Browsers.get_diagram_for_item
        :param JSON_Diagram_File diags: The file to search in
        :param string block: The block to search for
        """
        diag = diags.get(Diagram_Utils.block_split(
            block, count=1, backward=True)[0])

        if diag:
            it = diag.get_item(block)
            return (diag, it)

        return None

    def get_mdl_file(self, file):
        """
        Return the name of the MDL file used to generate the given file
        :param GPS.File file: the source file
        :return: a `GPS.File`
        """
        return self._mdl.get(file.path, None)
