# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (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.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is Mozilla-specific Buildbot steps.
#
# The Initial Developer of the Original Code is
# Mozilla Foundation.
# Portions created by the Initial Developer are Copyright (C) 2009
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#   Brian Warner <warner@lothar.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****

from zope.interface import implements
from twisted.application import service

from buildbot import interfaces
from buildbot.process.properties import Properties
from buildbot.util import ComparableMixin, NotABranch
from buildbot.schedulers import filter

class _None:
    pass

class BaseScheduler(service.MultiService, ComparableMixin):
    implements(interfaces.IScheduler)
    # subclasses must set .compare_attrs

    upstream_name = None # set to be notified about upstream buildsets

    def __init__(self, name, builderNames, properties):
        service.MultiService.__init__(self)
        self.name = name
        self.properties = Properties()
        self.properties.update(properties, "Scheduler")
        self.properties.setProperty("scheduler", name, "Scheduler")
        errmsg = ("The builderNames= argument to Scheduler must be a list "
                  "of Builder description names (i.e. the 'name' key of the "
                  "Builder specification dictionary)")
        assert isinstance(builderNames, (list, tuple)), errmsg
        for b in builderNames:
            assert isinstance(b, str), errmsg
        self.builderNames = builderNames
        # I will acquire a .schedulerid value before I'm started

    def compareToOther(self, them):
        # like ComparableMixin.__cmp__, but only used by our manager
        # TODO: why?? why not use __cmp__?
        result = cmp(type(self), type(them))
        if result:
            return result
        result = cmp(self.__class__, them.__class__)
        if result:
            return result
        assert self.compare_attrs == them.compare_attrs
        self_list = [getattr(self, name, _None) for name in self.compare_attrs]
        them_list = [getattr(them, name, _None) for name in self.compare_attrs]
        return cmp(self_list, them_list)

    def get_initial_state(self, max_changeid):
        # override this if you pay attention to Changes, probably to:
        #return {"last_processed": max_changeid}
        return {}

    def get_state(self, t):
        return self.parent.db.scheduler_get_state(self.schedulerid, t)
    
    def set_state(self, t, state):
        self.parent.db.scheduler_set_state(self.schedulerid, t, state)

    def listBuilderNames(self):
        return self.builderNames

    def getPendingBuildTimes(self):
        return []

    def create_buildset(self, ssid, reason, t, props=None, builderNames=None):
        db = self.parent.db
        if props is None:
            props = self.properties
        if builderNames is None:
            builderNames = self.builderNames
        bsid = db.create_buildset(ssid, reason, props, builderNames, t)
        # notify downstream schedulers so they can watch for it to complete
        self.parent.publish_buildset(self.name, bsid, t)
        return bsid

class ClassifierMixin:
    """
    Mixin to classify changes using self.change_filter, a filter.ChangeFilter instance.
    """

    def make_filter(self, change_filter=None, branch=NotABranch, categories=None):
        if change_filter:
            if (branch is not NotABranch or categories is not None):
                raise RuntimeError("cannot specify both change_filter and either branch or categories")
            self.change_filter = change_filter
            return

        # build a change filter from the deprecated category and branch args
        cfargs = {}
        if branch is not NotABranch: cfargs['branch'] = branch
        if categories: cfargs['category'] = categories
        self.change_filter = filter.ChangeFilter(**cfargs)

    def classify_changes(self, t):
        db = self.parent.db
        cm = self.parent.change_svc
        state = self.get_state(t)
        state_changed = False
        last_processed = state.get("last_processed", None)

        if last_processed is None:
            last_processed = state['last_processed'] = cm.getLatestChangeNumberNow(t)
            state_changed = True

        changes = cm.getChangesGreaterThan(last_processed, t)
        for c in changes:
            if self.change_filter.filter_change(c):
                important = True
                if self.fileIsImportant:
                    important = self.fileIsImportant(c)
                db.scheduler_classify_change(self.schedulerid, c.number,
                                             bool(important), t)
        # now that we've recorded a decision about each, we can update the
        # last_processed record
        if changes:
            max_changeid = max([c.number for c in changes])
            state["last_processed"] = max_changeid # retain other keys
            state_changed = True

        if state_changed:
            self.set_state(t, state)
