require "Machine_Action.rb"
OSX.require_framework 'AddressBook'

class Action < Machine_Action_rb
  
  attr_accessor :doneCalc, :noteReader
  
  #@@dueColor=NSColor.redColor.blendedColorWithFraction(0.5, ofColor:NSColor.blackColor)
  @@dueColor=NSColor.redColor.blendedColorWithFraction(0.5, ofColor:NSColor.blackColor)
  @@beforeStartColor=NSColor.lightGrayColor
  @@nextColor=NSColor.purpleColor.blendedColorWithFraction(0.5, ofColor:NSColor.blackColor)
  @@doneColor=NSColor.greenColor.blendedColorWithFraction(0.5, ofColor:NSColor.blackColor)
  @@todayColor=NSColor.alternateSelectedControlColor
  @@uiColor=NSColor.orangeColor.blendedColorWithFraction(0.5, ofColor:NSColor.blackColor)
  @@iColor=NSColor.blueColor.blendedColorWithFraction(0.5, ofColor:NSColor.blackColor)
  @@uColor=NSColor.yellowColor.blendedColorWithFraction(0.5, ofColor:NSColor.blackColor)
  
  @@txtIcon=NSImage.imageNamed("txt")
  @@notxtIcon=NSImage.imageNamed("notxt")
  
  Action.setKeys(["dueDate","doDate","done","startDate","parent","sequenceValue","isImportant","isUrgent"],triggerChangeNotificationsForDependentKey:"textColor")
  Action.setKeys(["dueDate"],triggerChangeNotificationsForDependentKey:"dueDateDate")
  Action.setKeys(["dueDate"],triggerChangeNotificationsForDependentKey:"dueDateTime")
  Action.setKeys(["dueDate"],triggerChangeNotificationsForDependentKey:"dueDateDateOrNil")
  Action.setKeys(["dueDate"],triggerChangeNotificationsForDependentKey:"dueDateTimeOrNil")
  Action.setKeys(["doDate"],triggerChangeNotificationsForDependentKey:"doDateDate")
  Action.setKeys(["doDate"],triggerChangeNotificationsForDependentKey:"doDateTime")
  Action.setKeys(["doDate"],triggerChangeNotificationsForDependentKey:"doDateDateOrNil")
  Action.setKeys(["doDate"],triggerChangeNotificationsForDependentKey:"doDateTimeOrNil")
  Action.setKeys(["startDate"],triggerChangeNotificationsForDependentKey:"startDateDate")
  Action.setKeys(["startDate"],triggerChangeNotificationsForDependentKey:"startDateTime")
  Action.setKeys(["startDate"],triggerChangeNotificationsForDependentKey:"startDateDateOrNil")
  Action.setKeys(["startDate"],triggerChangeNotificationsForDependentKey:"startDateTimeOrNil")
  Action.setKeys(["parent","sequenceValue"],triggerChangeNotificationsForDependentKey:"sequencePath")
  ["do","due","start"].each do |d|
    ["#{d}Hour","#{d}Minute","#{d}DateDate"].each do |k|
      Action.setKeys(["doDate"],triggerChangeNotificationsForDependentKey:k)
    end
  end
  
  def self.==(obj)
    self.isEqual(obj)
  end
  
  def reset_caches
    resetProjectCalc
    reset_kids
    reset_actionable
    reset_actionableKids
    reset_childDate
    reset_dateQuestions
    reset_doneCalc
    reset_actionable
    reset_textColor
	resetTemplateCache
  end
  
  def parentChangeKey(key)
	reset_caches
	kids.each{|c| c.parentChangeKey(key)} if children? 
  end
  def childChangeKey(key)
	reset_caches
	parent.childChangeKey(key) if parent?  && !syncing?
  end
  
  def siblingChangeKey(key)
    case key
    when "done"
      if isSequential?
        reset_actionable
        parent.reset_actionableKids if parent?
      end
      reset_textColor
    end
  end
  
  def didChangeValueForKey(key)
    if (!syncing?) then
		reset_caches
		if parent? 
		  parent.childChangeKey(key)
		  parent.kids.each{|c| c.siblingChangeKey(key)} if parent.children?
		end
		kids.each{|c| c.parentChangeKey(key)} if children?
		super_didChangeValueForKey(key)
	end
  end
  
  def openJustNotes
	@noteReader ||= NoteReader.alloc.initWithAction(self)
    @noteReader.show
    setNoteOpen(true)
  end

  def openNoteReader
    if inMailMessageID? then
		link = "message://%3c#{inMailMessageID}%3e"
		print "opening mail link: #{link}\n"
		link = NSURL.URLWithString(link)
		NSWorkspace.sharedWorkspace.openURL?(link) if link
	end
    @noteReader ||= NoteReader.alloc.initWithAction(self)
    @noteReader.show
    setNoteOpen(true)
  end
  
  def closeNoteReader
    setNoteOpen(false)
    @noteReader = nil
  end

  # find accesor for key-value coding
  # "key" must be a ruby string

#  def kvc_getter_method(key)
#    [key, key + '?'].each do |m|
#      return m if respond_to? m
#    end
#    return nil # accessor not found
#  end
#
#  def kvc_setter_method(key)
#    [kvc_internal_setter(key), key + '='].each do |m|
#      return m if respond_to? m
#    end
#    return nil
#  end
#
#  def kvc_accessor_notfound(key)
#    fmt = '%s: this class is not key value coding-compliant for the key "%s"'
#    raise sprintf(fmt, self.class, key.to_s)
#  end
#
#  def rbSetValue_forKey(value, key)
#    if m = kvc_setter_method(key.to_s)
#      send(m, value)
#    else
#      kvc_accessor_notfound(key)
#    end
#  end
#  def rbValue_forKey(value, key)
#    print "rbValueForKey\n"
#    if m = kvc_getter_method(key.to_s)
#      send(m, value)
#    else
#      kvc_accessor_notfound(key)
#    end
#  end
#  
#  def valueForKey(key)
#    print "vfk ",key,"\n"
#    if m = kvc_getter_method(key.to_s)
#      return send(m)
#    else
#      #super_valueForKey(key)
#      reurn objc_send(key.to_s)
#    end
#  end
#  
#  def setValue_forKey(value,key)
#    if m = kvc_setter_method(key.to_s)
#      send(m,value)
#    else
#      super
#    end
#  end
#    
# 
#  def valueForUndefinedKey(key)
#     if m = kvc_getter_method(key.to_s)
#      return send(m)
#    else
#      super
#    end
#  end
#
#  def setValue_forUndefinedKey(value,key)
#    if m_kvc_setter_method(key.to_s)
#      send(m,value)
#    else
#      super
#    end
#  end
#
  def awakeFromInsert
    #super
    setCreateDate(NSCalendarDate.calendarDate)
  end
  
#  def context
#	  result=super_context
#	  return parent.context if result==nil && parent?
#	  return result
#  end
  
  

  def appleScriptName
    "actions"
  end

  def parent?  #cache to avoid crossing bridge
	return @parentQ if @parentQ != nil
    @parentQ= (self.parent != nil)
    @parentQ
  end

  def children?
    return @childrenQ if @childrenQ != nil
    kids= self.children
  	@childrenQ=(kids != nil && kids.count > 0)
  end

  def sequencePath
    if parent? then
      result="%s.%03d" % [parent.sequencePath,sequenceValue]
    else
      result="%03d" % sequenceValue
    end
  end
  
  def namePath(sep)
	if parent? then
	   result = "%s%s%s" % [parent.namePath(sep),sep,name]
	else
	   result = name
	end
	result
  end

  def sequenceValue
    result=super_sequenceValue
    result = 999 if (result==nil || result.to_i==0)
    result
  end
  
  def reset_kids
    @kids=nil
    @actionableKids=nil
  end
  
  def kids
    return @kids if @kids
    return nil if !children?
    @kids=children.allObjects.sort_by {|x| x.sequenceValue.to_i}
    @kids
  end
  
  def checkSequential?
  	return true if !parent?
  	pkids= parent.kids
  	pkids.each do |a|
  		return false if a != self && !a.done?  # sequential items can only start after previous items complete
  		return true if a==self # if we're the first undone item, we can go!
  	end
  end
  
  def reset_actionableKids
    @actionableKids=nil
  end
  
  def actionableKids
    return @actionableKids if @actionableKids
    return nil if !children?
    @actionableKids= kids.select {|x| x.isActionable?}
    @actionableKids
  end
  
  def isNext?
    return false if isArea? || !isActionable?
    return false if !parent?
    return parent.actionableKids[0]==self if parent.actionableKids
    return false
  end
  
  def bumpChildrenFromIndex_throughIndex(fromIndex,toIndex)
    #print "bumping #{fromIndex} to #{toIndex}\n"
    bump=toIndex-fromIndex+1
    list= kids
    list.each{|a| a.sequenceValue= a.sequenceValue.to_i+bump if a.sequenceValue.to_i >=fromIndex } if list
  end

  def resequence
    #print "resequencing\n"
    list= kids
    list.each_with_index {|a,index| a.sequenceValue=index+1} if list
  end

  def resequenceAll
    self.resequence
    children.each { |x| resequenceAll} if (children?)
  end

  def isSomedayUp?
    return isSomeday? || (parent? && parent.isSomedayUp?)
  end
  
  def isTemplateUp
     return isTemplateUp?
  end

  def isTemplateUp?
	return @isTemplateUpCache if @isTemplateUpCache !=nil
	@isTemplateUpCache=isTemplate? || (parent? && parent.isTemplateUp?)
    return @isTemplateUpCache
  end
  
  def resetTemplateCache
	@isTemplateUpCache=nil
  end
  
  def templateParent
	return parent.templateParent if (parent? && parent.isTemplate?) 
	return self
  end
  
  def isTemplateRoot
	return isTemplateRoot?
  end

  def isTemplateRoot?
	return isTemplate? && !(parent.isTemplateUp?)
  end
  

  def isSomedayUp=(value)
    if (!value || value.to_i==0) then
      if (parent? && parent.isSomedayUp?)
		parent.isSomedayUp=value #recurse up to disable if parent is set
	  else
	    setIsSomeday(value)
	  end
    else
		setIsSomeday(value)
	end
  end

  def setIsSomedayUp=(value)
    isSomedayUp=(value)
  end

  def isTemplateUp=(value)
    if (!value || value.to_i==0) then
      if (parent? && parent.isTemplateUp)
		parent.isTemplateUp=value #recurse up to disable if parent is set
	  else
	    setIsTemplate(value)
	  end
    else
		setIsTemplate(value)
	end
  end

  def setIsTemplateUp=(value)
    isTemplateUp=(value)
  end

  def isArea?
    isArea.boolValue
  end

  def isProject?
    (!isArea?) && children?
  end
  
  def isProject
    isProject?
  end

  def isTask?
    ! (isArea? || children?)
  end
  
  
  def reset_actionable
    @actionable=nil
  end
  
  def isActionable?
    return @actionable if @actionable !=nil
    @actionable=actionableCalc
  end
  def actionableCalc
    ! (done? || isArea? || children? || isSomedayUp? || isTemplateUp? || beforeStart? || (isSequential? && !checkSequential? ))
  end
  
  def isTask
    isTask?
  end
  
  def rootAction
    return parent.rootAction if parent?
    self
  end
  
  def resetProjectCalc
    @projectCalc=nil
    @parentQ=nil
    @childrenQ=nil
  end
  
  def setIsArea(value)
    super_setIsArea(value)
  end

  def project
    return @projectCalc if @projectCalc
    if parent? && !(parent.isArea?)
      @projectCalc=parent.project 
    else
      @projectCalc=self
    end
  end
  
  def project=(value)
    setParent(value)
  end

  def setProject(value)
    setParent(value)
  end
  
  def setParentAtIndex(value,index)
    return if value.hasAncestor?(self) 
	resetProjectCalc
    value.bumpChildrenFromIndex_throughIndex(index,index+1) if value
    value.reset_kids if value
    parentChange(parent,value)
    super_setParent(value)
    setSequenceValue(index)
  end
  
  def setParent(value)
    return if value && value.hasAncestor?(self) 
    oldparent = parent
	resetProjectCalc
    sequenceValue= 999 # throw to the bottom of the list
    parentChange(parent,value)
    super_setParent(value)
    parent.resequence if parent?
    oldparent.resequence if oldparent != nil
  end

  def area
    return if parent? && !isArea?
    self
  end

  def depth
    depth=1
    p=parent
    while(p) do
      depth++
      p=parent
    end
    depth
  end

  def done?
    done.to_i==1
  end

  def finishOrDoDate
    result=nil
    if done? then
      result=finishDate
    elsif doDate then
      result=doDate
    elsif startDate then
      result=startDate
    else
      result=dueDate
    end
    result
  end
  
  def isToday?
    today=NSDate.date.dayNumber
    return ( 
        (doDate && doDate.dayNumber==today) || 
        (startDate && startDate.dayNumber==today) || 
        (finishDate && finishDate.dayNumber==today)|| 
        (!done? && ((doDate && doDate.dayNumber<=today) || (dueDate && dueDate.dayNumber<=today)))
      )
  end

  def isDue?
    return @isDue if @isDue != nil
    @isDue= (!done? && dueDate && dueDate.dayNumber <= NSDate.date.dayNumber)
    @isDue
  end
  
  def reset_dateQuestions
    @beforeStart=nil
    @isDue=nil
  end
  

  def beforeStart?
    return @beforeStart if @beforeStart != nil
    @beforeStart = (startDate && !done? && startDate.dayNumber > NSDate.date.dayNumber)
    @beforeStart
  end

  def hasAncestor?(ancestor)
    if ancestor==self then
      return true
    else
      return parent !=nil && parent.hasAncestor?(ancestor)
    end
  end
  
  def hasChild?(child)
    child.hasAncestor?(self)
  end

  def primitiveDone
  	willAccessValueForKey "done"
  	result = primitiveValueForKey "done"
  	didAccessValueForKey "done"
  	result
  end
  
  def reset_nextCalc
    reset_doneCalc
    reset_dateQuestions
    if actionableKids && actionableKids.size > 0
      nextAction=actionableKids[0]
      nextAction.willChangeValueForKey "textColor"
      nextAction.didChangeValueForKey "textColor"
    end
  end
  
  def reset_doneCalc
    @doneCalc=nil
    reset_actionableKids
    parent.reset_doneCalc if parent? && parent != nil # recurion might not have reset everything
  end
  def doneCalc
    return @doneCalc if @doneCalc!= nil
    mx=self.valueForKeyPath("children.@min.done")
    mn=self.valueForKeyPath("children.@max.done")
    @doneCalc=mx
    @doneCalc= -1 if (mn!=mx)
    @doneCalc
  end
  
  def done
    result=super_done
    result=doneCalc if children?
    
    #recurring items "reset" when asked if they're done
    if result.to_i > 0 && isRecurring? && recurReset? && doDate != nil && doDate.dayNumber.to_i <= NSDate.date.dayNumber.to_i
      setDone(0)
      setFinishDate(nil)
      result=0
    end
    result
  end
  
  def instantiateTemplate
    newAction=cloneOfSelf
    newAction.setCreateDate(NSCalendarDate.calendarDate)
    newAction.setIsTemplate(false)
    newAction.setDone(0)
	if children?
		kids.each do |a|
		  newA=a.instantiateTemplate
		  newA.setParent(newAction)
		end
	end
    newAction
  end
  
  def editTemplate
      AppDelegate.newFocusWindow(self)
  end
  
  def markedDoneTransition
	 setFinishDate(NSCalendarDate.calendarDate)
	 NSNotificationCenter.defaultCenter.postNotificationName_object("markedDoneTransition",self);
  end
  
  def markedUndoneTransition
	 setFinishDate(nil)
	 NSNotificationCenter.defaultCenter.postNotificationName_object("markedUndoneTransition",self);
  end
  
  def setDone(value)
    value=1 if (-1 == value.to_i) # mixed state checkboxes do 0->-1->1, we only want 0->1
    oldvalue=primitiveDone
    wasNext= (oldvalue.to_i==0 && value.to_i > 0 && isNext?)
    super_setDone(value)
    if (oldvalue.to_i != value.to_i && !syncing?) then
      if (1 == value.to_i)
	    markedDoneTransition
        if isRecurring? 
          offset=1
          deadline=0
          offset=recurDays.to_i if recurDays?
          deadline=recurDeadline.to_i if recurDeadline?
          if !recurReset
            clone=cloneOfSelf
            clone.setDone(0)
            clone.setFinishDate(0)
            clone.setStartDate(NSDate.date.dateByAddingDays(offset))
            clone.setDoDate(NSDate.date.dateByAddingDays(offset))
            clone.setDueDate(NSDate.date.dateByAddingDays(offset+deadline)) if dueDate?
            setIsRecurring(0)
          else
            setStartDate(NSDate.date.dateByAddingDays(offset))
            setDoDate(NSDate.date.dateByAddingDays(offset))
            setDueDate(NSDate.date.dateByAddingDays(offset+deadline)) if dueDate?
          end
        end 
      else
        markedUndoneTransition
      end
      if parent?
        if (wasNext)
            parent.reset_nextCalc
        else
            parent.reset_doneCalc
        end
        parent.willChangeValueForKey "done"
        parent.didChangeValueForKey "done"
      end
    end
  end
    
  def parentChange(oldParent,newParent)
    if oldParent
      oldParent.reset_childDate 
      oldParent.resetProjectCalc
    end
    if newParent
      newParent.reset_childDate 
      newParent.resetProjectCalc
    end
	@isTemplateUpCache=nil
  end

  def dueDate
    date=super_dueDate
    if children? then
      @childDueDate=self.valueForKeyPath("children.@max.dueDate")
      date=@childDueDate if @childDueDate && (!date || !(@childDueDate.compare(date) < 0))
    end
    date
  end
  
  def reset_childDate
    @childStartDate=nil
    @childDueDate=nil
    reset_dateQuestions
  end

  def startDate
    date=super_startDate
    if children?
      @childStartDate=self.valueForKeyPath("children.@min.startDate") if not @childStartDate
      date=@childStartDate if @childStartDate && (!date || !(@childStartDate.compare(date) < 0))
    end
    date
  end

  def priority
    p = 4
    p -= 1 if isUrgent?
    p -= 2 if isImportant?
    p
  end

  def deepEach
    yield(self) #yield once for me
    self.children.allObjects.to_a.each{|c| c.deepEach { |x| yield(x)} } if children? 
  end

  def eachTask
    yield(self) if isTask?
    if children?
      self.children.allObjects.to_a.each do |c| 
        c.eachTask{ |x| yield(x) } 
      end
    end
  end
  
  def hasChildren
    children?
  end
  
  def setSmartName(value)
    smartName= value
  end
  
  def smartName=(value)
    value=parseSmartString(value)
    super_setName(value)
  end
  
  def smartName(value)
    name
  end
  
  def setName(value)
    value=parseSmartString(value)
    super_setName(value)
  end
  
  def findObjectByName(entity,value)
    moc=self.managedObjectContext
    ed=NSEntityDescription.entityForName_inManagedObjectContext(entity,moc)
    request=NSFetchRequest.alloc.init
    request.setEntity(ed)
    pred=NSPredicate.predicateWithFormat_argumentArray("name BEGINSWITH[c] %@",[value])
    request.setPredicate(pred)
    sort=NSSortDescriptor.alloc.initWithKey_ascending("name",true)
    request.setSortDescriptors([sort])
    error=0
    result=moc.executeFetchRequest_error(request,nil)
    return result[0] if result && result.count > 0
    return nil
  end
  
  def findContext(value)
    value.strip!
    result=findObjectByName("Context",value)
    #print "Found Context #{result.name} for #{value}\n" if result
    #print "Couldn't find '#{value}'\n" unless result
    result
  end
  
  def findProject(value)
    value.strip!
    result=findObjectByName("Action",value)
    #print "Found Project #{result.name} for #{value}\n" if result
    #print "Couldn't find '#{value}'\n" unless result
    result
  end
  
  def parseDate(value)
    df=NSDateFormatter.alloc.initWithDateFormat_allowNaturalLanguage("%a %b %e %I%:%M%p",true)
    
    result=df.dateFromString(value)
    print "Turned #{value} into #{result}\n"
    print "Couldn't parse #{value}\n" unless result
    result
  end
  
  def parseSmartString(value)

    value=value.to_s #make sure its a ruby string
	markdone=0
    value.sub!(/^[ \t]*did[ \t]+/) {|s| markdone=1; ''}
    value.sub!(/^[ \t]*Did[ \t]+/) {|s| markdone=1; ''}
    
    # process action name to produce new value
    # @ context will search for the matching context, or :context, or :c
    # > Project will search for the matching project, or :project, or :p
    # :due will try to set the due date, i.e. :due tues
    # :start will try to set the start date, i.e. :start mon
    # :on will try to set the do on date, i.e. :on mon or :do
    # :i will set "is important"
    # :u will set "is urgent"
    # :ui, :will set both
    # :someday
    # :template
    # :recur
    # :reset
    # :area
    contextr= %r{((@[ \t]*)|(\:context|:c))[ \t]*([^:@>\n\r]+)}i
    projectr= %r{((>[ \t]*)|(\:project|:p))[ \t]*([^:@>\n\r]+)}i
    duer=%r{(:due)[ \t]*(((\d{1,2}:\d\d)|[^:@>\n\r])+)}i
    startr=%r{(\:start)[ \t]*(((\d{1,2}:\d\d)|[^:@>\n\r])+)}i
    doner=%r{(\:done)[ \t]*}i
    onr=%r{(\:on)[ \t]*(((\d{1,2}:\d\d)|[^:@>\n\r])+)}i
    priorityr=%r{(\:iu|:ui|:i|:u)[ \t]*}i
    somedayr=%r{(\:someday)[ \t]*}i
    templater=%r{(\:templ*a*t*e*)[ \t]*}i
    recurr=%r{(\:recur)[ \t]*}i
    resetr=%r{(\:reset)[ \t]*}i
    arear=%r{(\:area)[ \t]*}i
    noter=%r{(:note|\n|\r)[ \t]*(.*)}i
    regex=Regexp.union(contextr,projectr,duer,startr,onr,priorityr,somedayr,templater,recurr,resetr,arear,doner,noter)

    value.gsub!(regex) do
    #print "match: ",$&,"\n"
    #print "list: 1#{$1} 2#{$2} 3#{$3} 4#{$4}"
    match=$~.to_a
    match.shift
    match=match.select{|x| x}
	managedObjectContext.processPendingChanges()  # best to process changes as we manipulate relationships
    case match[0]
      when /@|:c/i
        ##print "Context: #{match[1]} #{match[2]}\n"
        setContext(findContext(match[2]))
      when />|:p/i
        ##print "Project: #{match[1]} #{match[2]}\n"
        setParent(findProject(match[2]))
      when /:due/i
        ##print "Due: #{match[1]}\n"
        ddate= parseDate(match[1])
        setDueDate(ddate)
      when /:start/i
        ##print "Start: #{match[1]}\n"
        sdate= parseDate(match[1])
        print "got a start date #{sdate}\n"
        setStartDate(sdate)
        print "now date #{startDate}\n"
      when /:done/i
        markdone=1
      when /:on|:do/
        ##print "Do: #{match[1]}\n"
        ddate= parseDate(match[1])
        setDoDate(ddate)
      when /:iu|:ui/i
        setIsImportant(1)
        setIsUrgent(1)
      when /:i/i
        setIsImportant(1)
      when /:u/i
        setIsUrgent(1)
      when /:someday/i
        setIsSomeday(1)
      when /:temp/i
        setIsTemplate(1)
      when /:recur/i
        setIsRecurring(1)
      when /:reset/i
        setRecurReset(1)
      when /:area/i
        setIsArea(1)
      when /:note|\n|\r/i
        setNoteString(match[1])
      end
      #print "Match #{match}\n\n"
      ''
    end
	managedObjectContext.processPendingChanges()  # best to process changes as we manipulate relationships
	super_setName(value)
	setDone(1) if (markdone==1)
    value
  end
  
  def isUrgent
    result = super_isUrgent
    result = 0 unless result != nil
    result
  end
  
  def isImportant
    result=super_isImportant
    result=0 unless result != nil
    result
  end  
  
  def smartNameTest
    string = " did stuff @work @ play > Project >project4 :project Project2 :p project3 :c work :due Feb 3 4:00pm :done :i :u :ui :someday :template :recur :reset :area"

    string.sub!(/^[ \t]*did[ \t]*/) {|s| print "found did\n"; ''}

    # @ context will search for the matching context, or :context, or :c
    # > Project will search for the matching project, or :project, or :p
    # :due will try to set the due date, i.e. :due tues
    # :start will try to set the start date, i.e. :start mon
    # :on will try to set the do on date, i.e. :on mon
    # :i will set "is important"
    # :u will set "is urgent"
    # :ui, :will set both
    # :someday
    # :temp[late]
    # :recur
    # :reset
    # :area

     contextr= %r{((@[ \t]*)|(\:context|:c))[ \t]*([^:@>]+)}i
     projectr= %r{((>[ \t]*)|(\:project|:p))[ \t]*([^:@>]+)}i
     duer=%r{(:due)[ \t]*(((\d{1,2}:\d\d)|[^:@>])+)}i
     startr=%r{(\:start)[ \t]*(((\d{1,2}:\d\d)|[^:@>])+)}i
     doner=%r{(\:done)[ \t]*}i
     onr=%r{(\:on|:do)[ \t]*(((\d{1,2}:\d\d)|[^:@>])+)}i
     priorityr=%r{(\:iu|:ui|:i|:u)[ \t]*}i
     somedayr=%r{(\:someday)[ \t]*}i
     templater=%r{(\:templ*a*t*e*)[ \t]*}i
     recurr=%r{(\:recur)[ \t]*}i
     resetr=%r{(\:reset)[ \t]*}i
     arear=%r{(\:area)[ \t]*}i
     regex=Regexp.union(contextr,projectr,duer,startr,onr,priorityr,somedayr,templater,recurr,resetr,arear,doner)

    string.gsub!(regex) do
        #print "match: ",$&,"\n"
        #print "list: 1#{$1} 2#{$2} 3#{$3} 4#{$4}"
        match=$~.to_a
        match.shift
        match=match.select{|x| x}
        case match[0]
          when /@|:c/
            print "Context: #{match[1]} #{match[2]}\n"
          when /\>/
            print "Project: #{match[1]} #{match[2]}\n"
          when /:p/i
            print "Project: #{match[1]} #{match[2]}\n"
          when /:due/
            print "Due: #{match[1]}\n"
          when /:start/
            print "Start: #{match[1]}\n"
          when /:on|do/
            print "Do On: #{match[1]}\n"
          when /:done/
            print "Done\n"
          when /:iu|:ui/
            print "UI\n"
          when /:i/
            print "important\n"
          when /:u/
            print "Urgent\n"
          when /:someday/
            print "someday\n"
          when /:temp/
            print "template\n"
          when /:recur/
            print "recur\n"
          when /:reset/
            print "reset\n"
          when /:area/
            print "area\n"
          else
            print "No Match? "
            match.each_with_index {|i,m| print "#{i}:#{m} "}
            print "\n"
          end
        print "Match #{match}\n\n"
        ''
      end
    print string
  end
  
  def addChildrenObject(value)
    super_addChildrenObject(value)
    reset_kids
  end
  def removeChildrenObject(value)
    super_removeChildrenObject(value)
    reset_kids
  end
  
  def reset_textColor
    @textColor=nil
  end
  def textColorCalc
    return @@dueColor if isDue?
    return @@beforeStartColor if beforeStart?
    return @@nextColor if isNext?
    return @@doneColor if done?
    return @@uiColor if isUrgent? && isImportant?
    return @@iColor if isImportant?
    return @@uColor if isUrgent?
    return @@todayColor if isToday?
	return @@beforeStartColor if isTemplateUp?
    return NSColor.blackColor
  end
  def textColor
    return @textColor if @textColor
    @textColor=textColorCalc
    @textColor
  end
  #objc_method :textColor,%w{id}
  
  def actionState
      return "stateDue" if isDue?
      return "stateBeforeStart" if beforeStart?
      return "stateNext" if isNext?
      return "stateDone" if done?
      return "stateUI" if isUrgent? && isImportant?
      return "stateI" if isImportant?
      return "stateU" if isUrgent?
      return "stateToday" if isToday?
      return "stateTemplate" if isTemplateUp?
      return "stateNone"
    end
    
  
  def selfPlusChildren(array)
    array << self
    kids.each {|a| a.selfPlusChildren(array)} if children?
    array
  end
  
  def Action.loadDefaults
    defaults=NSUserDefaultsController.sharedUserDefaultsController.values
    @@dueColor= NSUnarchiver.unarchiveObjectWithData(defaults.valueForKey("dueColor")) if defaults.valueForKey("dueColor")
    @@beforeStartColor= NSUnarchiver.unarchiveObjectWithData(defaults.valueForKey("beforeStartColor")) if defaults.valueForKey("beforeStartColor")
    @@nextColor= NSUnarchiver.unarchiveObjectWithData(defaults.valueForKey("nextColor")) if defaults.valueForKey("nextColor")
    @@doneColor= NSUnarchiver.unarchiveObjectWithData(defaults.valueForKey("doneColor")) if defaults.valueForKey("doneColor")
    @@uiColor= NSUnarchiver.unarchiveObjectWithData(defaults.valueForKey("UIColor")) if defaults.valueForKey("UIColor")
    @@iColor=NSUnarchiver.unarchiveObjectWithData(defaults.valueForKey("IColor")) if defaults.valueForKey("IColor")
    @@uColor= NSUnarchiver.unarchiveObjectWithData(defaults.valueForKey("UColor")) if defaults.valueForKey("UColor")
    @@todayColor= NSUnarchiver.unarchiveObjectWithData(defaults.valueForKey("todayColor")) if defaults.valueForKey("todayColor")
  end
  
  def appLaunch
	setIsUrgent(0) if self.isUrgent==nil
	setIsImportant(0) if self.isImportant==nil
  end
  
  def doDelegateTo(abperson,bestEmail,extraText)
    now = NSCalendarDate.date
	#print "best email #{bestEmail}\n"
	defaults=NSUserDefaults.standardUserDefaults
	delay = defaults.integerForKey("delegateFollowUpDelay")
	email = defaults.boolForKey("delegateShouldGenerateEmail")
	delay = 7 if not delay
	future = now.dateByAddingDays(delay)
	self.delegatedToID=abperson.uniqueId
	if (self.dueDate?) then
	   if (self.dueDate.dayNumber > future)
	      self.startDate=future
	   else
		  self.startDate=self.dueDate
	   end
	else
	   self.startDate = future
	end
	n = self.name
	n = n.sub("Fup: ","")
	n = "Fup: " << n
	name=n
	kids.each {|a| a.doDelegateTo(abperson) } if kids
	#print "Properties #{ABPerson.properties}\n"
	#print "Person: #{abperson}\n"
	#print "Person email: #{abperson.valueForProperty('Email')}\n"
	if (!bestEmail) then
		emailAddresses=abperson.valueForProperty("Email")
		bestEmail = emailAddresses.valueAtIndex(emailAddresses.indexForIdentifier(emailAddresses.primaryIdentifier))
	end
	if email && !done?
		MailSyncer.singleton.sendDelegateMail(bestEmail,self,extraText,delay) 
		self.delegatedToEmail=bestEmail
	end
  end		  
  
  def setDelegatedToID(value)
	@delegatedTo=nil
    super_setDelegatedToID(value)
  end

  def setDelegatedFromID(value)
	@delegatedFrom=nil
    super_setDelegatedFromID(value)
  end

  
  def delegatedTo
	return nil unless delegatedToID?
	@delgatedTo=ABAddressBook.sharedAddressBook.recordForUniqueId(delegatedToID) unless @delegatedTo
	@delegateTo
  end

  def delegatedFrom
	return nil unless delegatedFromID?
	@delgatedFrom=ABAddressBook.sharedAddressBook.recordForUniqueId(delgatedFromID) unless @delgatedFrom
	@delegatedFrom
  end
  
  def rootProject
    project
  end
  
  def rootArea
    p = project.parent
	return p if p && p.isArea?
	return nil
  end
  
  def objectEnumerator
	return nil
  end
  
end
