from symbolic._lowlevel import lib, ffi
from symbolic._compat import range_type
from symbolic.utils import RustObject, rustcall, decode_str, encode_str, \
    attached_refs


__all__ = ['SourceView', 'SourceMapView', 'SourceMapTokenMatch']


class SourceMapTokenMatch(object):
    """Represents a token matched or looked up from the index."""

    def __init__(self, src_line, src_col, dst_line, dst_col,
                 src_id=None, name=None, src=None, function_name=None):
        self.src_line = src_line
        self.src_col = src_col
        self.dst_line = dst_line
        self.dst_col = dst_col
        self.src_id = src_id
        self.name = name
        self.src = src
        self.function_name = function_name

    @classmethod
    def _from_objptr(cls, tm):
        rv = object.__new__(cls)
        rv.src_line = tm.src_line
        rv.src_col = tm.src_col
        rv.dst_line = tm.dst_line
        rv.dst_col = tm.dst_col
        rv.src_id = tm.src_id
        rv.name = decode_str(tm.name) or None
        rv.src = decode_str(tm.src) or None
        rv.function_name = decode_str(tm.function_name) or None
        return rv

    def __eq__(self, other):
        if self.__class__ is not other.__class__:
            return False
        return self.__dict__ == other.__dict__

    def __ne__(self, other):
        return not self.__eq__(other)

    def __repr__(self):
        return '<SourceMapTokenMatch %s:%d>' % (
            self.src,
            self.src_line,
        )


class SourceView(RustObject):
    """Gives reasonably efficient access to javascript sourcecode."""
    __dealloc_func__ = lib.symbolic_sourceview_free

    @classmethod
    def from_bytes(cls, data):
        """Constructs a source view from bytes."""
        data = bytes(data)
        rv = cls._from_objptr(rustcall(lib.symbolic_sourceview_from_bytes,
                              data, len(data)))
        # we need to keep this reference alive or we crash. hard.
        attached_refs[rv] = data
        return rv

    def get_source(self):
        return decode_str(self._methodcall(lib.symbolic_sourceview_as_str))

    def __len__(self):
        return self._methodcall(lib.symbolic_sourceview_get_line_count)

    def __getitem__(self, idx):
        if not isinstance(idx, slice):
            if idx >= len(self):
                raise IndexError('No such line')
            return decode_str(self._methodcall(
                lib.symbolic_sourceview_get_line, idx))

        rv = []
        for idx in range_type(*idx.indices(len(self))):
            try:
                rv.append(self[idx])
            except IndexError:
                pass
        return rv

    def __iter__(self):
        for x in range_type(len(self)):
            yield self[x]


class SourceMapView(RustObject):
    """Gives access to a source map."""
    __dealloc_func__ = lib.symbolic_sourcemapview_free

    @classmethod
    def from_json_bytes(cls, data):
        """Constructs a sourcemap from bytes of JSON data."""
        data = bytes(data)
        return cls._from_objptr(rustcall(
            lib.symbolic_sourcemapview_from_json_slice, data, len(data)))

    def lookup(self, line, col, minified_function_name=None,
               minified_source=None):
        """Looks up a token from the sourcemap and optionally also
        resolves a function name from a stacktrace to the original one.
        """
        if minified_function_name is None or minified_source is None:
            rv = self._methodcall(
                lib.symbolic_sourcemapview_lookup_token, line, col)
        else:
            if not isinstance(minified_source, SourceView):
                raise TypeError('source view required')
            rv = self._methodcall(
                lib.symbolic_sourcemapview_lookup_token_with_function_name,
                line, col, encode_str(minified_function_name),
                minified_source._objptr)
        if rv != ffi.NULL:
            try:
                return SourceMapTokenMatch._from_objptr(rv)
            finally:
                rustcall(lib.symbolic_token_match_free, rv)

    def get_sourceview(self, idx):
        """Given a source index returns the source view that created it."""
        rv = self._methodcall(lib.symbolic_sourcemapview_get_sourceview, idx)
        if rv != ffi.NULL:
            return SourceView._from_objptr(rv, shared=True)

    @property
    def source_count(self):
        """Returns the number of sources."""
        return self._methodcall(lib.symbolic_sourcemapview_get_source_count)

    def get_source_name(self, idx):
        """Returns the name of the source at the given index."""
        return decode_str(self._methodcall(
            lib.symbolic_sourcemapview_get_source_name, idx)) or None

    def iter_sources(self):
        """Iterates over the sources in the file."""
        for src_id in range_type(self.source_count):
            yield src_id, self.get_source_name(src_id)

    def __len__(self):
        return self._methodcall(lib.symbolic_sourcemapview_get_tokens)

    def __getitem__(self, idx):
        rv = self._methodcall(lib.symbolic_sourcemapview_get_token, idx)
        if rv == ffi.NULL:
            raise IndexError('Token out of range')
        try:
            return SourceMapTokenMatch._from_objptr(rv)
        finally:
            rustcall(lib.symbolic_token_match_free, rv)

    def __iter__(self):
        for x in range_type(len(self)):
            yield self[x]
