from AccessControl import getSecurityManager
from Acquisition import aq_chain
from Acquisition import aq_inner
from email.header import Header
from email.mime.text import MIMEText
from plone.app.layout.navigation.interfaces import INavigationRoot
from plonetheme.nuplone import MessageFactory as _
from Products.CMFCore.ActionInformation import ActionInfo
from Products.CMFCore.utils import getToolByName
from Products.CMFPlone.interfaces import IPloneSiteRoot
from z3c.form.interfaces import IAddForm
from z3c.form.interfaces import IEditForm

import collections
import email.utils as emailutils
import logging


log = logging.getLogger(__name__)


def getPortal(context):
    for obj in aq_chain(aq_inner(context)):
        if IPloneSiteRoot.providedBy(obj):
            return obj


def getNavigationRoot(context):
    for obj in aq_chain(aq_inner(context)):
        if INavigationRoot.providedBy(obj):
            break
    return obj


def checkPermission(context, permission):
    return getSecurityManager().checkPermission(permission, context)


def isAnonymous(user=None):
    if user is None:
        user = getSecurityManager().getUser()
    return user is None or user.getUserName() == "Anonymous User"


def viewType(context, request):
    """Determine what type of view the user is looking at.

    This returns one of three options: ``view`` for normal object views,
    ``add`` for add forms, ``edit`` for edit forms, and ``other`` for
    all other types.
    """

    view = request.other.get("PUBLISHED")
    if view is not None:
        if IAddForm.providedBy(view):
            return "add"
        if IEditForm.providedBy(view):
            return "edit"

    for url in ["ACTUAL_URL", "VIRTUAL_URL", "URL"]:
        current_url = request.get(url)
        if current_url is not None:
            break
    else:
        return "view"

    if current_url.endswith("/"):
        current_url = current_url[:-1]

    if current_url.endswith("@@edit"):
        return "edit"
    elif "/++add++" in current_url:
        return "add"

    try:
        if current_url == aq_inner(context).absolute_url():
            return "view"
    except AttributeError:
        # object has no absolute_url (None or Not Found)
        pass

    return "other"


class SimpleLiteral(str):
    def __html__(self):
        return str(self)


def setLanguage(request, context, lang=None):
    """Switch Plone to another language. If no language is given via the `lang`
    parameter the language is taken from a `language` request parameter. If a
    dialect was chosen but is not available the main language is used instead.
    If the main language is also not available nothing is done and ``False`` is
    returned.

    It is safe to call this method before rendering a template: it will
    immediately switch the current language. That means there is no need
    to trigger a redirect to force a new HTTP request.
    """
    if lang is None:
        lang = request.form.get("language")
    if not lang:
        return

    lang = lang.lower()
    lt = getToolByName(context, "portal_languages")
    res = lt.setLanguageCookie(lang=lang, request=request)
    if res is None and "-" in lang:
        lang = lang.split("-")[0]
        res = lt.setLanguageCookie(lang=lang, request=request)
        if res is None:
            log.warning("Failed to switch language to %s", lang)
            return False

    # In addition to setting the cookie also update the PTS language.
    # This effectively switches Plone over to the new language without
    # requiring a new HTTP request.
    request["LANGUAGE"] = lang
    binding = request.get("LANGUAGE_TOOL", None)
    if binding is not None:
        binding.LANGUAGE = lang

    return True


FactoryInfo = collections.namedtuple("FactoryInfo", "id title description url")


def getFactoriesInContext(context):
    """Return a list of all factories available to the current user at the
    given location."""
    context = aq_inner(context)
    ftis = context.allowedContentTypes()
    if not ftis:
        return []

    tt = getToolByName(context, "portal_types")
    ec = tt._getExprContext(context)
    actions = [ActionInfo(fti, ec) for fti in ftis]
    actions = [
        FactoryInfo(
            action.get("id"),
            action.get("title") or action.get("id"),
            action.get("description") or None,
            action["url"],
        )
        for action in actions
    ]
    return actions


def createEmailTo(
    sender_name,
    sender_email,
    recipient_name,
    recipient_email,
    subject,
    body,
    format="plain",
):
    """Create an :obj:`email.MIMEText.MIMEtext` instance for an email.

    This method will take care of adding addings a date header and
    message ID to the email, as well as quoting of non-ASCII content.
    """
    if isinstance(body, str):
        mail = MIMEText(body.encode("utf-8"), format, "utf-8")
    else:
        mail = MIMEText(body, format)

    if sender_name:
        mail["From"] = emailutils.formataddr((sender_name, sender_email))
    else:
        mail["From"] = sender_email
    if recipient_name:
        mail["To"] = emailutils.formataddr((recipient_name, recipient_email))
    else:
        mail["To"] = recipient_email
    mail["Subject"] = Header(subject.encode("utf-8"), "utf-8")
    mail["Date"] = emailutils.formatdate(localtime=True)
    mail.set_param("charset", "utf-8")

    return mail


def formatDate(request, date, length="long"):
    """Wrapper aound the zope.i18n date formatter which does not abort on
    pre-1900 dates."""
    if date.year < 1900:
        return _("date_to_early", default="<pre-1900-date>")
    return request.locale.dates.getFormatter("date", length).format(date)


def formatTime(request, time, length=None):
    return request.locale.dates.getFormatter("time", length).format(time)


def formatDatetime(request, timestamp, length="long"):
    """Wrapper aound the zope.i18n datetime formatter which does not abort on
    pre-1900 dates."""
    if timestamp.year < 1900:
        return _("date_to_early", default="<pre-1900-date>")
    if length == "long":
        return _(
            "format_datetime",
            default="${date} at ${time}",
            mapping=dict(
                date=formatDate(request, timestamp, "long"),
                time=formatTime(request, timestamp, "short"),
            ),
        )
    return request.locale.dates.getFormatter("dateTime", length).format(timestamp)
