u'''
Created on Oct 3, 2010

@author: Mark V Systems Limited
(c) Copyright 2010 Mark V Systems Limited, All rights reserved.
'''
import gc, sys, traceback, logging
from arelle import ModelXbrl, Validate, DisclosureSystem
from arelle.PluginManager import pluginClassMethods

def initialize(cntlr):
    modelManager = ModelManager(cntlr)
    modelManager.modelXbrl = None
    return modelManager
    
class ModelManager(object):
    u"""ModelManager provides an interface between modelXbrl's and the controller.  Model manager is a 
    singleton object, one is created in initialization of a controller.
    
    The ModelManager coordinates ModelXbrl instances for the Controller, and is the interface to utility 
    functions (such as the Python web cache), and application specific formalisms (such as the SEC 
    restrictions on referencable base taxonomies).

        .. attribute:: validateDisclosureSystem
    
        True if disclosure system is to be validated (e.g., EFM)
        
        .. attribute:: disclosureSystem
        
        Disclosure system object.  To select the disclosure system, e.g., 'gfm', moduleManager.disclosureSystem.select('gfm').
          
        .. attribute:: validateCalcLB
        
        True for calculation linkbase validation.
        
        .. attribute:: validateInferDecimals
        
        True for calculation linkbase validation to infer decimals (instead of precision)
        
        .. attribute:: validateUTR
        
        True for validation of unit type registry
        
        .. attribute:: defaultLang
        
        The default language code for labels selection and views (e.g. 'en-US'), set from the operating system defaults on startup.
    """
    
    def __init__(self, cntlr):
        self.cntlr = cntlr
        self.validateDisclosureSystem = False
        self.disclosureSystem = DisclosureSystem.DisclosureSystem(self)
        self.validateCalcLB = False
        self.validateInferDecimals = False
        self.validateInfoset = False
        self.validateUtr = False
        self.skipDTS = False
        self.skipLoading = None
        self.abortOnMajorError = False
        self.collectProfileStats = False
        self.loadedModelXbrls = []
        from arelle import Locale
        self.locale = Locale.getUserLocale(cntlr.config.get(u"userInterfaceLocaleOverride",u""))
        self.defaultLang = Locale.getLanguageCode()

    def shutdown(self):
        self.status = u"shutdown"
        
    def addToLog(self, message, messageCode=u"", file=u"", level=logging.INFO):
        u"""Add a simple info message to the default logger
           
        :param message: Text of message to add to log.
        :type message: str
        :param messageCode: Message code (e.g., a prefix:id of a standard error)
        :param messageCode: str
        :param file: File name (and optional line numbers) pertaining to message
        :type file: str
        """
        self.cntlr.addToLog(message, messageCode=messageCode, file=file, level=level)
        
    def showStatus(self, message, clearAfter=None):
        u"""Provide user feedback on status line of GUI or web page according to type of controller.
        
        :param message: Message to display on status widget.
        :type message: str
        :param clearAfter: Time, in ms., after which to clear the message (e.g., 5000 for 5 sec.)
        :type clearAfter: int
        """
        self.cntlr.showStatus(message, clearAfter)
        
    def viewModelObject(self, modelXbrl, objectId):
        u"""Notify any active views to show and highlight selected object.  Generally used
        to scroll list control to object and highlight it, or if tree control, to find the object
        and open tree branches as needed for visibility, scroll to and highlight the object.
           
        :param modelXbrl: ModelXbrl (DTS) whose views are to be notified
        :type modelXbrl: ModelXbrl
        :param objectId: Selected object id (string format corresponding to ModelObject.objectId() )
        :type objectId: str
        """
        self.cntlr.viewModelObject(modelXbrl, objectId)
        
    def reloadViews(self, modelXbrl):
        u"""Notify all active views to reload and redisplay their entire contents.  May be used
        when loaded model is changed significantly, or when individual object change notifications
        (by viewModelObject) would be difficult to identify or too numerous.
           
        :param modelXbrl: ModelXbrl (DTS) whose views are to be reloaded
        :type modelXbrl: ModelXbrl
        """
        self.cntlr.reloadViews(modelXbrl)
        
    def load(self, filesource, nextaction=None):
        u"""Load an entry point modelDocument object(s), which in turn load documents they discover 
        (for the case of instance, taxonomies, and versioning reports), but defer loading instances 
        for test case and RSS feeds.  
        
        The modelXbrl that is loaded is 'stacked', by this class, so that any modelXbrl operations such as validate, 
        and close, operate on the most recently loaded modelXbrl, and compareDTSes operates on the two 
        most recently loaded modelXbrl's.
        
        :param filesource: may be a FileSource object, with the entry point selected, or string file name (or web URL). 
        :type filesource: FileSource or str
        :param nextAction: status line text string, if any, to show upon completion
        :type nextAction: str
        """
        try:
            if filesource.url.startswith(u"urn:uuid:"): # request for an open modelXbrl
                for modelXbrl in self.loadedModelXbrls:
                    if not modelXbrl.isClosed and modelXbrl.uuid == filesource.url:
                        return modelXbrl
                raise IOError(_(u"Open file handle is not open: {0}").format(filesource.url))
        except AttributeError:
            pass # filesource may be a string, which has no url attribute
        self.filesource = filesource
        modelXbrl = None # loaded modelXbrl
        for customLoader in pluginClassMethods(u"ModelManager.Load"):
            modelXbrl = customLoader(self, filesource)
            if modelXbrl is not None:
                break # custom loader did the loading
        if modelXbrl is None:  # use default xbrl loader
            modelXbrl = ModelXbrl.load(self, filesource, nextaction)
        self.modelXbrl = modelXbrl
        self.loadedModelXbrls.append(self.modelXbrl)
        return self.modelXbrl
    
    def saveDTSpackage(self, allDTSes=False):
        if allDTSes:
            for modelXbrl in self.loadedModelXbrls:
                modelXbrl.saveDTSpackage()
        elif self.modelXbrl is not None:
            self.modelXbrl.saveDTSpackage()
    
    def create(self, newDocumentType=None, url=None, schemaRefs=None, createModelDocument=True, isEntry=False, errorCaptureLevel=None, initialXml=None, base=None):
        self.modelXbrl = ModelXbrl.create(self, newDocumentType, url, schemaRefs, createModelDocument, isEntry, errorCaptureLevel, initialXml, base)
        self.loadedModelXbrls.append(self.modelXbrl)
        return self.modelXbrl
    
    def validate(self):
        u"""Validates the most recently loaded modelXbrl (according to validation properties).
        
        Results of validation will be in log.
        """
        try:
            if self.modelXbrl:
                Validate.validate(self.modelXbrl)
        except Exception, err:
            self.addToLog(_(u"[exception] Validation exception: {0} at {1}").format(
                           err,
                           traceback.format_tb(sys.exc_info()[2])))
        
    def compareDTSes(self, versReportFile, writeReportFile=True):
        u"""Compare two most recently loaded DTSes, saving versioning report in to the file name provided.
        
        :param versReportFile: file name in which to save XBRL Versioning Report
        :type versReportFile: str
        :param writeReportFile: False to prevent writing XBRL Versioning Report file
        :type writeReportFile: bool
        """
        from arelle.ModelVersReport import ModelVersReport
        if len(self.loadedModelXbrls) >= 2:
            fromDTS = self.loadedModelXbrls[-2]
            toDTS = self.loadedModelXbrls[-1]
            from arelle.ModelDocument import Type
            modelVersReport = self.create(newDocumentType=Type.VERSIONINGREPORT,
                                          url=versReportFile,
                                          createModelDocument=False)
            ModelVersReport(modelVersReport).diffDTSes(versReportFile, fromDTS, toDTS)
            return modelVersReport
        return None
        
    def close(self, modelXbrl=None):
        u"""Closes the specified or most recently loaded modelXbrl
        
        :param modelXbrl: Specific ModelXbrl to be closed (defaults to last opened ModelXbrl)
        :type modelXbrl: ModelXbrl
        """
        if modelXbrl is None: modelXbrl = self.modelXbrl
        if modelXbrl:
            while modelXbrl in self.loadedModelXbrls:
                self.loadedModelXbrls.remove(modelXbrl)
            if (modelXbrl == self.modelXbrl): # dereference modelXbrl from this instance
                if len(self.loadedModelXbrls) > 0:
                    self.modelXbrl = self.loadedModelXbrls[0]
                else:
                    self.modelXbrl = None
            modelXbrl.close()
            gc.collect()

