from __future__ import unicode_literals, division, absolute_import, print_function
import xml.dom
import css_parser
from . import cssrule
from .cssstyledeclaration import CSSStyleDeclaration
from css_parser.prodparser import Prod, PreDef, Sequence, Choice, ProdParser
"""MarginRule implements DOM Level 2 CSS MarginRule."""

__all__ = ['MarginRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'

import sys
if sys.version_info[0] >= 3:
    string_type = str
else:
    string_type = basestring


class MarginRule(cssrule.CSSRule):
    """
    A margin at-rule consists of an ATKEYWORD that identifies the margin box
    (e.g. '@top-left') and a block of declarations (said to be in the margin
    context).

    Format::

        margin :
               margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S*
               ;

        margin_sym :
               TOPLEFTCORNER_SYM |
               TOPLEFT_SYM |
               TOPCENTER_SYM |
               TOPRIGHT_SYM |
               TOPRIGHTCORNER_SYM |
               BOTTOMLEFTCORNER_SYM |
               BOTTOMLEFT_SYM |
               BOTTOMCENTER_SYM |
               BOTTOMRIGHT_SYM |
               BOTTOMRIGHTCORNER_SYM |
               LEFTTOP_SYM |
               LEFTMIDDLE_SYM |
               LEFTBOTTOM_SYM |
               RIGHTTOP_SYM |
               RIGHTMIDDLE_SYM |
               RIGHTBOTTOM_SYM
               ;

    e.g.::

        @top-left {
            content: "123";
            }
    """
    margins = ['@top-left-corner',
               '@top-left',
               '@top-center',
               '@top-right',
               '@top-right-corner',
               '@bottom-left-corner',
               '@bottom-left',
               '@bottom-center',
               '@bottom-right',
               '@bottom-right-corner',
               '@left-top',
               '@left-middle',
               '@left-bottom',
               '@right-top',
               '@right-middle',
               '@right-bottom'
               ]

    def __init__(self, margin=None, style=None, parentRule=None,
                 parentStyleSheet=None, readonly=False):
        """
        :param atkeyword:
            The margin area, e.g. '@top-left' for this rule
        :param style:
            CSSStyleDeclaration for this MarginRule
        """
        super(MarginRule, self).__init__(parentRule=parentRule,
                                         parentStyleSheet=parentStyleSheet)

        self._atkeyword = self._keyword = None

        if margin:
            self.margin = margin

        if style:
            self.style = style
        else:
            self.style = CSSStyleDeclaration(parentRule=self)

        self._readonly = readonly

    def _setMargin(self, margin):
        """Check if new keyword fits the rule it is used for."""
        n = self._normalize(margin)

        if n not in MarginRule.margins:
            self._log.error('Invalid margin @keyword for this %s rule: %r' %
                            (self.margin, margin),
                            error=xml.dom.InvalidModificationErr)

        else:
            self._atkeyword = n
            self._keyword = margin

    margin = property(lambda self: self._atkeyword, _setMargin,
                      doc="Margin area of parent CSSPageRule. "
                          "`margin` and `atkeyword` are both normalized "
                          "@keyword of the @rule.")

    atkeyword = margin

    def __repr__(self):
        return "css_parser.css.%s(margin=%r, style=%r)" % (
                self.__class__.__name__, self.margin, self.style.cssText)

    def __str__(self):
        return "<css_parser.css.%s object margin=%r style=%r "\
               "at 0x%x>" % (self.__class__.__name__,
                             self.margin,
                             self.style.cssText,
                             id(self))

    def _getCssText(self):
        """Return serialized property cssText."""
        return css_parser.ser.do_MarginRule(self)

    def _setCssText(self, cssText):
        """
        :exceptions:
            - :exc:`~xml.dom.SyntaxErr`:
              Raised if the specified CSS string value has a syntax error and
              is unparsable.
            - :exc:`~xml.dom.InvalidModificationErr`:
              Raised if the specified CSS string value represents a different
              type of rule than the current one.
            - :exc:`~xml.dom.HierarchyRequestErr`:
              Raised if the rule cannot be inserted at this point in the
              style sheet.
            - :exc:`~xml.dom.NoModificationAllowedErr`:
              Raised if the rule is readonly.
        """
        super(MarginRule, self)._setCssText(cssText)

        # TEMP: all style tokens are saved in store to fill styledeclaration
        # TODO: resolve when all generators
        styletokens = Prod(name='styletokens',
                           match=lambda t, v: v != '}',
                           # toSeq=False,
                           toStore='styletokens',
                           storeToken=True
                           )

        prods = Sequence(Prod(name='@ margin',
                              match=lambda t, v:
                              t == 'ATKEYWORD' and
                              self._normalize(v) in MarginRule.margins,
                              toStore='margin'
                              # TODO?
                              # , exception=xml.dom.InvalidModificationErr
                              ),
                         PreDef.char('OPEN', '{'),
                         Sequence(Choice(PreDef.unknownrule(toStore='@'),
                                         styletokens),
                                  minmax=lambda: (0, None)
                                  ),
                         PreDef.char('CLOSE', '}', stopAndKeep=True)
                         )
        # parse
        ok, seq, store, unused = ProdParser().parse(cssText,
                                                    'MarginRule',
                                                    prods)

        if ok:
            # TODO: use seq for serializing instead of fixed stuff?
            self._setSeq(seq)

            if 'margin' in store:
                # may raise:
                self.margin = store['margin'].value
            else:
                self._log.error('No margin @keyword for this %s rule' %
                                self.margin,
                                error=xml.dom.InvalidModificationErr)

            # new empty style
            self.style = CSSStyleDeclaration(parentRule=self)

            if 'styletokens' in store:
                # may raise:
                self.style.cssText = store['styletokens']

    cssText = property(fget=_getCssText, fset=_setCssText,
                       doc="(DOM) The parsable textual representation.")

    def _setStyle(self, style):
        """
        :param style: A string or CSSStyleDeclaration which replaces the
            current style object.
        """
        self._checkReadonly()
        if isinstance(style, string_type):
            self._style = CSSStyleDeclaration(cssText=style, parentRule=self)
        else:
            style._parentRule = self
            self._style = style

    style = property(lambda self: self._style, _setStyle,
                     doc="(DOM) The declaration-block of this rule set.")

    type = property(lambda self: self.MARGIN_RULE,
                    doc="The type of this rule, as defined by a CSSRule "
                        "type constant.")

    wellformed = property(lambda self: bool(self.atkeyword))
