import sys
import os
from datetime import datetime
from sqlalchemy import *
import logging

from sqlalchemy.databases.mysql import MSEnum as Enum
from sqlalchemy.ext.assignmapper import assign_mapper

##############################################################################
# This was originally a TG app, so I'm duplicating TGs SA support:
import sqlalchemy
from sqlalchemy.ext import activemapper, sessioncontext

_engine = None
def get_engine():
    "Retreives the engine based on the current configuration"
    global _engine
    if not _engine:
        dburi = 'sqlite:///:memory:'
        _engine = sqlalchemy.create_engine(dburi)
        metadata.connect(_engine)
    elif not metadata.is_bound():
        metadata.connect(_engine)
    return _engine

def create_session():
    "Creates a session with the appropriate engine"
    return sqlalchemy.create_session(bind_to=get_engine())

metadata = activemapper.metadata
session = activemapper.Objectstore(create_session)
activemapper.objectstore = session

def bind_meta_data():
    get_engine()

# End of TG's SA support
##############################################################################

##############################################################################
# Table definitions
user_id_type = String(32)

counter = Table(
    'counter', metadata,
    Column('entity_type', String(10), primary_key=True),
    Column('release_line_id', Integer,
           ForeignKey('release_line.id'),
           primary_key=True,
           nullable=False),
    Column('value', Integer, PassiveDefault('0'),
           nullable=False),
    Column('version', Integer, PassiveDefault('0'),
           nullable=False)
    )

branch = Table(
    'branch', metadata,
    Column('id', Integer, primary_key=True),
    Column('name', String(50), unique=True),
    Column('change_origin_id', Integer,
           ForeignKey('change_origin.id'),
           nullable=False),
    Column('change_reference', String(32)),
    Column('user_id', user_id_type,
           ForeignKey('user.id'), index=True,
           nullable=False),
    Column('status', Enum('Development',
                          'In QCP',
                          'Passed QCP',
                          'Merged',
                          'Closed',
                          'Obsolete'),
           index=True,
           nullable=False,
           default='Development'),
    Column('description', String),
    Column('release_line_id', Integer,
           ForeignKey('release_line.id'),
           nullable=False),
    Column('date', DateTime, PassiveDefault(func.current_timestamp())),
    )

branch_label = Table(
    'branch_label', metadata,
    Column('branch_id', Integer,
           ForeignKey('branch.id'),
           primary_key=True),
    Column('label_id', Integer,
           ForeignKey('label.id'),
           primary_key=True),
    )

change_origin = Table(
    'change_origin', metadata,
    Column('id', Integer,
           primary_key=True),
    Column('name', String(32)),
    )

component = Table(
    'component', metadata,
    Column('id', Integer, primary_key=True),
    Column('name', String(32), unique=True),
    Column('description', String),
    Column('code', String(8), unique=True),
    Column('part_number', String(18)),
    Column('active', Boolean),
    )

component_link = Table(
    'component_link', metadata,
    Column('component_id', Integer,
           ForeignKey('component.id'),
           primary_key=True),
    Column('subcomponent_id', Integer,
           ForeignKey('subcomponent.id'),
           primary_key=True),
    )


label = Table(
    'label', metadata,
    Column('id', Integer, primary_key=True),
    Column('name', String(50), unique=True),
    Column('description', String),
    Column('config_spec', String),
    Column('status', Enum('Created', 'Applied', 'Obsolete'),
           nullable=False, index=True, default='Created'),
    Column('release_line_id', Integer,
           ForeignKey('release_line.id'),
           nullable=False),
    Column('label_type', Enum('baseline', 'release'),
           nullable=False, index=True, default='baseline'),
    Column('user_id', user_id_type, ForeignKey('user.id'),
           nullable=False),
    )

release_line = Table(
    'release_line', metadata,
    Column('id', Integer, primary_key=True),
    Column('name', String(32)),
    Column('description', String),
    Column('branch_point_label_id', Integer,
           ForeignKey('label.id', use_alter=True,
                      name='branch_point_label_constraint')),
    Column('component_id', Integer,
           ForeignKey('component.id'),
           nullable=False),
    )

subcomponent = Table(
    'subcomponent', metadata,
    Column('id', Integer,
           primary_key=True),
    Column('vob_id', Integer,
           ForeignKey('vob.id'),
           nullable=False),
    Column('path', String(255)),
    )

user = Table(
    'user', metadata,
    Column('id', user_id_type, primary_key=True),
    Column('display_name', String(32), unique=True),
    Column('unix_id', String(32), unique=True),
    Column('email', String(64)),
    Column('active', Boolean),
    Column('default_component_id', Integer,
           ForeignKey('component.id')),
    Column('friendly_name', String(32), unique=True),
    )

vob = Table(
    'vob', metadata,
    Column('id', Integer, primary_key=True),
    Column('path', String(32), unique=True),
    )


# End of table definitions
##############################################################################

##############################################################################
# Mapped classes

class Entity(object):
    _str_attr = 'name'
    _repr_attr = 'name'
    def __init__(self, **kwargs):
        for name in kwargs:
            if not hasattr(self, name):
                raise AttributeError('%s has no such attribute: %s' %
                                     (type(self).__name__, name))
            setattr(self, name, kwargs[name])

    def __str__(self):
        return str(getattr(self, self._str_attr, '?'))

    def __repr__(self):
        return "<%s %s>" % (type(self).__name__,
                            getattr(self, self._repr_attr, '?'))


class Counter(object):
    def get_next_value(self):
        self.value += 1
        return self.value
assign_mapper(session.context, Counter, counter,
              version_id_col=counter.c.version)


class User(Entity):
    _repr_attr = _str_attr = 'display_name'
assign_mapper(session.context, User, user)


class Branch(Entity):
    pass
assign_mapper(session.context, Branch, branch,
              properties=dict(user=relation(User)))

class ChangeOrigin(Entity):
    pass
assign_mapper(session.context, ChangeOrigin, change_origin)
class_mapper(Branch).add_property('change_origin', relation(ChangeOrigin))

class Component(Entity):
    def __init__(self, subcomponents=None, **kwargs):
        super(Component, self).__init__(**kwargs)
        if subcomponents:
            self.add_subcomponents(*subcomponents)

        # Create a 'main' release line
        main = ReleaseLine(name='main',
                           component=self)

    def add_subcomponents(self, *subcomponents):
        self.subcomponents.extend(subcomponents)
            

    def get_main(self):
        return self.release_lines[0]
    main = property(get_main)

assign_mapper(session.context, Component, component)


class Label(Entity):
    pass
assign_mapper(session.context, Label, label,
              properties=dict(user=relation(User),
                              branches=relation(Branch,
                                                secondary=branch_label,
                                                backref='labels')))

class ReleaseLine(Entity):
    def __init__(self, **kwargs):
        super(ReleaseLine, self).__init__(**kwargs)
        # Create counters for branches and labels
        self.branch_counter = Counter(release_line=self, entity_type='branch')
        self.label_counter = Counter(release_line=self, entity_type='label')
            
    def create_branch(self, **kwargs):
        number = self.branch_counter.get_next_value()
        name = 'dev-%s-%d' % (self.component.code.lower(), number)
        branch = Branch(release_line=self,
                        name=name,
                        **kwargs)
        return branch

    def create_baseline_label(self, **kwargs):
        number = self.label_counter.get_next_value()
        name = 'B%d-%s-%s' % (number,
                              self.component.code.upper(),
                              datetime.today().strftime('%Y%b%d').upper())
        label = Label(release_line=self,
                      name=name,
                      **kwargs)
        return label

assign_mapper(
    session.context, ReleaseLine, release_line,
    properties=dict(
        component=relation(
            Component,
            backref='release_lines'
            ),
        branch_point=relation(
            Label,
            primaryjoin=release_line.c.branch_point_label_id==label.c.id
            )
        )
    )
for kind in ['label', 'branch']:
    condition = and_(counter.c.release_line_id == release_line.c.id,
                     counter.c.entity_type == kind)
    class_mapper(ReleaseLine).add_property('%s_counter' % kind,
                                           relation(Counter,
                                                    primaryjoin=condition,
                                                    uselist=False))
del kind, condition
class_mapper(Branch).add_property(
    'release_line',
    relation(ReleaseLine,
             primaryjoin=branch.c.release_line_id==release_line.c.id,
             remote_side=release_line.c.id,
             foreign_keys=[branch.c.release_line_id],
             uselist=False,
             )
    )
class_mapper(Counter).add_property('release_line', relation(ReleaseLine))
class_mapper(Label).add_property(
    'release_line',
    relation(ReleaseLine,
             primaryjoin=Label.c.release_line_id==ReleaseLine.c.id,
             remote_side=release_line.c.id,
             foreign_keys=[label.c.release_line_id],
             )
    )

class Vob(Entity):
    pass
assign_mapper(session.context, Vob, vob)

class Subcomponent(Entity):
    _repr_attr = _str_attr = 'full_path'
    def get_full_path(self):
        return os.path.join(self.vob.path, self.path)
    full_path = property(get_full_path)
assign_mapper(
    session.context,
    Subcomponent,
    subcomponent,
    properties=dict(components=relation(Component,
                                        secondary=component_link,
                                        backref='subcomponents'),
                    vob=relation(Vob, backref='directories'))
    )


##############################################################################
# Create some initial values in the database
##############################################################################
def initialise_database():
    change_origins = ['Defect',
                      'Problem Report',
                      'Feature Request',
                      'System Development']
    for name in change_origins:
        co = ChangeOrigin.get_by(name=name)
        if not co:
            co = ChangeOrigin(name=name)
            co.flush()


def test():
    # Initialisation
    get_engine()
    metadata.create_all()
    initialise_database()
    u = User(id='nfhd78',
             display_name='Simon King',
             friendly_name='simonk',
             unix_id='nfhd78')
    testvob = Vob(path='/vobs/testvob')
    sc = Subcomponent(path='', vob=testvob)
    c = Component(subcomponents=[sc], name='Testvob', code='TST')
    session.flush()
    session.close()

    # Now try and create a couple of branches
    logging.basicConfig(stream=sys.stdout)
    c = Component.get_by(name='Testvob')
    co = ChangeOrigin.get_by(name='System Development')
    u = User.get('nfhd78')
    b1 = c.main.create_branch(change_origin=co, user=u)
    b2 = c.main.create_branch(change_origin=co, user=u)
    logging.getLogger('sqlalchemy.orm').setLevel(logging.DEBUG)
    try:
        session.flush()
    finally:
        logging.getLogger('sqlalchemy.orm').setLevel(logging.ERROR)
    assert b1.name == 'dev-tst-1'
    assert b2.name == 'dev-tst-2'
    print "Finished OK"
    
if __name__ == '__main__':
    test()
