##
# Copyright (c) 2009-2017 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##

"""
Calendar Server Web UI.
"""

__all__ = [
    "WebCalendarResource",
]

import os

from time import time
from urlparse import urlparse
from cgi import parse_qs

from twisted.internet.defer import succeed

from txweb2 import responsecode
from txweb2.http import Response
from txweb2.http_headers import MimeType
from txweb2.stream import MemoryStream
from txdav.xml import element as davxml
from txweb2.dav.resource import TwistedACLInheritable

from twistedcaldav.config import config
from twistedcaldav.extensions import DAVFile, ReadOnlyResourceMixIn
from twistedcaldav.timezones import hasTZ

DEFAULT_TIMEZONE = "America/Los_Angeles"


class WebCalendarResource (ReadOnlyResourceMixIn, DAVFile):

    def defaultAccessControlList(self):
        return succeed(
            davxml.ACL(
                davxml.ACE(
                    davxml.Principal(davxml.Authenticated()),
                    davxml.Grant(
                        davxml.Privilege(davxml.Read()),
                    ),
                    davxml.Protected(),
                    TwistedACLInheritable(),
                ),
            )
        )

    def etag(self):
        # Can't be calculated here
        return succeed(None)

    def contentLength(self):
        # Can't be calculated here
        return None

    def lastModified(self):
        return None

    def exists(self):
        return True

    def displayName(self):
        return "Web Calendar"

    def contentType(self):
        return MimeType.fromString("text/html; charset=utf-8")

    def contentEncoding(self):
        return None

    def createSimilarFile(self, path):
        return DAVFile(path, principalCollections=self.principalCollections())

    _htmlContent_lastCheck = 0
    _htmlContent_statInfo = 0
    _htmlContentDebug_lastCheck = 0
    _htmlContentDebug_statInfo = 0

    def htmlContent(self, debug=False):
        if debug:
            cacheAttr = "_htmlContentDebug"
            templateFileName = "debug_standalone.html"
        else:
            cacheAttr = "_htmlContent"
            templateFileName = "standalone.html"

        templateFileName = os.path.join(
            config.WebCalendarRoot, templateFileName
        )

        #
        # See if the file changed, and dump the cached template if so.
        # Don't bother to check if we've checked in the past minute.
        # We don't cache if debug is true.
        #
        if not debug and hasattr(self, cacheAttr):
            currentTime = time()
            if currentTime - getattr(self, cacheAttr + "_lastCheck") > 60:
                statInfo = os.stat(templateFileName)
                statInfo = (statInfo.st_mtime, statInfo.st_size)
                if statInfo != getattr(self, cacheAttr + "_statInfo"):
                    delattr(self, cacheAttr)
                    setattr(self, cacheAttr + "_statInfo", statInfo)
                setattr(self, cacheAttr + "_lastCheck", currentTime)

        #
        # If we don't have a cached template, load it up.
        #
        if not hasattr(self, cacheAttr):
            with open(templateFileName) as templateFile:
                htmlContent = templateFile.read()

            if debug:
                # Don't cache
                return htmlContent
            setattr(self, cacheAttr, htmlContent)

        return getattr(self, cacheAttr)

    def render(self, request):
        if not self.fp.isdir():
            return responsecode.NOT_FOUND

        #
        # Get URL of authenticated principal.
        # Don't need to authenticate here because the ACL will have already
        # required it.
        #
        authenticatedPrincipalURL = request.authnUser.principalURL()

        def queryValue(arg):
            query = parse_qs(urlparse(request.uri).query, True)
            return query.get(arg, [""])[0]

        #
        # Parse debug query arg
        #
        debug = queryValue("debug")
        debug = debug is not None and debug.lower() in ("1", "true", "yes")

        #
        # Parse TimeZone query arg
        #
        tzid = queryValue("tzid")
        if not tzid:
            tzid = getLocalTimezone()
            self.log.debug("Determined timezone to be {tzid}", tzid=tzid)

        #
        # Make some HTML
        #
        try:
            htmlContent = self.htmlContent(debug) % {
                "tzid": tzid,
                "principalURL": authenticatedPrincipalURL,
            }
        except IOError, e:
            self.log.error("Unable to obtain WebCalendar template: %s" % (e,))
            return responsecode.NOT_FOUND

        response = Response()
        response.stream = MemoryStream(htmlContent)

        for (header, value) in (
            ("content-type", self.contentType()),
            ("content-encoding", self.contentEncoding()),
        ):
            if value is not None:
                response.headers.setHeader(header, value)

        return response


try:
    from osx.utils import CFTimeZoneRef

    def lookupSystemTimezone():
        try:
            return CFTimeZoneRef.defaultTimeZoneName()
        except:
            return ""

except ImportError:
    def lookupSystemTimezone():
        return ""


def getLocalTimezone():
    """
    Returns the default timezone for the server.  The order of precedence is:
    config.DefaultTimezone, lookupSystemTimezone( ), DEFAULT_TIMEZONE.
    Also, if neither of the first two values in that list are in the timezone
    database, DEFAULT_TIMEZONE is returned.

    @return: The server's local timezone name
    @rtype: C{str}
    """
    if config.DefaultTimezone:
        if hasTZ(config.DefaultTimezone):
            return config.DefaultTimezone

    systemTimezone = lookupSystemTimezone()
    if hasTZ(systemTimezone):
        return systemTimezone

    return DEFAULT_TIMEZONE
