# -*- coding: utf-8 -*-
##----------------------------------------------------------------------
## Load and syncronized built-in FM classes, rules and MIBs
##----------------------------------------------------------------------
## Copyright (C) 2007-2009 The NOC Project
## See LICENSE for details
##----------------------------------------------------------------------
"""
"""
from __future__ import with_statement
from django.core.management.base import BaseCommand
from django.db import transaction
from noc.fm.rules.classes import EventClass
from noc.fm.rules.classification import ClassificationRule
from noc.fm.models import MIB,MIBRequiredException,EventClassificationRule,EventCorrelationRule
import os,re
##
## patterns
##
rx_comment=re.compile(r"#+(.*)\s*$")
##
## Command handler
##
class Command(BaseCommand):
    help="Syncronize built-in FM classes and rules"
    def handle(self, *args, **options):
        transaction.enter_transaction_management()
        self.sync_mibs()
        self.sync_classes()
        self.sync_classification_rules()
        self.sync_correlation_rules()
        transaction.leave_transaction_management()
    ##
    ## Search for subclasses of givent class inside given directory
    ##
    def search(self,cls,d):
        classes={}
        for dirpath,dirnames,filenames in os.walk(d):
            mb="noc.fm."+".".join(dirpath.split(os.sep)[1:])+"."
            for f in [f for f in filenames if f.endswith(".py") and f!="__init__.py"]:
                m=__import__(mb+f[:-3],{},{},"*")
                for ec_name in dir(m):
                    ec=getattr(m,ec_name)
                    try:
                        if not issubclass(ec,cls) or ec==cls:
                            continue
                    except:
                        continue
                    classes[ec]=None
        return classes.keys()
    ##
    ## Syncronize event classes
    ##
    def sync_classes(self):
        for c in self.search(EventClass,"fm/rules/classes"):
            c.sync()
    ##
    ## Syncronize event classification rules
    ##
    def sync_classification_rules(self):
        # Make built-in rules list
        loaded_rules={}
        for r in EventClassificationRule.objects.filter(is_builtin=True):
            loaded_rules[r.name]=None
        # Sync new rules
        for c in self.search(ClassificationRule,"fm/rules/classification"):
            c.sync()
            if c.name in loaded_rules:
                del loaded_rules[c.name]
        # Delete stale rules
        for r in loaded_rules:
            rule=EventClassificationRule.objects.get(name=r)
            rule.delete()
            print "DELETE CLASSIFICATION RULE %s"%r
    ##
    ## Syncronize event correlation rules
    ##
    def sync_correlation_rules(self):
        # Generator returning rules in file
        def get_rules(path):
            pkg="_".join(path.split(os.path.sep)[3:])[:-4]
            with open(path) as f:
                description=[]
                body=[]
                name=None
                for l in f.readlines():
                    l=l[:-1].replace("\t","    ")
                    if l.startswith(" "):
                        body+=[l[4:]]
                        continue
                    else:
                        if body and name:
                            yield {
                                "name"        : name,
                                "body"        : "\n".join(body),
                                "description" : "\n".join(description)
                            }
                            name=None
                            body=[]
                            description=[]
                        if l.startswith("#"):
                            match=rx_comment.match(l)
                            if match:
                                description+=[match.group(1)]
                            continue
                        elif l.strip()=="":
                            description=[]
                            continue
                        else:
                            name=pkg+"_"+l.strip()
                            continue
                if body and name:
                    yield {
                        "name"        : name,
                        "body"        : "\n".join(body),
                        "description" : "\n".join(description)
                    }
        loaded_rules={}
        for r in EventCorrelationRule.objects.filter(is_builtin=True):
            loaded_rules[r.name]=None
        # Retrieve rules
        for dirpath,dirnames,filenames in os.walk(os.path.join("fm","rules","correlation")):
            for f in [f for f in filenames if f.endswith(".krb")]:
                for r in get_rules(os.path.join(dirpath,f)):
                    try:
                        rule=EventCorrelationRule.objects.get(name=r["name"])
                        rule.rule=r["body"]
                        rule.description=r["description"]
                        rule.is_builtin=True
                        print "UPDATE CORRELATION RULE %s"%r["name"]
                    except EventCorrelationRule.DoesNotExist:
                        rule=EventCorrelationRule(name=r["name"],rule=r["body"],description=r["description"],is_builtin=True)
                        print "CREATE CORRELATION RULE %s"%r["name"]
                    rule.save()
                    if r["name"] in loaded_rules:
                        del loaded_rules[r["name"]]
        # Delete stale rules
        for r in loaded_rules:
            rule=EventCorrelationRule.objects.get(name=r)
            rule.delete()
            print "DELETE CORRELATION RULE %s"%r
    ##
    ## Syncronize built-in MIBs
    ##
    def sync_mibs(self):
        # Loaded MIBs cache
        loaded_mibs={}
        for m in MIB.objects.all():
            loaded_mibs[m.name]=None
        # Enumerate local stored MIBs
        prefix=os.path.join("share","mibs")
        new_mibs={}
        for m in os.listdir(prefix):
            mib_name,ext=os.path.splitext(m)
            if mib_name not in loaded_mibs:
                # Try to upload new MIB
                try:
                    MIB.load(os.path.join(prefix,m))
                    loaded_mibs[mib_name]=None
                    print "CREATE MIB %s"%mib_name
                except MIBRequiredException,x:
                    new_mibs[mib_name]=x.requires_mib
        # Try to load new MIBs
        while new_mibs:
            l_new_mibs=len(new_mibs)
            for mib_name,requires_mib in new_mibs.items():
                if requires_mib in loaded_mibs:
                    try:
                        MIB.load(os.path.join(prefix,mib_name+".mib"))
                        loaded_mibs[mib_name]=None
                        print "CREATE MIB %s"%mib_name
                        del new_mibs[mib_name]
                    except MIBRequiredException,x:
                        new_mibs[mib_name]=x.requires_mib
            if len(new_mibs)==l_new_mibs: # No new MIBs loaded
                raise Exception("Following builtin MIBs cannot be loaded: %s"%" ".join(new_mibs.keys()))
