# Copyright The IETF Trust 2007, All Rights Reserved

import os.path
import datetime
import re

from django.conf import settings
from django.db import models
from ietf.utils import FKAsOneToOne
from ietf.utils.broken_foreign_key import BrokenForeignKey
from ietf.utils.cached_lookup_field import CachedLookupField
from ietf.utils.admin import admin_link

class Acronym(models.Model):
    INDIVIDUAL_SUBMITTER = 1027
    
    acronym_id = models.AutoField(primary_key=True)
    acronym = models.CharField(max_length=12)
    name = models.CharField(max_length=100)
    name_key = models.CharField(max_length=50, editable=False)
    def save(self):
        self.name_key = self.name.upper()
	super(Acronym, self).save()
    def __str__(self):
        return self.acronym
    class Meta:
        db_table = "acronym"

class AreaStatus(models.Model):
    status_id = models.AutoField(primary_key=True)
    status = models.CharField(max_length=25, db_column='status_value')
    def __str__(self):
	return self.status
    class Meta:
        verbose_name = "Area Status"
        verbose_name_plural = "Area Statuses"
        db_table = 'area_status'

# I think equiv_group_flag is historical.
class IDState(models.Model):
    PUBLICATION_REQUESTED = 10
    LAST_CALL_REQUESTED = 15
    IN_LAST_CALL = 16
    WAITING_FOR_WRITEUP = 18
    WAITING_FOR_AD_GO_AHEAD = 19
    IESG_EVALUATION = 20
    IESG_EVALUATION_DEFER = 21
    APPROVED_ANNOUNCEMENT_SENT = 30
    AD_WATCHING = 42
    DEAD = 99
    DO_NOT_PUBLISH_STATES = (33, 34)
    
    document_state_id = models.AutoField(primary_key=True)
    state = models.CharField(max_length=50, db_column='document_state_val')
    equiv_group_flag = models.IntegerField(null=True, blank=True)
    description = models.TextField(blank=True, db_column='document_desc')
    def __str__(self):
        return self.state
    def choices():
	return [(state.document_state_id, state.state) for state in IDState.objects.all()]
    choices = staticmethod(choices)
    class Meta:
        db_table = 'ref_doc_states_new'
	ordering = ['document_state_id']

class IDNextState(models.Model):
    cur_state = models.ForeignKey(IDState, related_name='nextstate')
    next_state = models.ForeignKey(IDState, related_name='prevstate')
    condition = models.CharField(blank=True, max_length=255)
    def __str__(self):
	return "%s -> %s" % (self.cur_state.state, self.next_state.state )
    class Meta:
        db_table = 'ref_next_states_new'

class IDSubState(models.Model):
    sub_state_id = models.AutoField(primary_key=True)
    sub_state = models.CharField(max_length=55, db_column='sub_state_val')
    description = models.TextField(blank=True, db_column='sub_state_desc')
    def __str__(self):
        return self.sub_state
    class Meta:
        db_table = 'sub_state'
	ordering = ['sub_state_id']

class Area(models.Model):
    ACTIVE=1
    area_acronym = models.OneToOneField(Acronym, primary_key=True)
    start_date = models.DateField(auto_now_add=True)
    concluded_date = models.DateField(null=True, blank=True)
    status = models.ForeignKey(AreaStatus)
    comments = models.TextField(blank=True)
    last_modified_date = models.DateField(auto_now=True)
    extra_email_addresses = models.TextField(blank=True,null=True)
    def __str__(self):
	return self.area_acronym.acronym
    def additional_urls(self):
        return AreaWGURL.objects.filter(name=self.area_acronym.name)
    def active_wgs(self):
        return IETFWG.objects.filter(group_type=1,status=IETFWG.ACTIVE,areagroup__area=self).order_by('group_acronym__acronym')
    def active_areas():
        return Area.objects.filter(status=Area.ACTIVE).order_by('area_acronym__acronym')
    active_areas = staticmethod(active_areas)
    class Meta:
        db_table = 'areas'
	verbose_name="area"

class AreaWGURL(models.Model):
    id = models.AutoField(primary_key=True, db_column='area_ID')
    # For WGs, this is the WG acronym; for areas, it's the area name.
    name = models.CharField(max_length=50, db_column='area_Name')
    url = models.CharField(max_length=50)
    description = models.CharField(max_length=50)
    def __unicode__(self):
        return u'%s (%s)' % (self.name, self.description)
    class Meta:
        ordering = ['name']
        verbose_name = "Area/WG URL"
        db_table = "wg_www_pages"

class IDStatus(models.Model):
    status_id = models.AutoField(primary_key=True)
    status = models.CharField(max_length=25, db_column='status_value')
    def __str__(self):
        return self.status
    class Meta:
        db_table = "id_status"
	verbose_name="I-D Status"
	verbose_name_plural="I-D Statuses"

class IDIntendedStatus(models.Model):
    intended_status_id = models.AutoField(primary_key=True)
    intended_status = models.CharField(max_length=25, db_column='status_value')
    def __str__(self):
        return self.intended_status
    class Meta:
        db_table = "id_intended_status"
	verbose_name="I-D Intended Publication Status"
	verbose_name_plural="I-D Intended Publication Statuses"

class InternetDraft(models.Model):
    DAYS_TO_EXPIRE=185
    id_document_tag = models.AutoField(primary_key=True)
    title = models.CharField(max_length=255, db_column='id_document_name')
    id_document_key = models.CharField(max_length=255, editable=False)
    group = models.ForeignKey(Acronym, db_column='group_acronym_id')
    filename = models.CharField(max_length=255, unique=True)
    revision = models.CharField(max_length=2)
    revision_date = models.DateField()
    file_type = models.CharField(max_length=20)
    txt_page_count = models.IntegerField()
    local_path = models.CharField(max_length=255, blank=True, null=True)
    start_date = models.DateField()
    expiration_date = models.DateField(null=True)
    abstract = models.TextField()
    dunn_sent_date = models.DateField(null=True, blank=True)
    extension_date = models.DateField(null=True, blank=True)
    status = models.ForeignKey(IDStatus)
    intended_status = models.ForeignKey(IDIntendedStatus)
    lc_sent_date = models.DateField(null=True, blank=True)
    lc_changes = models.CharField(max_length=3,null=True)
    lc_expiration_date = models.DateField(null=True, blank=True)
    b_sent_date = models.DateField(null=True, blank=True)
    b_discussion_date = models.DateField(null=True, blank=True)
    b_approve_date = models.DateField(null=True, blank=True)
    wgreturn_date = models.DateField(null=True, blank=True)
    rfc_number = models.IntegerField(null=True, blank=True, db_index=True)
    comments = models.TextField(blank=True,null=True)
    last_modified_date = models.DateField()
    replaced_by = BrokenForeignKey('self', db_column='replaced_by', blank=True, null=True, related_name='replaces_set')
    replaces = FKAsOneToOne('replaces', reverse=True)
    review_by_rfc_editor = models.BooleanField()
    expired_tombstone = models.BooleanField()
    idinternal = FKAsOneToOne('idinternal', reverse=True, query=models.Q(rfc_flag = 0))
    shepherd = BrokenForeignKey('PersonOrOrgInfo', null=True, blank=True, null_values=(0, ))
    def __str__(self):
        return self.filename
    def save(self, *args, **kwargs):
        self.id_document_key = self.title.upper()
        super(InternetDraft, self).save(*args, **kwargs)
    def displayname(self):
        return self.filename
    def file_tag(self):
        return "<%s>" % (self.filename_with_rev())
    def filename_with_rev(self):
        return "%s-%s.txt" % (self.filename, self.revision_display())
    def name(self):
        # small hack to make model forward-compatible with new schema
        return self.filename
    def group_acronym(self):
	return self.group.acronym
    def group_ml_archive(self):
	return self.group.ietfwg.clean_email_archive()
    def idstate(self):
	idinternal = self.idinternal
	if idinternal:
	    return idinternal.docstate()
	else:
	    return "I-D Exists"
    def revision_display(self):
	r = int(self.revision)
	if self.status.status != 'Active' and not self.expired_tombstone:
	   r = max(r - 1, 0)
	return "%02d" % r
    def expiration(self):
        return self.revision_date + datetime.timedelta(self.DAYS_TO_EXPIRE)
    def can_expire(self):
        # Copying the logic from expire-ids-1 without thinking
        # much about it.
        if self.review_by_rfc_editor:
            return False
        idinternal = self.idinternal
        if idinternal:
            cur_state_id = idinternal.cur_state_id
            # 42 is "AD is Watching"; this matches what's in the
            # expire-ids-1 perl script.
            # A better way might be to add a column to the table
            # saying whether or not a document is prevented from
            # expiring.
            if cur_state_id < 42:
                return False
        return True

    def clean_abstract(self):
        # Cleaning based on what "id-abstracts-text" script does
        a = self.abstract
        a = re.sub(" *\r\n *", "\n", a)  # get rid of DOS line endings
        a = re.sub(" *\r *", "\n", a)  # get rid of MAC line endings
        a = re.sub("(\n *){3,}", "\n\n", a)  # get rid of excessive vertical whitespace
        a = re.sub("\f[\n ]*[^\n]*\n", "", a)  # get rid of page headers
        # Get rid of 'key words' boilerplate and anything which follows it:
        # (No way that is part of the abstract...)
        a = re.sub("(?s)(Conventions [Uu]sed in this [Dd]ocument|Requirements [Ll]anguage)?[\n ]*The key words \"MUST\", \"MUST NOT\",.*$", "", a)
        # Get rid of status/copyright boilerplate
        a = re.sub("(?s)\nStatus of [tT]his Memo\n.*$", "", a)
        # wrap long lines without messing up formatting of Ok paragraphs:
        while re.match("([^\n]{72,}?) +", a):
            a = re.sub("([^\n]{72,}?) +([^\n ]*)(\n|$)", "\\1\n\\2 ", a)
        # Remove leading and trailing whitespace
        a = a.strip()
        return a 

    class Meta:
        db_table = "internet_drafts"
        
class PersonOrOrgInfo(models.Model):
    person_or_org_tag = models.AutoField(primary_key=True)
    record_type = models.CharField(blank=True, null=True, max_length=8)
    name_prefix = models.CharField(blank=True, null=True, max_length=10)
    first_name = models.CharField(blank=True, max_length=20)
    first_name_key = models.CharField(blank=True, max_length=20, editable=False)
    middle_initial = models.CharField(blank=True, null=True, max_length=4)
    middle_initial_key = models.CharField(blank=True, null=True, max_length=4, editable=False)
    last_name = models.CharField(blank=True, max_length=50)
    last_name_key = models.CharField(blank=True, max_length=50, editable=False)
    name_suffix = models.CharField(blank=True, null=True, max_length=10)
    date_modified = models.DateField(null=True, blank=True, auto_now=True)
    modified_by = models.CharField(blank=True, null=True, max_length=8)
    date_created = models.DateField(auto_now_add=True, null=True)
    created_by = models.CharField(blank=True, null=True, max_length=8)
    address_type = models.CharField(blank=True, null=True, max_length=4)
    def save(self, **kwargs):
        self.first_name_key = self.first_name.upper()
        self.middle_initial_key = self.middle_initial.upper()
        self.last_name_key = self.last_name.upper()
        super(PersonOrOrgInfo, self).save(**kwargs)
    def __str__(self):
        # For django.VERSION 0.96
	if self.first_name == '' and self.last_name == '':
	    return "(Person #%s)" % self.person_or_org_tag
        return "%s %s" % ( self.first_name or "<nofirst>", self.last_name or "<nolast>")
    def __unicode__(self):
        # For django.VERSION 1.x
	if self.first_name == '' and self.last_name == '':
	    return u"(Person #%s)" % self.person_or_org_tag
        return u"%s %s" % ( self.first_name or u"<nofirst>", self.last_name or u"<nolast>")
    def email(self, priority=1, type=None):
        name = unicode(self)
        email = ''
        addresses = self.emailaddress_set.filter(address__contains="@").order_by('priority')
        if addresses:
            email = addresses[0].address
            for a in addresses:
                if a.priority == priority:
                    email = a.address
	return (name, email)
    # Added by Sunny Lee to display person's affiliation - 5/26/2007
    def affiliation(self, priority=1):
        try:
            postal = self.postaladdress_set.get(address_priority=priority)
        except PostalAddress.DoesNotExist:
            return "PersonOrOrgInfo with no postal address!"
        except AssertionError:
            return "PersonOrOrgInfo with multiple priority-%d addresses!" % priority
        return "%s" % ( postal.affiliated_company or postal.department or "???" )
    def full_name_as_key(self):
        return self.first_name.lower() + "." + self.last_name.lower()
    class Meta:
        db_table = 'person_or_org_info'
        ordering = ['last_name']
	verbose_name="Rolodex Entry"
	verbose_name_plural="Rolodex"

# could use a mapping for user_level
class IESGLogin(models.Model):
    SECRETARIAT_LEVEL = 0
    AD_LEVEL = 1
    INACTIVE_AD_LEVEL = 2
    
    USER_LEVEL_CHOICES = (
	(SECRETARIAT_LEVEL, 'Secretariat'),
	(AD_LEVEL, 'IESG'),
	(INACTIVE_AD_LEVEL, 'ex-IESG'),
	(3, 'Level 3'),
	(4, 'Comment Only(?)'),
    )
    id = models.AutoField(primary_key=True)
    login_name = models.CharField(blank=True, max_length=255)
    password = models.CharField(max_length=25)
    user_level = models.IntegerField(choices=USER_LEVEL_CHOICES)
    first_name = models.CharField(blank=True, max_length=25)
    last_name = models.CharField(blank=True, max_length=25)
    # this could be a OneToOneField but the unique constraint is violated in the data (for person_or_org_tag=188)
    person = BrokenForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag', unique=True, null_values=(0, 888888), null=True)
    pgp_id = models.CharField(blank=True, null=True, max_length=20)
    default_search = models.NullBooleanField()
    def __str__(self):
        #return "%s, %s" % ( self.last_name, self.first_name)
        return "%s %s" % ( self.first_name, self.last_name)
    def is_current_ad(self):
	return self.user_level == 1
    def active_iesg():
	return IESGLogin.objects.filter(user_level=1,id__gt=1).order_by('last_name')	#XXX hardcoded
    active_iesg = staticmethod(active_iesg)
    class Meta:
        db_table = 'iesg_login'

class AreaDirector(models.Model):
    area = models.ForeignKey(Area, db_column='area_acronym_id', null=True)
    person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
    def __str__(self):
        return "%s (%s)" % ( self.person, self.role() )
    def role(self):
	try:
	    return "%s AD" % self.area
	except Area.DoesNotExist:
	    return "?%d? AD" % self.area_id
    class Meta:
        db_table = 'area_directors'


###
# RFC tables

class RfcIntendedStatus(models.Model):
    NONE=5
    intended_status_id = models.AutoField(primary_key=True)
    status = models.CharField(max_length=25, db_column='status_value')
    def __str__(self):
        return self.status
    class Meta:
        db_table = 'rfc_intend_status'
	verbose_name = 'RFC Intended Status Field'

class RfcStatus(models.Model):
    status_id = models.AutoField(primary_key=True)
    status = models.CharField(max_length=25, db_column='status_value')
    def __str__(self):
        return self.status
    class Meta:
        db_table = 'rfc_status'
	verbose_name = 'RFC Status'
	verbose_name_plural = 'RFC Statuses'

class Rfc(models.Model):
    ONLINE_CHOICES=(('YES', 'Yes'), ('NO', 'No'))
    rfc_number = models.IntegerField(primary_key=True)
    title = models.CharField(max_length=200, db_column='rfc_name')
    rfc_name_key = models.CharField(max_length=200, editable=False)
    group_acronym = models.CharField(blank=True, max_length=8)
    area_acronym = models.CharField(blank=True, max_length=8)
    status = models.ForeignKey(RfcStatus, db_column="status_id")
    intended_status = models.ForeignKey(RfcIntendedStatus, db_column="intended_status_id", default=RfcIntendedStatus.NONE)
    fyi_number = models.CharField(blank=True, max_length=20)
    std_number = models.CharField(blank=True, max_length=20)
    txt_page_count = models.IntegerField(null=True, blank=True)
    online_version = models.CharField(choices=ONLINE_CHOICES, max_length=3, default='YES')
    rfc_published_date = models.DateField(null=True, blank=True)
    proposed_date = models.DateField(null=True, blank=True)
    draft_date = models.DateField(null=True, blank=True)
    standard_date = models.DateField(null=True, blank=True)
    historic_date = models.DateField(null=True, blank=True)
    lc_sent_date = models.DateField(null=True, blank=True)
    lc_expiration_date = models.DateField(null=True, blank=True)
    b_sent_date = models.DateField(null=True, blank=True)
    b_approve_date = models.DateField(null=True, blank=True)
    comments = models.TextField(blank=True)
    last_modified_date = models.DateField()
    
    idinternal = CachedLookupField(lookup=lambda self: IDInternal.objects.get(draft=self.rfc_number, rfc_flag=1))
    group = CachedLookupField(lookup=lambda self: Acronym.objects.get(acronym=self.group_acronym))
    
    def __str__(self):
	return "RFC%04d" % ( self.rfc_number )        
    def save(self):
	self.rfc_name_key = self.title.upper()
	self.last_modified_date = datetime.date.today()
	super(Rfc, self).save()
    def displayname(self):
        return "%s.txt" % ( self.filename() )
    def filename(self):
	return "rfc%d" % ( self.rfc_number )
    def name(self):
        # small hack to make model forward-compatible with new schema
        return self.filename()
    def revision(self):
	return "RFC"
    def revision_display(self):
	return "RFC"
    def file_tag(self):
        return "RFC %s" % self.rfc_number

    # return set of RfcObsolete objects obsoleted or updated by this RFC
    def obsoletes(self): 
        return RfcObsolete.objects.filter(rfc=self.rfc_number)

    # return set of RfcObsolete objects obsoleting or updating this RFC
    def obsoleted_by(self): 
        return RfcObsolete.objects.filter(rfc_acted_on=self.rfc_number)

    class Meta:
        db_table = 'rfcs'
	verbose_name = 'RFC'
	verbose_name_plural = 'RFCs'

class RfcAuthor(models.Model):
    rfc = models.ForeignKey(Rfc, db_column='rfc_number', related_name='authors')
    person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
    def __str__(self):
        return "%s, %s" % ( self.person.last_name, self.person.first_name)
    class Meta:
        db_table = 'rfc_authors'
	verbose_name = 'RFC Author'

class RfcObsolete(models.Model):
    ACTION_CHOICES=(('Obsoletes', 'Obsoletes'), ('Updates', 'Updates'))
    rfc = models.ForeignKey(Rfc, db_column='rfc_number', related_name='updates_or_obsoletes')
    action = models.CharField(max_length=20, choices=ACTION_CHOICES)
    rfc_acted_on = models.ForeignKey(Rfc, db_column='rfc_acted_on', related_name='updated_or_obsoleted_by')
    def __str__(self):
        return "RFC%04d %s RFC%04d" % (self.rfc_id, self.action, self.rfc_acted_on_id)
    class Meta:
        db_table = 'rfcs_obsolete'
	verbose_name = 'RFC updates or obsoletes'
	verbose_name_plural = verbose_name

## End RFC Tables

class BallotInfo(models.Model):   # Added by Michael Lee
    ballot = models.AutoField(primary_key=True, db_column='ballot_id')
    active = models.BooleanField()
    an_sent = models.BooleanField()
    an_sent_date = models.DateField(null=True, blank=True)
    an_sent_by = models.ForeignKey(IESGLogin, db_column='an_sent_by', related_name='ansent', null=True)
    defer = models.BooleanField(blank=True)
    defer_by = models.ForeignKey(IESGLogin, db_column='defer_by', related_name='deferred', null=True)
    defer_date = models.DateField(null=True, blank=True)
    approval_text = models.TextField(blank=True)
    last_call_text = models.TextField(blank=True)
    ballot_writeup = models.TextField(blank=True)
    ballot_issued = models.IntegerField(null=True, blank=True)
    def __str__(self):
	try:
	    return "Ballot for %s" % self.drafts.get(primary_flag=1)
	except IDInternal.DoesNotExist:
	    return "Ballot ID %d (no I-D?)" % (self.ballot)
    def remarks(self):
        remarks = list(self.discusses.all()) + list(self.comments.all())
        return remarks
    def active_positions(self):
        '''Returns a list of dicts, with AD and Position tuples'''
	active_iesg = IESGLogin.active_iesg()
	ads = [ad.id for ad in active_iesg]
	positions = {}
	for position in self.positions.filter(ad__in=ads):
	    positions[position.ad_id] = position
	ret = []
	for ad in active_iesg:
	    ret.append({'ad': ad, 'pos': positions.get(ad.id, None)})
	return ret 
    def needed(self, standardsTrack=True):
	'''Returns text answering the question "what does this document
	need to pass?".  The return value is only useful if the document
	is currently in IESG evaluation.'''
	active_iesg = IESGLogin.active_iesg()
	ads = [ad.id for ad in active_iesg]
	yes = 0
	noobj = 0
	discuss = 0
	recuse = 0
	for position in self.positions.filter(ad__in=ads):
	    yes += 1 if position.yes > 0 else 0
	    noobj += 1 if position.noobj > 0 else 0
	    discuss += 1 if position.discuss > 0 else 0
	    recuse += 1 if position.recuse > 0 else 0
	answer = ''
	if yes < 1:
	    answer += "Needs a YES. "
	if discuss > 0:
	    if discuss == 1:
		answer += "Has a DISCUSS. "
	    else:
		answer += "Has %d DISCUSSes. " % discuss
	if standardsTrack:
	    # For standards-track, need positions from 2/3 of the
	    # non-recused current IESG.
	    needed = ( active_iesg.count() - recuse ) * 2 / 3
	else:
	    # Info and experimental only need one position.
	    needed = 1
	have = yes + noobj + discuss
	if have < needed:
            more = needed - have
            if more == 1:
                answer += "Needs %d more position. " % more
            else:
                answer += "Needs %d more positions. " % more
	else:
	    answer += "Has enough positions to pass"
	    if discuss:
		answer += " once DISCUSSes are resolved"
	    answer += ". "

	return answer.rstrip()

    class Meta:
        db_table = 'ballot_info'

def format_document_state(state, substate):
    if substate:
        return state.state + "::" + substate.sub_state
    else:
        return state.state

class IDInternal(models.Model):
    """
    An IDInternal represents a document that has been added to the
    I-D tracker.  It can be either an Internet Draft or an RFC.
    The table has only a single primary key field, meaning that
    there is the danger of RFC number collision with low-numbered
    Internet Drafts.

    Since it's most common to be an Internet Draft, the draft
    field is defined as a FK to InternetDrafts.  One side effect
    of this is that select_related() will only work with
    rfc_flag=0.

    When searching where matches may be either I-Ds or RFCs,
    you cannot use draft__ as that will cause an INNER JOIN
    which will limit the responses to I-Ds.
    """

    ACTIVE=1
    PUBLISHED=3
    EXPIRED=2
    WITHDRAWN_SUBMITTER=4
    REPLACED=5
    WITHDRAWN_IETF=6
    INACTIVE_STATES=[99,32,42]

    draft = models.ForeignKey(InternetDraft, primary_key=True, unique=True, db_column='id_document_tag')
    rfc_flag = models.IntegerField(null=True)
    ballot = models.ForeignKey(BallotInfo, related_name='drafts', db_column="ballot_id")
    primary_flag = models.IntegerField(blank=True, null=True)
    group_flag = models.IntegerField(blank=True, default=0)
    token_name = models.CharField(blank=True, max_length=25)
    token_email = models.CharField(blank=True, max_length=255)
    note = models.TextField(blank=True)
    status_date = models.DateField(blank=True,null=True)
    email_display = models.CharField(blank=True, max_length=50)
    agenda = models.IntegerField(null=True, blank=True)
    cur_state = models.ForeignKey(IDState, db_column='cur_state', related_name='docs')
    prev_state = models.ForeignKey(IDState, db_column='prev_state', related_name='docs_prev')
    assigned_to = models.CharField(blank=True, max_length=25)
    mark_by = models.ForeignKey(IESGLogin, db_column='mark_by', related_name='marked')
    job_owner = models.ForeignKey(IESGLogin, db_column='job_owner', related_name='documents')
    event_date = models.DateField(null=True)
    area_acronym = models.ForeignKey(Area)
    cur_sub_state = BrokenForeignKey(IDSubState, related_name='docs', null=True, blank=True, null_values=(0, -1))
    prev_sub_state = BrokenForeignKey(IDSubState, related_name='docs_prev', null=True, blank=True, null_values=(0, -1))
    returning_item = models.IntegerField(null=True, blank=True)
    telechat_date = models.DateField(null=True, blank=True)
    via_rfc_editor = models.IntegerField(null=True, blank=True)
    state_change_notice_to = models.CharField(blank=True, max_length=255)
    dnp = models.IntegerField(null=True, blank=True)
    dnp_date = models.DateField(null=True, blank=True)
    noproblem = models.IntegerField(null=True, blank=True)
    resurrect_requested_by = BrokenForeignKey(IESGLogin, db_column='resurrect_requested_by', related_name='docsresurrected', null=True, blank=True)
    approved_in_minute = models.IntegerField(null=True, blank=True)
    def __str__(self):
        if self.rfc_flag:
	    return "RFC%04d" % ( self.draft_id )
	else:
	    return self.draft.filename
    def get_absolute_url(self):
	if self.rfc_flag:
	    return "/doc/rfc%d/" % ( self.draft_id )
	else:
	    return "/doc/%s/" % ( self.draft.filename )
    _cached_rfc = None
    def document(self):
	if self.rfc_flag:
	    if self._cached_rfc is None:
		self._cached_rfc = Rfc.objects.get(rfc_number=self.draft_id)
	    return self._cached_rfc
	else:
	    return self.draft
    def public_comments(self):
	return self.comments().filter(public_flag=True)
    def comments(self):
	# would filter by rfc_flag but the database is broken. (see
	# trac ticket #96) so this risks collisions.
	# return self.documentcomment_set.all().order_by('-date','-time','-id')
        #
        # the obvious code above doesn't work with django.VERSION 1.0/1.1
        # because "draft" isn't a true foreign key (when rfc_flag=1 the
        # related InternetDraft object doesn't necessarily exist).
        return DocumentComment.objects.filter(document=self.draft_id).order_by('-date','-time','-id')
    def ballot_set(self):
	return IDInternal.objects.filter(ballot=self.ballot_id).order_by('-primary_flag')
    def ballot_primary(self):
	return IDInternal.objects.filter(ballot=self.ballot_id,primary_flag=1)
    def ballot_others(self):
	return IDInternal.objects.filter(models.Q(primary_flag=0)|models.Q(primary_flag__isnull=True), ballot=self.ballot_id)
    def docstate(self):
        return format_document_state(self.cur_state, self.cur_sub_state)
    def change_state(self, state, sub_state):
        self.prev_state = self.cur_state
        self.cur_state = state
        self.prev_sub_state_id = self.cur_sub_state_id
        self.cur_sub_state = sub_state        
    class Meta:
        db_table = 'id_internal'
	verbose_name = 'IDTracker Draft'
    def draft_link(self):
        try:
            if self.rfc_flag:
                return '<a href="http://tools.ietf.org/html/rfc%s">rfc%s</a>' % (self.draft.pk, self.draft.pk)
            else:
                return '<a href="http://tools.ietf.org/html/%s">%s</a>' % (self.draft.filename, self.draft.filename)
        except Exception:
            return ""
    draft_link.allow_tags = True
    def tracker_link(self):
        try:
            if self.rfc_flag:
                return '<a href="http://datatracker.ietf.org/doc/rfc%s">rfc%s</a>' % (self.draft.pk, self.draft.pk)
            else:
                return '<a href="http://datatracker.ietf.org/doc/%s">%s</a>' % (self.draft.filename, self.draft.filename)
        except Exception:
            return ""
    tracker_link.allow_tags = True

class DocumentComment(models.Model):
    BALLOT_DISCUSS = 1
    BALLOT_COMMENT = 2
    BALLOT_CHOICES = (
	(BALLOT_DISCUSS, 'discuss'),
	(BALLOT_COMMENT, 'comment'),
    )
    document = models.ForeignKey(IDInternal)
    # NOTE: This flag is often NULL, which complicates its correct use...
    rfc_flag = models.IntegerField(null=True, blank=True)
    public_flag = models.BooleanField()
    date = models.DateField(db_column='comment_date', default=datetime.date.today)
    time = models.CharField(db_column='comment_time', max_length=20, default=lambda: datetime.datetime.now().strftime("%H:%M:%S"))
    version = models.CharField(blank=True, max_length=3)
    comment_text = models.TextField(blank=True)
    # NOTE: This is not a true foreign key -- it sometimes has values 
    # (like 999) that do not exist in IESGLogin. So using select_related()
    # will break!    
    created_by = BrokenForeignKey(IESGLogin, db_column='created_by', null=True, null_values=(0, 999))
    result_state = BrokenForeignKey(IDState, db_column='result_state', null=True, related_name="comments_leading_to_state", null_values=(0, 99))
    origin_state = models.ForeignKey(IDState, db_column='origin_state', null=True, related_name="comments_coming_from_state")
    ballot = models.IntegerField(null=True, choices=BALLOT_CHOICES)
    def __str__(self):
        return "\"%s...\" by %s" % (self.comment_text[:20], self.get_author())
    def get_absolute_url(self):
	# use self.document.rfc_flag, since
	# self.rfc_flag is not always set properly.
	if self.document.rfc_flag:
	    return "/idtracker/rfc%d/comment/%d/" % (self.document_id, self.id)
	else:
	    return "/idtracker/%s/comment/%d/" % (self.document.draft.filename, self.id)
    def get_author(self):
	if self.created_by:
	    return str(self.created_by)
	else:
	    return "(System)"
    def get_username(self):
	if self.created_by:
	    return self.created_by.login_name
	else:
	    return "(System)"
    def get_fullname(self):
	if self.created_by:
	    return self.created_by.first_name + " " + self.created_by.last_name
	else:
	    return "(System)"
    def datetime(self):
	# this is just a straightforward combination, except that the time is
	# stored incorrectly in the database.
	return datetime.datetime.combine( self.date, datetime.time( * [int(s) for s in self.time.split(":")] ) )
    def doc_id(self):
        return self.document_id
    def doc_name(self):
        return self.document.draft.filename
    class Meta:
        db_table = 'document_comments'
        
class Position(models.Model):
    ballot = models.ForeignKey(BallotInfo, related_name='positions')
    ad = models.ForeignKey(IESGLogin)
    yes = models.IntegerField(db_column='yes_col')
    noobj = models.IntegerField(db_column='no_col')
    abstain = models.IntegerField()
    approve = models.IntegerField(default=0) # doesn't appear to be used anymore?
    discuss = models.IntegerField()
    recuse = models.IntegerField()
    def __str__(self):
	return "Position for %s on %s" % ( self.ad, self.ballot )
    def abstain_ind(self):
        if self.recuse:
            return 'R'
        if self.abstain:
            return 'X'
        else:
            return ' '
    def name(self):
      positions = {"yes":"Yes",
                   "noobj":"No Objection",
                   "discuss":"Discuss",
                   "abstain":"Abstain",
                   "recuse":"Recuse"}
      p = None
      for k,v in positions.iteritems():
          if self.__dict__[k] > 0:
              p = v
      if not p:
          p = "No Record"
      return p

    class Meta:
        db_table = 'ballots'
	unique_together = (('ballot', 'ad'), )
	verbose_name = "IESG Ballot Position"

class IESGComment(models.Model):
    ballot = models.ForeignKey(BallotInfo, related_name="comments")
    ad = models.ForeignKey(IESGLogin)
    date = models.DateField(db_column="comment_date")
    revision = models.CharField(max_length=2)
    active = models.IntegerField() # doesn't appear to be used
    text = models.TextField(blank=True, db_column="comment_text")
    def __str__(self):
	return "Comment text by %s on %s" % ( self.ad, self.ballot )
    def is_comment(self):
        return True
    class Meta:
        db_table = 'ballots_comment'
	unique_together = (('ballot', 'ad'), )
	verbose_name = 'IESG Comment Text'
	verbose_name_plural = 'IESG Comments'

class IESGDiscuss(models.Model):
    ballot = models.ForeignKey(BallotInfo, related_name="discusses")
    ad = models.ForeignKey(IESGLogin)
    date = models.DateField(db_column="discuss_date")
    revision = models.CharField(max_length=2)
    active = models.IntegerField()
    text = models.TextField(blank=True, db_column="discuss_text")
    def __str__(self):
	return "Discuss text by %s on %s" % ( self.ad, self.ballot )
    def is_discuss(self):
        return True
    class Meta:
        db_table = 'ballots_discuss'
	unique_together = (('ballot', 'ad'), )
	verbose_name = 'IESG Discuss Text'
	verbose_name_plural = 'IESG Discusses'

class IDAuthor(models.Model):
    document = models.ForeignKey(InternetDraft, db_column='id_document_tag', related_name='authors')
    person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
    author_order = models.IntegerField()
    def __str__(self):
	return "%s authors %s" % ( self.person, self.document.filename )
    def email(self):
        addresses = self.person.emailaddress_set.filter(type='I-D',priority=self.document_id)
        if len(addresses) == 0:
            return None
        else:
            return addresses[0].address
    def final_author_order(self):
        # Unfortunately, multiple authors for the same draft can have
        # the same value for author_order (although they should not).
        # Sort by person_id in that case to get a deterministic ordering.
        return "%08d%08d" % (self.author_order, self.person_id)
    class Meta:
        db_table = 'id_authors'
	verbose_name = "I-D Author"
        ordering = ['document','author_order']

# PostalAddress, EmailAddress and PhoneNumber are edited in
#  the admin for the Rolodex.
# The unique_together constraint is commented out for now, because
#  of a bug in oldforms and AutomaticManipulator which fails to
#  create the isUniquefoo_bar method properly.  Since django is
#  moving away from oldforms, I have to assume that this is going
#  to be fixed by moving admin to newforms.
# must decide which field is/are core.
class PostalAddress(models.Model):
    address_type = models.CharField(max_length=4)
    address_priority = models.IntegerField(null=True)
    person_or_org = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
    person_title = models.CharField(max_length=50, blank=True)
    affiliated_company = models.CharField(max_length=70, blank=True)
    aff_company_key = models.CharField(max_length=70, blank=True, editable=False)
    department = models.CharField(max_length=100, blank=True)
    staddr1 = models.CharField(max_length=40)
    staddr2 = models.CharField(max_length=40, blank=True)
    mail_stop = models.CharField(max_length=20, blank=True)
    city = models.CharField(max_length=20, blank=True)
    state_or_prov = models.CharField(max_length=20, blank=True)
    postal_code = models.CharField(max_length=20, blank=True)
    country = models.CharField(max_length=20, blank=True)
    def save(self):
	self.aff_company_key = self.affiliated_company.upper()
	super(PostalAddress, self).save()
    class Meta:
        db_table = 'postal_addresses'
	#unique_together = (('address_type', 'person_or_org'), )
	verbose_name_plural = 'Postal Addresses'

class EmailAddress(models.Model):
    person_or_org = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
    type = models.CharField(max_length=4, db_column='email_type')
    priority = models.IntegerField(db_column='email_priority')
    address = models.CharField(max_length=255, db_column='email_address')
    comment = models.CharField(blank=True, null=True, max_length=255, db_column='email_comment')
    def __str__(self):
	return self.address
    person_link = admin_link('person_or_org')
    def priority_link(self):
        if self.type=="I-D":
            return '<a href="/admin/idtracker/internetdraft/%s/">%s</a>' % (self.priority, self.priority)
        else:
            return self.priority
    priority_link.allow_tags = True
    class Meta:
        db_table = 'email_addresses'
	#unique_together = (('email_priority', 'person_or_org'), )
	# with this, I get 'ChangeManipulator' object has no attribute 'isUniqueemail_priority_person_or_org'
	verbose_name_plural = 'Email addresses'

class PhoneNumber(models.Model):
    person_or_org = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
    phone_type = models.CharField(max_length=3)
    phone_priority = models.IntegerField()
    phone_number = models.CharField(blank=True, max_length=255)
    phone_comment = models.CharField(blank=True, max_length=255)
    class Meta:
        db_table = 'phone_numbers'
	#unique_together = (('phone_priority', 'person_or_org'), )

### Working Groups

class WGType(models.Model):
    group_type_id = models.AutoField(primary_key=True)
    type = models.CharField(max_length=25, db_column='group_type')
    def __str__(self):
	return self.type
    class Meta:
        verbose_name = "WG Type"
        db_table = 'g_type'

class WGStatus(models.Model):
    status_id = models.AutoField(primary_key=True)
    status = models.CharField(max_length=25, db_column='status_value')
    def __str__(self):
	return self.status
    class Meta:
        verbose_name = "WG Status"
        verbose_name_plural = "WG Statuses"
        db_table = 'g_status'

class IETFWG(models.Model):
    ACTIVE = 1
    group_acronym = models.OneToOneField(Acronym, primary_key=True, editable=False)
    group_type = models.ForeignKey(WGType)
    proposed_date = models.DateField(null=True, blank=True)
    start_date = models.DateField(null=True, blank=True)
    dormant_date = models.DateField(null=True, blank=True)
    concluded_date = models.DateField(null=True, blank=True)
    status = models.ForeignKey(WGStatus)
    area_director = models.ForeignKey(AreaDirector, null=True)
    meeting_scheduled = models.CharField(blank=True, max_length=3)
    email_address = models.CharField(blank=True, max_length=60)
    email_subscribe = models.CharField(blank=True, max_length=120)
    email_keyword = models.CharField(blank=True, max_length=50)
    email_archive = models.CharField(blank=True, max_length=95)
    comments = models.TextField(blank=True)
    last_modified_date = models.DateField()
    meeting_scheduled_old = models.CharField(blank=True, max_length=3)
    area = FKAsOneToOne('areagroup', reverse=True)
    def __str__(self):
	return self.group_acronym.acronym
    def active_drafts(self):
	return self.group_acronym.internetdraft_set.all().filter(status__status="Active")
    def choices():
	return [(wg.group_acronym_id, wg.group_acronym.acronym) for wg in IETFWG.objects.all().filter(group_type__type='WG').select_related().order_by('acronym.acronym')]
    choices = staticmethod(choices)
    def area_acronym(self):
        areas = AreaGroup.objects.filter(group__exact=self.group_acronym)
        if areas:
            return areas[areas.count()-1].area.area_acronym
        else:
            return None
    def area_directors(self):
        areas = AreaGroup.objects.filter(group__exact=self.group_acronym)
        if areas:
            return areas[areas.count()-1].area.areadirector_set.all()
        else:
            return None
    def chairs(self): # return a set of WGChair objects for this work group
        return WGChair.objects.filter(group_acronym__exact=self.group_acronym)
    def secretaries(self): # return a set of WGSecretary objects for this group
        return WGSecretary.objects.filter(group_acronym__exact=self.group_acronym)
    def milestones(self): # return a set of GoalMilestone objects for this group
        return GoalMilestone.objects.filter(group_acronym__exact=self.group_acronym)
    def rfcs(self): # return a set of Rfc objects for this group
        return Rfc.objects.filter(group_acronym__exact=self.group_acronym)
    def drafts(self): # return a set of Rfc objects for this group
        return InternetDraft.objects.filter(group__exact=self.group_acronym)
    def charter_text(self): # return string containing WG description read from file
        # get file path from settings. Syntesize file name from path, acronym, and suffix
        try:
            filename = os.path.join(settings.IETFWG_DESCRIPTIONS_PATH, self.group_acronym.acronym) + ".desc.txt"
            desc_file = open(filename)
            desc = desc_file.read()
        except BaseException:    
            desc =  'Error Loading Work Group Description'
        return desc
    def additional_urls(self):
        return AreaWGURL.objects.filter(name=self.group_acronym.acronym)        
    def clean_email_archive(self):
        x = self.email_archive
        # remove "current/" and "maillist.html"
        x = re.sub("^(http://www\.ietf\.org/mail-archive/web/)([^/]+/)(current/)?([a-z]+\.html)?$", "\\1\\2", x)
        return x
    chairs_link = admin_link('chairs')
    class Meta:
        db_table = 'groups_ietf'
	ordering = ['?']	# workaround django wanting to sort by acronym but not joining with it
	verbose_name = 'IETF Working Group'

class WGChair(models.Model):
    person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
    group_acronym = models.ForeignKey(IETFWG)
    def __str__(self):
	return "%s (%s)" % ( self.person, self.role() )
    def role(self):
	return "%s %s Chair" % ( self.group_acronym, self.group_acronym.group_type )
    person_link = admin_link('person')
    group_link = admin_link('group_acronym')
    class Meta:
        db_table = 'g_chairs'
	verbose_name = "WG Chair"

class WGEditor(models.Model):
    group_acronym = models.ForeignKey(IETFWG)
    person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag', unique=True)
    def __str__(self):
	return "%s (%s)" % (self.person, self.role())
    def role(self):
	return "%s Editor" % self.group_acronym
    class Meta:
        db_table = 'g_editors'
	verbose_name = "WG Editor"

# Note: there is an empty table 'g_secretary'.
# This uses the 'g_secretaries' table but is called 'GSecretary' to
# match the model naming scheme.
class WGSecretary(models.Model):
    group_acronym = models.ForeignKey(IETFWG)
    person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
    def __str__(self):
	return "%s (%s)" % ( self.person, self.role() )
    def role(self):
	return "%s %s Secretary" % ( self.group_acronym, self.group_acronym.group_type )
    class Meta:
        db_table = 'g_secretaries'
	verbose_name = "WG Secretary"
	verbose_name_plural = "WG Secretaries"

class WGTechAdvisor(models.Model):
    group_acronym = models.ForeignKey(IETFWG)
    person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
    def __str__(self):
	return "%s (%s)" % ( self.person, self.role() )
    def role(self):
	return "%s Technical Advisor" % self.group_acronym
    class Meta:
        db_table = 'g_tech_advisors'
	verbose_name = "WG Technical Advisor"

class AreaGroup(models.Model):
    area = models.ForeignKey(Area, db_column='area_acronym_id', related_name='areagroup')
    group = models.ForeignKey(IETFWG, db_column='group_acronym_id', unique=True)
    def __str__(self):
	return "%s is in %s" % ( self.group, self.area )
    class Meta:
        db_table = 'area_group'
	verbose_name = 'Area this group is in'
	verbose_name_plural = 'Area to Group mappings'

class GoalMilestone(models.Model):
    DONE_CHOICES = (
        ('Done', 'Done'),
        ('No', 'Not Done'),
    )
    gm_id = models.AutoField(primary_key=True)
    group_acronym = models.ForeignKey(IETFWG)
    description = models.TextField()
    expected_due_date = models.DateField()
    done_date = models.DateField(null=True, blank=True)
    done = models.CharField(blank=True, choices=DONE_CHOICES, max_length=4)
    last_modified_date = models.DateField()
    def __str__(self):
	return self.description
    class Meta:
        db_table = 'goals_milestones'
	verbose_name = 'IETF WG Goal or Milestone'
	verbose_name_plural = 'IETF WG Goals or Milestones'
	ordering = ['expected_due_date']


#### end wg stuff

class Role(models.Model):
    '''This table is named 'chairs' in the database, as its original
    role was to store "who are IETF, IAB and IRTF chairs?".  It has
    since expanded to store roles, such as "IAB Exec Dir" and "IAD",
    so the model is renamed.
    '''
    person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
    role_name = models.CharField(max_length=25, db_column='chair_name')
    
    # Role values
    IETF_CHAIR            = 1
    IAB_CHAIR             = 2
    NOMCOM_CHAIR          = 3
    IAB_EXCUTIVE_DIRECTOR = 4
    IRTF_CHAIR            = 5
    IAD_CHAIR             = 6
    RSOC_CHAIR            = 7

    # This __str__ makes it odd to use as a ForeignKey.
    def __str__(self):
	return "%s (%s)" % (self.person, self.role())
    def role(self):
	if self.role_name in ('IETF', 'IAB', 'IRTF', 'NomCom'):
	    return "%s Chair" % self.role_name
	else:
	    return self.role_name
    class Meta:
        db_table = 'chairs'

class ChairsHistory(models.Model):
    chair_type = models.ForeignKey(Role)
    present_chair = models.BooleanField()
    person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
    start_year = models.IntegerField()
    end_year = models.IntegerField(null=True, blank=True)
    def __str__(self):
	return str(self.person)
    class Meta:
        db_table = 'chairs_history'

#
# IRTF RG info
class IRTF(models.Model):
    irtf_id = models.AutoField(primary_key=True)
    acronym = models.CharField(blank=True, max_length=25, db_column='irtf_acronym')
    name = models.CharField(blank=True, max_length=255, db_column='irtf_name')
    charter_text = models.TextField(blank=True,null=True)
    meeting_scheduled = models.BooleanField(blank=True)
    def __str__(self):
	return self.acronym
    def chairs(self): # return a set of IRTFChair objects for this work group
        return IRTFChair.objects.filter(irtf=self)
    class Meta:
        db_table = 'irtf'
        verbose_name="IRTF Research Group"

class IRTFChair(models.Model):
    irtf = models.ForeignKey(IRTF)
    person = models.ForeignKey(PersonOrOrgInfo, db_column='person_or_org_tag')
    def __str__(self):
        return "%s is chair of %s" % (self.person, self.irtf)
    class Meta:
        db_table = 'irtf_chairs'
        verbose_name="IRTF Research Group Chair"

class IDDates(models.Model):
    FIRST_CUT_OFF = 1
    SECOND_CUT_OFF = 2
    IETF_MONDAY = 3
    ALL_IDS_PROCESSED_BY = 4
    IETF_MONDAY_AFTER = 5
    APPROVED_V00_SUBMISSIONS = 6
    
    date = models.DateField(db_column="id_date")
    description = models.CharField(max_length=255, db_column="date_name")
    f_name = models.CharField(max_length=255)
    
    class Meta:
        db_table = 'id_dates'
    
# Not a model, but it's related.
# This is used in the view to represent documents
# in "I-D Exists".
#
class DocumentWrapper(object):
    '''A wrapper for a document, used to synthesize I-D Exists.'''
    document = None
    synthetic = True
    job_owner = "Not Assigned Yet"
    docstate = "I-D Exists"
    cur_state = "I-D Exists"
    cur_state_id = 100
    primary_flag = 1
    def __init__(self, document):
	self.document = document

if settings.USE_DB_REDESIGN_PROXY_CLASSES:
    InternetDraftOld = InternetDraft
    IDInternalOld = IDInternal
    RfcOld = Rfc
    BallotInfoOld = BallotInfo
    IDStateOld = IDState
    IDSubStateOld = IDSubState
    AreaOld = Area
    AcronymOld = Acronym
    IESGLoginOld = IESGLogin
    IETFWGOld = IETFWG
    IRTFOld = IRTF
    AreaGroupOld = AreaGroup
    from ietf.doc.proxy import InternetDraft, IDInternal, BallotInfo, Rfc, IDState
    from ietf.name.proxy import IDSubState
    from ietf.group.proxy import Area, Acronym, IETFWG, IRTF, AreaGroup
    from ietf.person.proxy import IESGLogin


# changes done by convert-096.py:changed maxlength to max_length
# removed core
# removed edit_inline
# removed max_num_in_admin
# removed num_in_admin
# removed raw_id_admin
