// abview.m (this is -*- ObjC -*-)
// 
// \author: Björn Giesler <giesler@ira.uka.de>
// 
// 
// 
// $Author: bjoern $
// $Locker:  $
// $Revision: 1.3 $
// $Date: 2004/06/18 01:13:43 $

/* system includes */
#include <unistd.h>
#include <Addresses/Addresses.h>

/* my includes */
/* (none) */

extern char** environ;

#define DIE(message...) do {fprintf(stderr, message); return -1;} while(0)

@interface ADView: NSObject
{
  ADAddressBook *book;
  NSString *progname, *command;
  NSArray *args;
  BOOL localize, header;
}

- initWithArguments: (char**) argv
	      count: (int) count;

- (int) people;
- (int) showperson;
- (int) setme;
- (int) exportimage;
- (int) importimage;

- (int) groups;
- (int) members;
- (int) addmember;
- (int) delmember;
- (int) subgroups;
- (int) addgroup;
- (int) delgroup;

- (int) tree;
- (int) config;
- (int) import;

- (int) execute;
- (int) die;
@end;

@implementation ADView
- initWithArguments: (char**) argv
	      count: (int) count
{
  int start;
  NSArray *arr;
  
  [NSProcessInfo initializeWithArguments: argv
		 count: count
		 environment: environ];
  arr = [[NSProcessInfo processInfo] arguments];
  
  command = nil;
  header = YES;
  localize = NO;
  
  progname = [arr objectAtIndex: 0];
  start = 1;

  while(start < [arr count])
    {
      NSString *arg = [arr objectAtIndex: start];
      if(![[arg substringWithRange: NSMakeRange(0, 1)]
	    isEqualToString: @"-"])
	{
	  command = arg;
	  start++;
	  break;
	}
      else
	{
	  if([arg isEqualToString: @"-l"])
	    localize = YES;
	  else if([arg isEqualToString: @"-h"])
	    header = NO;
	  else
	    {
	      [self die];
	      exit(-1);
	    }
	}

      start++;
    }
      
  if(!command)
    {
      [self die];
      exit(-1);
    }

  if(start < count)
    args = [arr subarrayWithRange: NSMakeRange(start, count-start)];
  else
    args = nil;

  book = [ADAddressBook sharedAddressBook];
  if(!book)
    {
      fprintf(stderr, "Error: NIL address book\n");
      exit(-1);
    }
	      
  return [super init];
}

/*
 * person management
 */

- (int) people
{
  NSEnumerator *e;
  ADPerson *p;

  if([args count] != 0) return [self die];
  e = [[book people] objectEnumerator];

  if(header)
    printf("ID    Person Name\n");
  while((p = [e nextObject]))
    printf("%-5s %s %s\n", [[p valueForProperty: ADUIDProperty] cString],
	    [[p valueForProperty: ADFirstNameProperty] cString],
	    [[p valueForProperty: ADLastNameProperty] cString]);

  return 0;
}

- (int) showperson
{
  ADPerson *p;
  NSString *property;
  NSArray *props;
  NSEnumerator *e;

  if([args count] != 1) return [self die];

  if([[args objectAtIndex: 0] isEqualToString: @"me"])
    p = [book me];
  else
    p = (ADPerson*)[book recordForUniqueId: [args objectAtIndex: 0]];
  
  if(!p) DIE("No such record\n");
  if(![p isKindOfClass: [ADPerson class]])
    DIE("Record with index %s is not a person\n",
	[[args objectAtIndex: 0] cString]);

  props = [[ADPerson properties]
		     sortedArrayUsingSelector: @selector(compare:)];
  e = [props objectEnumerator];

  printf("%s %s\n",
	  [[p valueForProperty: ADFirstNameProperty] cString],
	  [[p valueForProperty: ADLastNameProperty] cString]);
	  
  while((property = [e nextObject]))
    {
      id val;

      val = [p valueForProperty: property];
      if(!val) continue;

      if([val isKindOfClass: [NSString class]] ||
	 [val isKindOfClass: [NSDate class]])
	{
	  if(localize)
	    printf("%-20s: %s\n",
		    [ADLocalizedPropertyOrLabel(property) cString],
		    [[val description] cString]);
	  else
	    printf("%-20s: %s\n",
		    [property cString],
		    [[val description] cString]);
	}
      else if([val isKindOfClass: [ADMultiValue class]])
	{
	  int i;

	  if(![val count]) continue;

	  for(i=0; i<[val count]; i++)
	    {
	      NSString *label, *identifier; id v;
	      v = [val valueAtIndex: i];
	      label = [val labelAtIndex: i];
	      identifier = [val identifierAtIndex: i];

	      if(i==0)
		{
		  if(localize)
		    printf("%-20s: %-5s ",
			    [ADLocalizedPropertyOrLabel(property) cString],
			    [identifier cString]);
		  else
		    printf("%-20s: %-5s",
			    [property cString], [identifier cString]);


		}
	      else
		printf("%-21s %-5s ", "", [identifier cString]);

	      if(localize)
		printf("%-20s ",
			[ADLocalizedPropertyOrLabel(label) cString]);
	      else
		printf("%-20s ", [label cString]);

	      printf("%s\n", [[v description] cString]);
	    }
	}
      else
	{
	  if(localize)
	    printf("%-20s: Undisplayable (class %s)\n",
		    [ADLocalizedPropertyOrLabel(property) cString],
		    [[val className] cString]);
	  else
	    printf("%-20s: Undisplayable (class %s)\n",
		    [property cString],
		    [[val className] cString]);
	}
    }

  return 0; 
}

- (int) setme
{
  ADPerson *p;

  if([args count] != 1) return [self die];

  p = (ADPerson*)[book recordForUniqueId: [args objectAtIndex: 0]];
  
  if(!p) DIE("No such record\n");
  if(![p isKindOfClass: [ADPerson class]])
    DIE("Record with index %s is not a person\n",
	[[args objectAtIndex: 0] cString]);

  [book setMe: p];
  return 0; 
}

- (int) exportimage
{
  ADPerson *p;
  NSString *filename;
  NSData *pic;
  
  if([args count] != 2) return [self die];

  p = (ADPerson*)[book recordForUniqueId: [args objectAtIndex: 0]];
  if(!p) DIE("No such record\n");
  if(![p isKindOfClass: [ADPerson class]])
    DIE("Record with index %s is not a person\n",
	[[args objectAtIndex: 0] cString]);

  filename = [args objectAtIndex: 1];
  pic = [p imageData];
  if(!pic) DIE("No image associated with person\n");
  if(![pic writeToFile: filename atomically: NO])
    DIE("Couldn't write image file %s\n", [filename cString]);

  return 0;
}

- (int) importimage
{
  ADPerson *p;
  NSString *filename;
  NSData *pic;

  if([args count] != 2) return [self die];
  p = (ADPerson*)[book recordForUniqueId: [args objectAtIndex: 0]];
  if(!p) DIE("No such record\n");
  if(![p isKindOfClass: [ADPerson class]])
    DIE("Record with index %s is not a person\n",
	[[args objectAtIndex: 0] cString]);

  filename = [args objectAtIndex: 1];
  pic = [NSData dataWithContentsOfFile: filename];
  if(!pic) DIE("Couldn't read image file %s\n", [filename cString]);
  if(![p setImageData: pic])
    DIE("Couldn't set image in person\n");

  if(![book save]) DIE("Error saving address book!\n");

  return 0;
}


/*
 * group management
 */

- (int) groups
{
  ADGroup *g;
  NSEnumerator *e;

  if([args count] != 0) return [self die];

  e = [[book groups] objectEnumerator];

  if(header)
    printf("ID    Group Name\n");
  while((g = [e nextObject]))
    printf("%-5s %s\n",
	   [[g valueForProperty: ADUIDProperty] cString],
	   [[g valueForProperty: ADGroupNameProperty] cString]);      
  return 0;
}

- (int) members
{
  ADGroup *g;
  ADPerson *p;
  NSEnumerator *e;

  if([args count] != 1) return [self die];

  g = (ADGroup*)[book recordForUniqueId: [args objectAtIndex: 0]];
  if(!g) DIE("No group with id %s\n", [[args objectAtIndex: 0] cString]);
  if(![g isKindOfClass: [ADGroup class]])
    DIE("Record with index %s is not a group\n",
	[[args objectAtIndex: 0] cString]);
  
  e = [[g members] objectEnumerator];

  if(header)
    printf("ID    Person Name\n");
  while((p = [e nextObject]))
    printf("%-5s %s %s\n",
	    [[p valueForProperty: ADUIDProperty] cString],
	    [[p valueForProperty: ADFirstNameProperty] cString],
	    [[p valueForProperty: ADLastNameProperty] cString]);
  return 0;
}

- (int) addmember
{
  ADGroup *g;
  ADPerson *p;

  if([args count] != 2) return [self die];
  g = (ADGroup*)[book recordForUniqueId: [args objectAtIndex: 0]];
  p = (ADPerson*)[book recordForUniqueId: [args objectAtIndex: 1]];
  if(!g) DIE("No group with id %s\n", [[args objectAtIndex: 0] cString]);
  if(![g isKindOfClass: [ADGroup class]])
    DIE("Record with id %s is not a group!\n",
	[[args objectAtIndex: 0] cString]);
  if(![p isKindOfClass: [ADPerson class]])
    DIE("Record with id %s is not a person!\n",
	[[args objectAtIndex: 0] cString]);

  if(![g addMember: p]) DIE("Error\n");
  return 0;
}  

- (int) delmember
{
  ADGroup *g;
  ADPerson *p;

  if([args count] != 2) return [self die];
  g = (ADGroup*)[book recordForUniqueId: [args objectAtIndex: 0]];
  p = (ADPerson*)[book recordForUniqueId: [args objectAtIndex: 1]];
  if(!g) DIE("No group with id %s\n", [[args objectAtIndex: 0] cString]);
  if(!p) DIE("No person with id %s\n", [[args objectAtIndex: 1] cString]);
  if(![g isKindOfClass: [ADGroup class]])
    DIE("Record with id %s is not a group!\n",
	[[args objectAtIndex: 0] cString]);
  if(![p isKindOfClass: [ADPerson class]])
    DIE("Record with id %s is not a person!\n",
	[[args objectAtIndex: 0] cString]);

  if(![g removeMember: p]) DIE("Error\n");
  return 0;
}  

- (int) subgroups
{
  ADGroup *g;
  NSEnumerator *e;

  if([args count] != 1) return [self die];
  g = (ADGroup*)[book recordForUniqueId: [args objectAtIndex: 0]];
  if(!g) DIE("No group with id %s\n", [[args objectAtIndex: 0] cString]);
  if(![g isKindOfClass: [ADGroup class]])
    DIE("Record with id %s is not a group!\n",
	[[args objectAtIndex: 0] cString]);
    
  e = [[g subgroups] objectEnumerator];
  if(header)
     printf("ID    Group Name\n");
  while((g = [e nextObject]))
    printf("%-5s %s\n",
	    [[g valueForProperty: ADUIDProperty] cString],
	    [[g valueForProperty: ADGroupNameProperty] cString]);
  return 0;
}

- (int) addgroup
{
  ADGroup *group;

  if([args count] != 1 && [args count] != 2) return [self die];
  group = [[ADGroup alloc] init];

  if([args count] == 1)
    {
      [group setValue: [args objectAtIndex: 0]
	     forProperty: ADGroupNameProperty];

      if(![book addRecord: group]) DIE("Error\n");
    }
  else
    {
      id sg;
      [group setValue: [args objectAtIndex: 1]
	     forProperty: ADGroupNameProperty];
      sg = [book recordForUniqueId: [args objectAtIndex: 0]];
      if(!sg)
	DIE("No group with id %s\n", [[args objectAtIndex: 0] cString]);
      else if(![sg isKindOfClass: [ADGroup class]])
	DIE("Record with id %s is not a group\n",
	    [[args objectAtIndex: 0] cString]);

      if(![sg addSubgroup: group]) DIE("Error\n");
    }
  printf("%s\n", [[group uniqueId] cString]);
  return 0;
}

- (int) delgroup
{
  if([args count] != 1 && [args count] != 2) return [self die];
  
  if([args count] == 1)
    {
      ADGroup *group;

      group = (ADGroup*)[book recordForUniqueId: [args objectAtIndex: 0]];
      if(![book removeRecord: group]) DIE("Error\n");
    }
  else
    {
      ADGroup *g1, *g2;

      g1 = (ADGroup*)[book recordForUniqueId: [args objectAtIndex: 0]];
      g2 = (ADGroup*)[book recordForUniqueId: [args objectAtIndex: 1]];
      if(![g1 removeSubgroup: g2]) DIE("Error\n");
    }
  return 0;
}

- (int) parentgroups
{
  id record;
  ADGroup *g;
  NSArray *parents;
  NSEnumerator *e;

  if([args count] != 1) return [self die];
  record = [book recordForUniqueId: [args objectAtIndex: 0]];

  if(!record) DIE("No record with id %s\n", [[args objectAtIndex: 0] cString]);

  parents = [record parentGroups];
  e = [parents objectEnumerator];

  if(header)
    printf("ID    Group Name\n");
  while((g = [e nextObject]))
    printf("%-5s %s\n",
	    [[g valueForProperty: ADUIDProperty] cString],
	    [[g valueForProperty: ADGroupNameProperty] cString]);

  return 0;
}
  
- (int) _showTreeForGroup: (ADGroup*) group level: (int) level
{
  NSArray *subgroups, *members;
  NSEnumerator *e; ADPerson *p; ADGroup *g;
  
  if(!group)
    {
      subgroups = [book groups];
      members = [book people];
    }
  else
    {
      subgroups = [group subgroups];
      members = [group members];
    }

  e = [subgroups objectEnumerator];
  while((g = [e nextObject]))
    {
      printf("%*sGROUP  %-5s %s\n", level, "", [[g uniqueId] cString],
	     [[g valueForProperty: ADGroupNameProperty] cString]);
      [self _showTreeForGroup: g level: level+2];
    }
  e = [members objectEnumerator];
  while((p = [e nextObject]))
    printf("%*sPERSON %-5s %s, %s\n", level, "", [[p uniqueId] cString],
	   [[p valueForProperty: ADLastNameProperty] cString],
	   [[p valueForProperty: ADFirstNameProperty] cString]);

  return 0;
}
  
- (int) tree
{
  if([args count] != 0) return [self die];
  return [self _showTreeForGroup: nil level: 0];
}

- (void) showInfoForAddressBook: (ADAddressBook*) ab
{
    NSDictionary *descr;
    NSEnumerator *e; NSString *key;
    ADEnvelopeAddressBook *env = (ADEnvelopeAddressBook*) book;

    descr = [ab addressBookDescription];
    fprintf(stderr, "%s", [[descr objectForKey: @"Class"] cString]);
    if(ab == [env primaryAddressBook])
	fprintf(stderr, " (Primary)\n");
    else
	fprintf(stderr, "\n");

    e = [[[descr allKeys] sortedArrayUsingSelector: @selector(compare:)] 
	    objectEnumerator];
    while((key = [e nextObject]))
    {
	if([key isEqualToString: @"Class"]) continue;
	fprintf(stderr, "\t%s: %s\n", [key cString], 
		[[descr objectForKey: key] cString]);
    }
}

- (int) config
{
    NSEnumerator *e; ADAddressBook *ab;
    ADEnvelopeAddressBook *env = (ADEnvelopeAddressBook*) book;

    if([args count] != 0) return [self die];
    [self showInfoForAddressBook: [env primaryAddressBook]];
    
    e = [env addressBooksEnumerator];
    while((ab = [e nextObject]))
    {
	if(ab == [env primaryAddressBook]) continue;
	[self showInfoForAddressBook: ab];
    }
    return 0;
}

- (int) import
{
  NSEnumerator *e;
  NSString *str;

  if(!args) return [self die];
  e = [args objectEnumerator];
  while((str = [e nextObject]))
    {
      id obj;
      id<ADInputConverting> conv =
	[[ADConverterManager sharedManager] inputConverterWithFile: str];

      if(!conv)
	{
	  fprintf(stderr, "Cannot import %s (no converter)\n",
		  [str cString]);
	  continue;
	}

      while((obj = [conv nextRecord]))
	[book addRecord: obj];
    }
  return 0;
}

- (int) execute
{
  int retval;
  NSMethodSignature *sig;
  NSInvocation *inv;
  
  if(![self respondsToSelector: NSSelectorFromString(command)])
    return [self die];

  sig =
    [self methodSignatureForSelector: @selector(execute)];
  inv =
    [NSInvocation invocationWithMethodSignature: sig];
  [inv setSelector: NSSelectorFromString(command)];
  [inv invokeWithTarget: self];

  [inv getReturnValue: &retval];
  if(retval == 0)
    {
      if([book hasUnsavedChanges])
	{
	  fprintf(stderr, "Saving address book.\n");
	  if(![book save])
	    {
	      fprintf(stderr, "Error saving address book.\n");
	      return -1;
	    }
	}
    }
  return retval;
}

- (int) die;
{
  const char *s = [progname cString];
  NSArray *arr;
  
  fprintf(stderr,
	  "\n%s: Command-line utility for the Addresses framework\n\n"
	  "Usage: %s [OPTIONS] COMMAND [PARAMETERS]\n\n"
	  "Options:\n"
	  "\t-l\tturn on localization of property names\n"
	  "\t-h\tturn off printing of header\n\n"
	  "Person Management Commands:\n"
	  "\tpeople\n\t\tShow all people (ID and name)\n"
	  "\tshowperson {PERSONID|me}\n\t\tShow a person's complete record\n"
	  "\tsetme PERSONID\n\t\tMark the given person as the 'me' record\n"
	  "\texportimage PERSONID FILENAME\n\t\tExport a person's image\n"
	  "\timportimage PERSONID FILENAME\n\t\tExport a person's image\n"
	  "\n"
	  "Group Management Commands:\n"
	  "\tgroups\n\t\tShow toplevel groups\n"
	  "\tmembers GROUPID\n\t\tDisplay the members of the given group\n"
	  "\taddmember GROUPID PERSONID\n\t\tAdd a person to a group\n"
	  "\tdelmember GROUPID PERSONID\n\t\tRemove a person from a group\n"
	  "\tsubgroups SUPERGROUPID\n\t\tShow a group's subgroups\n"
	  "\taddgroup [SUPERGROUPID] NAME\n"
	  "\t\tAdd a group to toplevel or the given supergroup\n"
	  "\tdelgroup [SUPERGROUPID] GROUPID\n"
	  "\t\tRemove a group from toplevel or the given supergroup\n"
	  "\tparentgroups {GROUPID|PERSONID}\n"
	  "\t\tShow a record's parent group(s)\n"
	  "\n"
	  "General Commands:\n"
	  "\ttree\n"
	  "\t\tShow a tree view of all members and groups\n"
	  "\tconfig\n"
	  "\t\tShow the current address book configuration\n"
	  "\timport FILE [FILE ...]\n"
	  "\t\tMerge file(s) with the database\n\t\tSupported file types: ",
	  s, s);

  arr = [[ADConverterManager sharedManager]
		   inputConvertableFileTypes];
  if(![arr count]) printf("None\n\n");
  else
    {
      int i;
      for(i=0; i<[arr count]-1; i++)
	printf("%s, ", [[arr objectAtIndex: i] cString]);
      printf("%s\n\n", [[arr objectAtIndex: [arr count]-1] cString]);
    }
  return -1;
}
@end

int main(int argc, char **argv, char **env)
{
  int retval;
  NSAutoreleasePool *pool;

  pool = [[NSAutoreleasePool alloc] init];
  retval = [[[ADView alloc] initWithArguments: argv count: argc] execute];
  [pool release];
  return retval;
}

