#!/usr/bin/env python

import re

class Stage:
    UNKNOWN_STAGE = 'unknown'
    STAGES = dict()
    def __init__(self, name, str_patterns):
        self.name = name
        self.str_patterns = str_patterns
        self.patterns = [ ]
        for pattern in str_patterns:
            self.patterns.append( re.compile(pattern) )        
    @staticmethod
    def add_stage(stage):
        Stage.STAGES[stage.name] = stage
    @staticmethod
    def get_stage(filename):
        for key, stage in Stage.STAGES.iteritems():
            for pattern in stage.patterns:
                if pattern.match(filename):
                    return stage.name
        return Stage.UNKNOWN_STAGE

class Role:
    UNKNOWN_ROLE = 'unknown'
    ROLES = dict()
    def __init__(self, name, str_patterns):
        self.name = name
        self.str_patterns = str_patterns
        self.patterns = [ ]
        for pattern in str_patterns:
            self.patterns.append( re.compile(pattern) )
    @staticmethod
    def add_role(role):
        Role.ROLES[role.name] = role
    @staticmethod
    def get_role(filename):
        default_role = Role.UNKNOWN_ROLE
        for key, role in Role.ROLES.iteritems():
            for pattern in role.patterns:
                if pattern.match(filename):
                    if role.name != 'fits':
                        return role.name
                    else:
                        default_role = role.name # i.e. 'fits' but there might be a 'better' match
        return default_role

class Lifetime:
    UNKNOWN_LIFETIME = 'unknown'
    LIFETIMES = dict()
    def __init__(self, stage, role, duration):
        if isinstance(stage, Stage):
            _stage = stage
        else:
            _stage = Stage.STAGES[stage]
        if isinstance(role, Role):
            _role = role
        else:
            _role = Role.ROLES[role]
        if duration not in ['permanent', 'raw', 'ephemeral']:
            raise Exception("Lifetime duration must be one of: permanent', 'raw', 'ephemeral'")
        self.stage = _stage
        self.role = _role
        self.duration = duration
    @staticmethod
    def add_lifetime(lifetime):
        try:
            subdict = Lifetime.LIFETIMES[lifetime.stage.name]
            subdict[lifetime.role.name] = lifetime
        except KeyError:
            Lifetime.LIFETIMES[lifetime.stage.name] = dict()
            Lifetime.add_lifetime(lifetime)

def initialize_IppTemplates():
    # Define the stages (see http://docs.python.org/library/re.html)
    # that will match the different stages
    Stage.add_stage(Stage( 'cam',
                           [ '^.*\.cm\..*$', ] ))
    Stage.add_stage(Stage( 'chip',
                           [ '^.*\.ch\..*$', ] ))
    Stage.add_stage(Stage( 'destreak',
                           [ '^.*destreak.*$', ] ))
    Stage.add_stage(Stage( 'detrend',
                           [ '^.*\.det.*$', ] ))
    Stage.add_stage(Stage( 'diff',
                           [ '^.*\.dif\..*$', ] ))
    Stage.add_stage(Stage( 'dist', 
                           [ '^.*dist.*$', ] ))
    Stage.add_stage(Stage( 'magic',
                           [ '^.*\.mgc\..*$', ] ))
    Stage.add_stage(Stage( 'raw',
                           [ '^.*\.ota\d\d\..*$', 
                             '^.*\.chip01\..*$', ] ))
    Stage.add_stage(Stage( 'stack',
                           [ '^.*\.stk\..*$', ] ))
    Stage.add_stage(Stage( 'warp',
                           [ '^.*\.wrp\..*$', ] ))
    Stage.add_stage(Stage( 'skycal',
                           [ '^.*\.skycal\..*$', ] ))
    Stage.add_stage(Stage( 'staticsky',
                           [ '^.*\.sky\..*$', ] ))

    #################### Roles and associated lifetimes
    Role.add_role(Role( 'b1fits',
                        [ '^.*\.b1\.fits$', ] ))
    Lifetime.add_lifetime( Lifetime('chip',    'b1fits', 'permanent') )
    Lifetime.add_lifetime( Lifetime('detrend', 'b1fits', 'permanent') )
    Lifetime.add_lifetime( Lifetime('diff',    'b1fits', 'permanent') )
    Lifetime.add_lifetime( Lifetime('stack',   'b1fits', 'permanent') )
    Lifetime.add_lifetime( Lifetime('warp',    'b1fits', 'permanent') )

    Role.add_role(Role( 'b1jpg',
                        [ '^.*\.b1\.jpeg$', 
                          '^.*\.b1\.jpg$', ] ))
    Lifetime.add_lifetime( Lifetime('chip',    'b1jpg', 'permanent') )
    Lifetime.add_lifetime( Lifetime('detrend', 'b1jpg', 'permanent') )
    Lifetime.add_lifetime( Lifetime('diff',    'b1jpg', 'permanent') )
    Lifetime.add_lifetime( Lifetime('stack',   'b1jpg', 'permanent') )
    Lifetime.add_lifetime( Lifetime('warp',    'b1jpg', 'permanent') )

    Role.add_role(Role( 'b2fits',
                        [ '^.*\.b2\.fits$', ] ))
    Lifetime.add_lifetime( Lifetime('chip',    'b2fits', 'permanent') )
    Lifetime.add_lifetime( Lifetime('detrend', 'b2fits', 'permanent') )
    Lifetime.add_lifetime( Lifetime('diff',    'b2fits', 'permanent') )
    Lifetime.add_lifetime( Lifetime('stack',   'b2fits', 'permanent') )
    Lifetime.add_lifetime( Lifetime('warp',    'b2fits', 'permanent') )

    Role.add_role(Role( 'b2jpg',
                        [ '^.*\.b2\.jpeg$',
                          '^.*\.b2\.jpg$', ] ))
    Lifetime.add_lifetime( Lifetime('cam',     'b2jpg', 'permanent') )
    Lifetime.add_lifetime( Lifetime('detrend', 'b2jpg', 'permanent') )
    Lifetime.add_lifetime( Lifetime('diff',    'b2jpg', 'permanent') )
    Lifetime.add_lifetime( Lifetime('stack',   'b2jpg', 'permanent') )
    Lifetime.add_lifetime( Lifetime('warp',    'b2jpg', 'permanent') )

    Role.add_role(Role( 'bttable',
                        [ '^.*\.burn\.tbl$', ] ))
    Lifetime.add_lifetime( Lifetime('raw', 'bttable', 'raw') )

    Role.add_role(Role( 'bundle',
                        [ '^.*\.tgz$', ] ))
    Lifetime.add_lifetime( Lifetime('cam',   'bundle', 'ephemeral') )
    Lifetime.add_lifetime( Lifetime('chip',  'bundle', 'ephemeral') )
    Lifetime.add_lifetime( Lifetime('diff',  'bundle', 'ephemeral') )
    Lifetime.add_lifetime( Lifetime('dist',  'bundle', 'ephemeral') )
    Lifetime.add_lifetime( Lifetime('raw',   'bundle', 'ephemeral') )
    Lifetime.add_lifetime( Lifetime('stack', 'bundle', 'ephemeral') )
    Lifetime.add_lifetime( Lifetime('warp',  'bundle', 'ephemeral') )

    Role.add_role(Role( 'catalog',
                        [ '^.*\.smf$',
                          '^.*\.cmf$', ] ))
    Lifetime.add_lifetime( Lifetime( 'cam',      'catalog', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'chip',     'catalog', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'destreak', 'catalog', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'diff',     'catalog', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'stack',    'catalog', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'warp',     'catalog', 'permanent') )

    Role.add_role(Role( 'compfits',
                        [ '^.*\.fits\.fz$', ] ))
    Lifetime.add_lifetime( Lifetime( 'raw', 'compfits', 'raw') )

    Role.add_role(Role( 'kernel',
                        [ '^.*kernel$', ] ))
    Lifetime.add_lifetime( Lifetime( 'diff', 'kernel', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'stack', 'kernel', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'warp', 'kernel', 'permanent') )

    Role.add_role(Role( 'log',
                        [ '^.*\.log$', ] ))
    Lifetime.add_lifetime( Lifetime( 'cam',      'log', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'chip',     'log', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'destreak', 'log', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'detrend',  'log', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'diff',     'log', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'dist',     'log', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'raw',      'log', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'stack',    'log', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'warp',     'log', 'permanent') )

    Role.add_role(Role( 'logupdate',
                        [ '^.*\.log\.update$', ] ))
    Lifetime.add_lifetime( Lifetime( 'cam',      'logupdate', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'chip',     'logupdate', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'destreak', 'logupdate', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'detrend',  'logupdate', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'diff',     'logupdate', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'dist',     'logupdate', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'raw',      'logupdate', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'stack',    'logupdate', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'warp',     'logupdate', 'permanent') )

    Role.add_role(Role( 'mask',
                        [ '^.*\.mk\.fits$',
                          '^.*\.mask\.fits$', ] ))
    Lifetime.add_lifetime( Lifetime( 'cam',      'mask', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'chip',     'mask', 'ephemeral') )
    Lifetime.add_lifetime( Lifetime( 'destreak', 'mask', 'ephemeral') )
    Lifetime.add_lifetime( Lifetime( 'diff',     'mask', 'ephemeral') )
    Lifetime.add_lifetime( Lifetime( 'stack',    'mask', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'warp',     'mask', 'ephemeral') )

    Role.add_role(Role( 'mdc',
                        [ '^.*\.mdc$', ] ))
    Lifetime.add_lifetime( Lifetime( 'cam',      'mdc', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'chip',     'mdc', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'destreak', 'mdc', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'diff',     'mdc', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'dist',     'mdc', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'stack',     'mdc', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'warp',     'mdc', 'permanent') )

    Role.add_role(Role( 'mdl',
                        [ '^.*\.mdl\.fits$', ] ))
    Lifetime.add_lifetime( Lifetime( 'chip',     'mdl', 'ephemeral') )

    Role.add_role(Role( 'miscfits',
                        [ '^.*\.exp\.fits$',
                          '^.*\.num\.fits$',
                          '^.*\.expwt\.fits$', ] ))
    Lifetime.add_lifetime( Lifetime( 'stack', 'miscfits', 'permanent') )

    Role.add_role(Role( 'png',
                        [ '^.*\.png$', ] ))
    Lifetime.add_lifetime( Lifetime( 'chip',  'png', 'ephemeral') )
    Lifetime.add_lifetime( Lifetime( 'magic', 'png', 'ephemeral') )

    Role.add_role(Role( 'psf',
                        [ '^.*\.psf$', ] ))
    Lifetime.add_lifetime( Lifetime( 'chip',   'psf', 'ephemeral') )
    Lifetime.add_lifetime( Lifetime( 'diff',   'psf', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'stack',  'psf', 'ephemeral') )
    Lifetime.add_lifetime( Lifetime( 'warp',  'psf', 'ephemeral') )

    Role.add_role(Role( 'ptn',
                        [ '^.*\.ptn$', ] ))
    Lifetime.add_lifetime( Lifetime( 'chip',   'ptn', 'ephemeral') )

    Role.add_role(Role( 'skycell',
                        [ '^.*\.skycell$', ] ))
    Lifetime.add_lifetime( Lifetime( 'warp',   'skycell', 'ephemeral') )

    Role.add_role(Role( 'stats',
                        [ '^.*\.stats$', ] ))
    Lifetime.add_lifetime( Lifetime( 'cam',      'stats', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'chip',     'stats', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'destreak', 'stats', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'detrend',  'stats', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'diff',     'stats', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'magic',    'stats', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'raw',      'stats', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'stack',    'stats', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'warp',     'stats', 'permanent') )

    Role.add_role(Role( 'trace',
                        [ '^.*\.trace$', ] ))
    Lifetime.add_lifetime( Lifetime( 'cam',      'trace', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'chip',     'trace', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'destreak', 'trace', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'detrend',  'trace', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'diff',     'trace', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'raw',      'trace', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'stack',    'trace', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'warp',     'trace', 'permanent') )

    Role.add_role(Role( 'weight',
                        [ '^.*\.wt\.fits$', ] ))
    Lifetime.add_lifetime( Lifetime( 'chip',     'weight', 'ephemeral') )
    Lifetime.add_lifetime( Lifetime( 'destreak', 'weight', 'ephemeral') )
    Lifetime.add_lifetime( Lifetime( 'diff',     'weight', 'ephemeral') )
    Lifetime.add_lifetime( Lifetime( 'stack',    'weight', 'ephemeral') )
    Lifetime.add_lifetime( Lifetime( 'warp',     'weight', 'ephemeral') )
    
    Role.add_role(Role( 'fits',
                        [ '^.*\.fits$', ] ))
    Lifetime.add_lifetime( Lifetime( 'cam',      'fits', 'ephemeral') )
    Lifetime.add_lifetime( Lifetime( 'chip',     'fits', 'ephemeral') )
    Lifetime.add_lifetime( Lifetime( 'destreak', 'fits', 'ephemeral') )
    Lifetime.add_lifetime( Lifetime( 'detrend',  'fits', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'diff',     'fits', 'ephemeral') )
    Lifetime.add_lifetime( Lifetime( 'magic',    'fits', 'ephemeral') )
    Lifetime.add_lifetime( Lifetime( 'raw',      'fits', 'raw') )
    Lifetime.add_lifetime( Lifetime( 'stack',    'fits', 'permanent') )
    Lifetime.add_lifetime( Lifetime( 'warp',     'fits', 'ephemeral') )

######################################################################################
#
# DON'T TOUCH ANYTHING AFTER THIS LINE UNLESS YOU KNOW WHAT YOU'RE DOING
#
######################################################################################
class Template:
    """
    >>> print Template.classify('1110322.gpc1:20080610:o4627g0006o.17394:o4627g0006o.17394.reg.ota03.log')
    ('raw', 'log', 'permanent')
    >>> print Template.classify('165757062.gpc1:SweetSpot.nt:2010:02:22:o5249g0554o.139304:SR_o5249g0554o.139304.cm.49578.XY03.mk.fits')
    ('cam', 'mask', 'permanent')
    >>> print Template.classify('165578038.gpc1:ThreePi.nt:2010:02:23:o5250g0408o.139800:o5250g0408o.139800.ch.62962.XY54.stats')
    ('chip', 'stats', 'permanent')
    >>> print Template.classify('173285968.gpc1:IPP-MOPS:IPP-MOPS.15546.neg.mops')
    ('unknown', 'unknown', 'unknown')
    >>> print Template.classify('191293758.gpc1:20100308:o5263g0051f.145823:o5263g0051f.145823.reg.log')
    ('unknown', 'log', 'unknown')
    >>> print Template.classify('216601960.gpc1:ThreePi.nt:2010:02:22:o5249g0213o.138965:o5249g0213o.138965.wrp.34664.skycell.2038.080.trace.update')
    ('warp', 'unknown', 'unknown')
    >>> print Template.classify('500266344.gpc1:20101010:o5479g0217o.237556:o5479g0217o.237556.mgc.87689.original.png')
    ('magic', 'png', 'ephemeral')
    """
    initialize_IppTemplates()

    def __init__(self):
        pass

    @staticmethod
    def classify(filename):
        # We have subclasses of FITS files: try to detect that first
        stage = Stage.get_stage(filename)
        role = Role.get_role(filename)
        try:
            return (stage, role, Lifetime.LIFETIMES[stage][role].duration)
        except KeyError:
            return (stage, role, Lifetime.UNKNOWN_LIFETIME)

    
def usage(progname):
    return """
<usage>: %s [test|wiki|<filename>]

  Provides the IPP files classification, that is (stage, role, lifetime duration) where:
    * stage is the stage name when the file has been generated (chip, cam...)
    * role is the role of the file (fits, log, mask...)
    * its lifetime duration (raw, permanent, ephemeral)
    Any undefined stage, role, or lifetime duration will be displayed as unknown.

  The complete list of stages is:
    %s

  The complete list of roles is:
    %s

Arguments:

   test: Run the unit tests

   wiki: Generate the table for the "Cluster Storage Notes" IPP wikipage 
         See http://svn.pan-starrs.ifa.hawaii.edu/trac/ipp/wiki/Cluster_Storage_Notes

   <filename>: Try to classify the <filename>, e.g.:
         %s 218164796.gpc1:20100327:o5282g0065o.151926:o5282g0065o.151926.reg.log
       should display:
         ('unknown', 'log', 'unknown')

""" % (progname, Stage.STAGES.keys(), Role.ROLES.keys(), progname)

def generate_wiki_documentation(date, command_line, cwd):
    # We start with an empty cell and display the stage names
    wiki = """This table was generated on %s by the command {{{%s}}} from {{{%s}}}. Do not update it manually.

Raw (R), Ephemeral (E), Should not be made (-), and Permanent Products (P) Files.
""" % (date, command_line, cwd)
    wiki = "%s\n%s" % (wiki, "|| || Stage ")
    stage_patterns_line = "|| Role || Pattern "
    for stage in sorted(Stage.STAGES.keys()):
        wiki = "%s || %10s" % (wiki, stage)
        wiki_patterns = ('%s' % (Stage.STAGES[stage].str_patterns))
        stage_patterns_line = "%s || {{{%10s}}}" % (stage_patterns_line, wiki_patterns)
    wiki = "%s||\n%s||\n" % (wiki, stage_patterns_line)
    # Each line contains: the role name, then the role patterns, and then,
    # for each stage, the lifetime duration
    for role in sorted(Role.ROLES.keys()):
        wiki = "%s|| %10s" % (wiki, role)
        wiki = "%s|| {{{%10s}}}" % (wiki, Role.ROLES[role].str_patterns)
        for stage in sorted(Stage.STAGES.keys()):
            try:
                wiki = "%s || %s" % (wiki, Lifetime.LIFETIMES[stage][role].duration[0].upper())
            except KeyError:
                wiki = "%s || -" % (wiki)
        wiki = "%s ||\n" % wiki
    print wiki

if __name__ == '__main__':
    import sys
    if len(sys.argv) != 2:
        sys.stderr.write(usage(sys.argv[0]))
        sys.exit(1)
    if sys.argv[1] == 'test':
        import doctest
        doctest.testmod()
    elif sys.argv[1] == 'wiki':
        import datetime
        import os
        generate_wiki_documentation(str(datetime.datetime.now()),
                                    " ".join(sys.argv),
                                    os.getcwd())
    else:
        print Template.classify(sys.argv[1])
