# coding: utf-8

from __future__ import absolute_import

import warnings
import textwrap

from strictyaml.ruamel.compat import utf8

if False:  # MYPY
    from typing import Any, Dict, Optional, List, Text  # NOQA


__all__ = [
    "FileMark",
    "StringMark",
    "CommentMark",
    "YAMLError",
    "MarkedYAMLError",
    "ReusedAnchorWarning",
    "UnsafeLoaderWarning",
    "MarkedYAMLWarning",
    "MarkedYAMLFutureWarning",
]


class StreamMark(object):
    __slots__ = "name", "index", "line", "column"

    def __init__(self, name, index, line, column):
        # type: (Any, int, int, int) -> None
        self.name = name
        self.index = index
        self.line = line
        self.column = column

    def __str__(self):
        # type: () -> Any
        where = '  in "%s", line %d, column %d' % (
            self.name,
            self.line + 1,
            self.column + 1,
        )
        return where

    def __eq__(self, other):
        # type: (Any) -> bool
        if self.line != other.line or self.column != other.column:
            return False
        if self.name != other.name or self.index != other.index:
            return False
        return True

    def __ne__(self, other):
        # type: (Any) -> bool
        return not self.__eq__(other)


class FileMark(StreamMark):
    __slots__ = ()


class StringMark(StreamMark):
    __slots__ = "name", "index", "line", "column", "buffer", "pointer"

    def __init__(self, name, index, line, column, buffer, pointer):
        # type: (Any, int, int, int, Any, Any) -> None
        StreamMark.__init__(self, name, index, line, column)
        self.buffer = buffer
        self.pointer = pointer

    def get_snippet(self, indent=4, max_length=75):
        # type: (int, int) -> Any
        if self.buffer is None:  # always False
            return None
        head = ""
        start = self.pointer
        while start > 0 and self.buffer[start - 1] not in u"\0\r\n\x85\u2028\u2029":
            start -= 1
            if self.pointer - start > max_length / 2 - 1:
                head = " ... "
                start += 5
                break
        tail = ""
        end = self.pointer
        while (
            end < len(self.buffer) and self.buffer[end] not in u"\0\r\n\x85\u2028\u2029"
        ):
            end += 1
            if end - self.pointer > max_length / 2 - 1:
                tail = " ... "
                end -= 5
                break
        snippet = utf8(self.buffer[start:end])
        caret = "^"
        caret = "^ (line: {})".format(self.line + 1)
        return (
            " " * indent
            + head
            + snippet
            + tail
            + "\n"
            + " " * (indent + self.pointer - start + len(head))
            + caret
        )

    def __str__(self):
        # type: () -> Any
        snippet = self.get_snippet()
        where = '  in "%s", line %d, column %d' % (
            self.name,
            self.line + 1,
            self.column + 1,
        )
        if snippet is not None:
            where += ":\n" + snippet
        return where


class CommentMark(object):
    __slots__ = ("column",)

    def __init__(self, column):
        # type: (Any) -> None
        self.column = column


class YAMLError(Exception):
    pass


class MarkedYAMLError(YAMLError):
    def __init__(
        self,
        context=None,
        context_mark=None,
        problem=None,
        problem_mark=None,
        note=None,
        warn=None,
    ):
        # type: (Any, Any, Any, Any, Any, Any) -> None
        self.context = context
        self.context_mark = context_mark
        self.problem = problem
        self.problem_mark = problem_mark
        self.note = note
        # warn is ignored

    def __str__(self):
        # type: () -> Any
        lines = []  # type: List[str]
        if self.context is not None:
            lines.append(self.context)
        if self.context_mark is not None and (
            self.problem is None
            or self.problem_mark is None
            or self.context_mark.name != self.problem_mark.name
            or self.context_mark.line != self.problem_mark.line
            or self.context_mark.column != self.problem_mark.column
        ):
            lines.append(str(self.context_mark))
        if self.problem is not None:
            lines.append(self.problem)
        if self.problem_mark is not None:
            lines.append(str(self.problem_mark))
        if self.note is not None and self.note:
            note = textwrap.dedent(self.note)
            lines.append(note)
        return "\n".join(lines)


class YAMLStreamError(Exception):
    pass


class YAMLWarning(Warning):
    pass


class MarkedYAMLWarning(YAMLWarning):
    def __init__(
        self,
        context=None,
        context_mark=None,
        problem=None,
        problem_mark=None,
        note=None,
        warn=None,
    ):
        # type: (Any, Any, Any, Any, Any, Any) -> None
        self.context = context
        self.context_mark = context_mark
        self.problem = problem
        self.problem_mark = problem_mark
        self.note = note
        self.warn = warn

    def __str__(self):
        # type: () -> Any
        lines = []  # type: List[str]
        if self.context is not None:
            lines.append(self.context)
        if self.context_mark is not None and (
            self.problem is None
            or self.problem_mark is None
            or self.context_mark.name != self.problem_mark.name
            or self.context_mark.line != self.problem_mark.line
            or self.context_mark.column != self.problem_mark.column
        ):
            lines.append(str(self.context_mark))
        if self.problem is not None:
            lines.append(self.problem)
        if self.problem_mark is not None:
            lines.append(str(self.problem_mark))
        if self.note is not None and self.note:
            note = textwrap.dedent(self.note)
            lines.append(note)
        if self.warn is not None and self.warn:
            warn = textwrap.dedent(self.warn)
            lines.append(warn)
        return "\n".join(lines)


class ReusedAnchorWarning(YAMLWarning):
    pass


class UnsafeLoaderWarning(YAMLWarning):
    text = """
The default 'Loader' for 'load(stream)' without further arguments can be unsafe.
Use 'load(stream, Loader=strictyaml.ruamel.Loader)' explicitly if that is OK.
Alternatively include the following in your code:

  import warnings
  warnings.simplefilter('ignore', strictyaml.ruamel.error.UnsafeLoaderWarning)

In most other cases you should consider using 'safe_load(stream)'"""
    pass


warnings.simplefilter("once", UnsafeLoaderWarning)


class MantissaNoDotYAML1_1Warning(YAMLWarning):
    def __init__(self, node, flt_str):
        # type: (Any, Any) -> None
        self.node = node
        self.flt = flt_str

    def __str__(self):
        # type: () -> Any
        line = self.node.start_mark.line
        col = self.node.start_mark.column
        return """
In YAML 1.1 floating point values should have a dot ('.') in their mantissa.
See the Floating-Point Language-Independent Type for YAML™ Version 1.1 specification
( http://yaml.org/type/float.html ). This dot is not required for JSON nor for YAML 1.2

Correct your float: "{}" on line: {}, column: {}

or alternatively include the following in your code:

  import warnings
  warnings.simplefilter('ignore', strictyaml.ruamel.error.MantissaNoDotYAML1_1Warning)

""".format(
            self.flt, line, col
        )


warnings.simplefilter("once", MantissaNoDotYAML1_1Warning)


class YAMLFutureWarning(Warning):
    pass


class MarkedYAMLFutureWarning(YAMLFutureWarning):
    def __init__(
        self,
        context=None,
        context_mark=None,
        problem=None,
        problem_mark=None,
        note=None,
        warn=None,
    ):
        # type: (Any, Any, Any, Any, Any, Any) -> None
        self.context = context
        self.context_mark = context_mark
        self.problem = problem
        self.problem_mark = problem_mark
        self.note = note
        self.warn = warn

    def __str__(self):
        # type: () -> Any
        lines = []  # type: List[str]
        if self.context is not None:
            lines.append(self.context)

        if self.context_mark is not None and (
            self.problem is None
            or self.problem_mark is None
            or self.context_mark.name != self.problem_mark.name
            or self.context_mark.line != self.problem_mark.line
            or self.context_mark.column != self.problem_mark.column
        ):
            lines.append(str(self.context_mark))
        if self.problem is not None:
            lines.append(self.problem)
        if self.problem_mark is not None:
            lines.append(str(self.problem_mark))
        if self.note is not None and self.note:
            note = textwrap.dedent(self.note)
            lines.append(note)
        if self.warn is not None and self.warn:
            warn = textwrap.dedent(self.warn)
            lines.append(warn)
        return "\n".join(lines)
