# -*- coding: utf-8 -*-

# Copyright 2010-2011 OpenStack Foundation
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# 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.

import logging
import pcre
import re


LOG = logging.getLogger()


class Rule(object):
    "Base class for rules."

    def __init__(self, linenum, *params):
        self.linenum = linenum
        self._params = params
        if len(params) == 4:
            # redirect code pattern target
            self.code = params[1]
            self.pattern = params[2]
            self.target = params[3]
        elif len(params) == 3:
            if params[1] == '410':
                # The page has been deleted and is not coming back.
                self.code = params[1]
                self.pattern = params[2]
                self.target = None
            else:
                # redirect pattern target
                # (code is implied)
                self.code = '301'
                self.pattern = params[1]
                self.target = params[2]
        else:
            raise ValueError('Could not understand rule {}'.format(params))

    def __str__(self):
        return '[{}] {}'.format(
            self.linenum,
            ' '.join(p for p in self._params if p),
        )

    def match(self, path):
        raise NotImplementedError('Base class does not implement match()')


class Redirect(Rule):
    "A Redirect rule."

    def match(self, path):
        if path == self.pattern:
            return (self.code, self.target)
        return None


class RedirectMatch(Rule):
    "A RedirectMatch rule with a regular expression."

    _group_subst = re.compile(r'(?<!\\)\$([0-9])')

    def __init__(self, linenum, *params):
        super(RedirectMatch, self).__init__(linenum, *params)
        self.regex = pcre.compile(self.pattern)
        if self.target:
            self.target_repl = self._get_target_repl()
        else:
            self.target_repl = None

    def _get_target_repl(self):
        escaped = pcre.escape_template(self.target)
        return self._group_subst.sub(r'{\1}', escaped).replace(r'\$', '$')

    def match(self, path):
        m = self.regex.search(path)
        if m:
            if self.target_repl:
                return (self.code, self.regex.sub(self.target_repl, path))
            else:
                # A rule that doesn't have a response target, like 410.
                return (self.code, self.target_repl)
        return None


class RuleSet(object):
    "An ordered collection of rules."

    _factories = {
        'redirect': Redirect,
        'redirectmatch': RedirectMatch,
    }

    def __init__(self):
        self._rules = []
        self._by_num = {}

    def add(self, linenum, *params):
        rule_type = params[0].lower()
        rule = self._factories[rule_type](linenum, *params)
        self._rules.append(rule)
        self._by_num[linenum] = rule

    def __getitem__(self, index):
        return self._by_num[index]

    def __iter__(self):
        return iter(self._rules)

    @property
    def all_ids(self):
        return list(self._by_num.keys())

    def match(self, path):
        for rule in self:
            try:
                m = rule.match(path)
            except Exception as e:
                LOG.warning('Failed to evaluate {} against {}: {}'.format(
                    rule, path, e))
            else:
                if m is not None:
                    LOG.debug(
                        'Matched "{}" for path "{}" producing {}'.format(
                            rule, path, m))
                    return (rule.linenum,) + m
