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_ofColor(0.5,NSColor.blackColor)
  @@beforeStartColor=NSColor.lightGrayColor
  @@nextColor=NSColor.purpleColor.blendedColorWithFraction_ofColor(0.5,NSColor.blackColor)
  @@doneColor=NSColor.greenColor.blendedColorWithFraction_ofColor(0.5,NSColor.blackColor)
  @@todayColor=NSColor.alternateSelectedControlColor
  @@uiColor=NSColor.orangeColor.blendedColorWithFraction_ofColor(0.5,NSColor.blackColor)
  @@iColor=NSColor.blueColor.blendedColorWithFraction_ofColor(0.5,NSColor.blackColor)
  @@uColor=NSColor.yellowColor.blendedColorWithFraction_ofColor(0.5,NSColor.blackColor)
  
  @@txtIcon=NSImage.imageNamed("txt")
  @@notxtIcon=NSImage.imageNamed("notxt")
  
  @@syncing=false
  
  def Action.startSync
    @@syncing=true
  end
  
  def Action.endSync
    @@syncing=false
  end
  
  def syncing?
	return @@syncing
  end
  
  Action.setKeys_triggerChangeNotificationsForDependentKey(["dueDate","doDate","done","startDate","parent","sequenceValue","isImportant","isUrgent"],"textColor")
  Action.setKeys_triggerChangeNotificationsForDependentKey(["dueDate"],"dueDateDate")
  Action.setKeys_triggerChangeNotificationsForDependentKey(["dueDate"],"dueDateTime")
  Action.setKeys_triggerChangeNotificationsForDependentKey(["dueDate"],"dueDateDateOrNil")
  Action.setKeys_triggerChangeNotificationsForDependentKey(["dueDate"],"dueDateTimeOrNil")
  Action.setKeys_triggerChangeNotificationsForDependentKey(["doDate"],"doDateDate")
  Action.setKeys_triggerChangeNotificationsForDependentKey(["doDate"],"doDateTime")
  Action.setKeys_triggerChangeNotificationsForDependentKey(["doDate"],"doDateDateOrNil")
  Action.setKeys_triggerChangeNotificationsForDependentKey(["doDate"],"doDateTimeOrNil")
  Action.setKeys_triggerChangeNotificationsForDependentKey(["startDate"],"startDateDate")
  Action.setKeys_triggerChangeNotificationsForDependentKey(["startDate"],"startDateTime")
  Action.setKeys_triggerChangeNotificationsForDependentKey(["startDate"],"startDateDateOrNil")
  Action.setKeys_triggerChangeNotificationsForDependentKey(["startDate"],"startDateTimeOrNil")
  Action.setKeys_triggerChangeNotificationsForDependentKey(["parent","sequenceValue"],"sequencePath")
  ["do","due","start"].each do |d|
    ["#{d}Hour","#{d}Minute","#{d}DateDate"].each do |k|
      Action.setKeys_triggerChangeNotificationsForDependentKey(["doDate"],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
	@parentQ=nil
  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)
    if !syncing?
		return if value && value.hasAncestor?(self) 
	else
		print "setting parent in sync\n"
		print "oldparent #{parent}\n"
		print "newparent #{value}\n"
	end
    oldparent = parent
	reset_caches
    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 doInstantiateTemplate
    newAction=cloneOfSelf
    newAction.setCreateDate(NSCalendarDate.calendarDate)
    newAction.setIsTemplate(false)
    newAction.setDone(0)
	if children?
		kids.each do |a|
		  newA=a.doInstantiateTemplate
		  newA.setParent(newAction)
		end
	end
    newAction
  end
  
  def instantiateTemplate
	newA = doInstantiateTemplate
	AppDelegate.newActionFocusWindow(newA)
  end
  
  def editTemplate
      AppDelegate.newActionFocusWindow(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.reset_caches
    end
    if newParent
      newParent.reset_childDate 
      newParent.reset_caches
    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 itemState
      return "itemDone" if done?
      return "itemToDo"
    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
