from ruamel.yaml.comments import CommentedSeq, CommentedMap
from ruamel.yaml import dump, RoundTripDumper
from copy import deepcopy
import sys


if sys.version_info[0] == 3:
    unicode = str


class YAMLPointer(object):
    """
    A sequence of indexes/keys that look up a specific chunk of a YAML document.

    A YAML pointer can point to a key, value, item in a sequence or part of a string
    in a value or item.
    """
    def __init__(self):
        self._indices = []

    @property
    def last_index(self):
        assert self.is_index()
        return self._indices[-1][1]

    @property
    def last_val(self):
        assert self.is_val()
        return self._indices[-1][1]

    @property
    def last_strictkey(self):
        assert self.is_key() or self.is_val()
        return self._indices[-1][1][1]

    @property
    def last_regularkey(self):
        assert self.is_key() or self.is_val()
        return self._indices[-1][1][0]

    def val(self, regularkey, strictkey):
        assert isinstance(regularkey, (str, unicode)), type(regularkey)
        assert isinstance(strictkey, (str, unicode)), type(strictkey)
        new_location = deepcopy(self)
        new_location._indices.append(('val', (regularkey, strictkey)))
        return new_location

    def is_val(self):
        return self._indices[-1][0] == 'val'

    def key(self, regularkey, strictkey):
        assert isinstance(regularkey, (str, unicode)), type(regularkey)
        assert isinstance(strictkey, (str, unicode)), type(strictkey)
        new_location = deepcopy(self)
        new_location._indices.append(('key', (regularkey, strictkey)))
        return new_location

    def is_key(self):
        return self._indices[-1][0] == 'key'

    def index(self, index):
        new_location = deepcopy(self)
        new_location._indices.append(('index', index))
        return new_location

    def is_index(self):
        return self._indices[-1][0] == 'index'

    def textslice(self, start, end):
        new_location = deepcopy(self)
        new_location._indices.append(('textslice', (start, end)))
        return new_location

    def is_textslice(self):
        return self._indices[-1][0] == 'textslice'

    def parent(self):
        new_location = deepcopy(self)
        new_location._indices = new_location._indices[:-1]
        return new_location

    def make_child_of(self, pointer):
        new_indices = deepcopy(pointer._indices)
        new_indices.extend(self._indices)

    def _slice_segment(self, indices, segment, include_selected):
        slicedpart = deepcopy(segment)

        if len(indices) == 0 and not include_selected:
            slicedpart = None
        else:
            if len(indices) > 0:
                if indices[0][0] in ("val", "key"):
                    index = indices[0][1][0]
                else:
                    index = indices[0][1]
                start_popping = False

                if isinstance(segment, CommentedMap):
                    for key in segment.keys():
                        if start_popping:
                            slicedpart.pop(key)

                        if index == key:
                            start_popping = True

                            if isinstance(segment[index], (CommentedSeq, CommentedMap)):
                                slicedpart[index] = self._slice_segment(
                                    indices[1:],
                                    segment[index],
                                    include_selected=include_selected
                                )

                            if not include_selected and len(indices) == 1:
                                slicedpart.pop(key)

                if isinstance(segment, CommentedSeq):
                    for i, value in enumerate(segment):
                        if start_popping:
                            del slicedpart[-1]

                        if i == index:
                            start_popping = True

                            if isinstance(segment[index], (CommentedSeq, CommentedMap)):
                                slicedpart[index] = self._slice_segment(
                                    indices[1:],
                                    segment[index],
                                    include_selected=include_selected
                                )

                            if not include_selected and len(indices) == 1:
                                slicedpart.pop(index)

        return slicedpart

    def start_line(self, document):
        slicedpart = self._slice_segment(self._indices, document, include_selected=False)

        if slicedpart is None or slicedpart == {} or slicedpart == []:
            return 1
        else:
            return len(dump(slicedpart, Dumper=RoundTripDumper).rstrip().split('\n')) + 1

    def end_line(self, document):
        slicedpart = self._slice_segment(self._indices, document, include_selected=True)
        return len(dump(slicedpart, Dumper=RoundTripDumper).rstrip().split('\n'))

    def lines(self, document):
        return "\n".join(dump(document, Dumper=RoundTripDumper).split('\n')[
            self.start_line(document) - 1:self.end_line(document)
        ])

    def lines_before(self, document, how_many):
        return "\n".join(dump(document, Dumper=RoundTripDumper).split('\n')[
            self.start_line(document) - 1 - how_many:self.start_line(document) - 1
        ])

    def lines_after(self, document, how_many):
        return "\n".join(dump(document, Dumper=RoundTripDumper).split('\n')[
            self.end_line(document):self.end_line(document) + how_many
        ])

    def get(self, document, strictdoc=False):
        segment = document
        for index_type, index in self._indices:
            if index_type == "val":
                segment = segment[index[1] if strictdoc else index[0]]
            elif index_type == "index":
                segment = segment[index]
            elif index_type == "textslice":
                segment = segment[index[0]:index[1]]
            elif index_type == "key":
                segment = index[1] if strictdoc else index[0]
            else:
                raise RuntimeError("Invalid state")
        return segment

    def __repr__(self):
        return "<YAMLPointer: {0}>".format(self._indices)
