/*
 File:       ComicsObj.m

 Contains:   Source code for the 3 classes used for the Comics database management:
             CIssue, CTitle, CComics

 Written by: Eric Simenel

 Created:    May 1997

 Copyright:  (c)1997 by Apple Computer, Inc., all rights reserved.

 Change History (most recent first):

 You may incorporate this sample code into your applications without
 restriction, though the sample code has been provided "AS IS" and the
 responsibility for its operation is 100% yours.  However, what you are
 not permitted to do is to redistribute the source as "DSC Sample Code"
 after having made changes. If you're going to re-distribute the source,
 we require that you make it clear in the source that the code was
 descended from Apple Sample Code, but that you've made changes.
*/

#import "ComicsObj.h"

// ComicsObj.h is a project header, so that comicsBase is known in all other source files
// There is only one instantiated object (when the main nib opens) of class CComics, and
// comicsBase is it. Simpler to use than having a "comics" outlet and a "comics" accessor
// in the main controller "VerifyController", and having all other source files refer to
// the database as [[NSApp delegate] comics]
CComics *comicsBase = nil;

// The following global arrays are here to "speed up" number to string conversion,
// see the initGlobals method of CComics
tnumstr gnumstr;
Str3 gnums[1000];

// Since we're dealing with American Comics only, the strings for edit and buy month are
// "JAN", "FEB", etc. contained in this array.
Str3 gmonths[12];

// This is the notification which will be sent by the InputController when the content of
// the database is changed. Some other Controllers will register to be notified.
NSString *ComicsDidChangeNotification = @"ComicsDidChangeNotification";

@implementation CIssue

- (id)init
{
    return [self initWithIsh:-1 withEdit:-1 withBuy:-1 withFlag:-1];
}
- (id)initWithIsh:(short)ish withEdit:(short)edit withBuy:(short)buy withFlag:(short)flag
{
    if (self = [super init])
      {
        [self setIssueNumber:ish];
        [self setEditMonth:edit];
        [self setBuyMonth:buy];
        [self setIssueFlags:flag];
      }
    return self;
}

- (void)setIssueFlags:(short)value { issueFlags = value; }
- (short)issueFlags { return issueFlags; }
- (void)setIssueNumber:(short)value { issueNumber = value; }
- (short)issueNumber { return issueNumber; }
- (void)setEditMonth:(short)value { editMonth = value; }
- (short)editMonth { return editMonth; }
- (void)setBuyMonth:(short)value { buyMonth = value; }
- (short)buyMonth { return buyMonth; }

- (NSString *)grade
{
    if (issueFlags & mskMint) return @"Mint";
    if (issueFlags & mskFine) return @"Fine";
    if (issueFlags & mskMiss) return @"Missing";
    if (issueFlags & mskPoor) return @"Poor"; else return @"Good";
}
- (NSString *)ishtype
{
    if (issueFlags & mskComics  ) return @"Comics";
    if (issueFlags & mskNewForm ) return @"New Format";
    if (issueFlags & mskLuxe    ) return @"Luxe"; else return @"Magazine";
}
- (NSString *)content
{
    if (issueFlags & mskStory  ) return @"Story";
    if (issueFlags & mskInfo   ) return @"Information"; else return @"Reprint";
}

- (id)initWithCoder:(NSCoder *)coder
{
    short temp;
    [coder decodeValuesOfObjCTypes:"s", &temp];
    [self setIssueNumber:temp];
    [coder decodeValuesOfObjCTypes:"s", &temp];
    [self setEditMonth:temp];
    [coder decodeValuesOfObjCTypes:"s", &temp];
    [self setBuyMonth:temp];
    [coder decodeValuesOfObjCTypes:"s", &temp];
    [self setIssueFlags:temp];
    return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
    short temp;
    temp = [self issueNumber];
    [coder encodeValuesOfObjCTypes:"s", &temp];
    temp = [self editMonth];
    [coder encodeValuesOfObjCTypes:"s", &temp];
    temp = [self buyMonth];
    [coder encodeValuesOfObjCTypes:"s", &temp];
    temp = [self issueFlags];
    [coder encodeValuesOfObjCTypes:"s", &temp];
}

@end

@implementation CTitle

- (id)init
{
    return [self initWithAbb:@"ZZZ" withTitle:@"Zzzzzzzzzz" withFlag:-1];
}
- (id)initWithAbb:(NSString *)theAbb withTitle:(NSString *)theTitle withFlag:(short)flag
{
    if (self = [super init])
      {
        [self setAbb:theAbb];
        [self setTitle:theTitle];
        [self setTitleFlags:flag];
        [self setIssues:[NSMutableArray array]];
      }
    return self;
}
- (void)dealloc
{
    [abb release];
    [title release];
    [issues release];
    [super dealloc];
}

- (void)setTitleFlags:(short)value { titleFlags = value; }
- (short)titleFlags { return titleFlags; }
- (void)setAbb:(NSString *)value { [abb autorelease]; abb = [value copy]; }
- (NSString *)abb { return abb; }
- (void)setTitle:(NSString *)value { [title autorelease]; title = [value copy]; }
- (NSString *)title { return title; }
- (NSString *)brand { return (titleFlags & mskMarvel)?@"Marvel":((titleFlags & mskDC)?@"DC":@"Other"); }
- (NSString *)tstate { return (titleFlags & mskLive)?@"Live":@"Dead"; }
- (NSString *)series { return (titleFlags & mskLong)?@"Long":@"Mini"; }
- (NSString *)kind { return (titleFlags & mskMain)?@"Main":@"Dual"; }
- (short)nbIssues { return [issues count]; }
- (void)setIssues:(NSMutableArray *)theArray { [theArray retain]; [issues release]; issues = theArray; }
- (NSMutableArray *)issues { return issues; }

// Returns a NSString containing the list of the issues of this title with the following format:
// "1-66,94-345" meaning all issues between 1 and 66 (included) and all issues between 94 and 345.
// Optimized (other time) to the point where it's obfuscated... Sorry.
- (NSString *)listIssues
{
    NSString *result;
    long ref, oref, nref, diff, i = 0, n = [issues count];
    char str[1000], *starts, *s;
    starts = s = str;
    while (i < n)
      {
        nref = oref = ref = [[issues objectAtIndex:i] issueNumber];
        *((long *)s) = gnumstr.nstrs[ref];
        s += gnumstr.lens[ref];
        while ((++i < n) && ((ref+1) == (nref = [[issues objectAtIndex:i] issueNumber]))) ref = nref;
        if ((diff = (ref - oref)) > 0)
          {
            *s++ = (diff > 1)?'-':',';
            *((long *)s) = gnumstr.nstrs[ref];
            s += gnumstr.lens[ref];
          }
        *s++ = ',';
      }
    if (s != starts) *--s = 0; else *s = 0;
    result = [[NSString alloc] initWithCString:str];
    [result autorelease];
    return result;
}

- (id)initWithCoder:(NSCoder *)coder
{
    short temp;
    [self setAbb: [coder decodeObject]];
    [self setTitle: [coder decodeObject]];
    [self setIssues: [coder decodeObject]];
    [coder decodeValuesOfObjCTypes:"s", &temp];
    [self setTitleFlags:temp];
    return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
    short temp = [self titleFlags];
    [coder encodeObject: [self abb]];
    [coder encodeObject: [self title]];
    [coder encodeObject: [self issues]];
    [coder encodeValuesOfObjCTypes:"s", &temp];
}

- (short)findIssue:(short)byIshNum
{
    int i, j = -1, found = 0, n = [issues count];
    for(i = 0; (i < n) && (!found); i++)
        if (found = ([[issues objectAtIndex:i] issueNumber] == byIshNum)) j = i;
    return j;
}
- (short)addIssue:(CIssue *)theIssue
{
    int i, j = -1, ok = 1, n = [issues count];
    CIssue *thisIssue;
    for(i = 0; (i < n) && ok; i++)
      {
        ok = ([(thisIssue = [issues objectAtIndex:i]) issueNumber] != [theIssue issueNumber]);
        if (j == -1) if ([theIssue editMonth] < [thisIssue editMonth]) j = i;
      }
    if (ok)
      {
        [comicsBase setIssue:theIssue];
        [comicsBase modNbIssues:1];
        if (j == -1) [issues addObject:theIssue];
        else [issues insertObject:theIssue atIndex:j];
        return 0;
      }
    else return -1;
}
- (short)modIssue:(CIssue *)oldIssue withNewIssue:(CIssue *)newIssue
{
    short theIndex;
    if ([oldIssue issueNumber] != [newIssue issueNumber]) return -2;
    if ((theIndex = [self findIssue:[oldIssue issueNumber]]) == -1) return -1;
    [comicsBase setIssue:newIssue];
    [issues replaceObjectAtIndex:theIndex withObject:newIssue];
#if debug
    NSLog(@"CTitle::modIssue");
#endif
    return 0;
}
- (short)deleteIssue:(CIssue *)theIssue
{
    short theIndex;
    if ((theIndex = [self findIssue:[theIssue issueNumber]]) == -1) return -1;
    [comicsBase modNbIssues:-1];
    [issues removeObjectAtIndex:theIndex];
    return 0;
}

// Comparison methods used in sortArray of CComics
- (NSComparisonResult)compareAbb:(CTitle *)aTitle { return [abb compare:[aTitle abb]]; }
- (NSComparisonResult)compareTitle:(CTitle *)aTitle { return [title compare:[aTitle title]]; }
- (NSComparisonResult)compareChrono:(CTitle *)aTitle
{
    short firstEdit = [[issues objectAtIndex:0] editMonth];
    short theOtherFirstEdit = [[[aTitle issues] objectAtIndex:0] editMonth];
    if (firstEdit == theOtherFirstEdit) return NSOrderedSame;
    else if (firstEdit < theOtherFirstEdit) return NSOrderedAscending;
    else return NSOrderedDescending;
}
- (NSComparisonResult)compareMaxIssue:(CTitle *)aTitle
{
    short lastMax = [[issues lastObject] issueNumber];
    short theOtherLastMax = [[[aTitle issues] lastObject] issueNumber];
    if (lastMax == theOtherLastMax) return NSOrderedSame;
    else if (lastMax < theOtherLastMax) return NSOrderedDescending;
    else return NSOrderedAscending;
}

@end

char strdate[10];

@implementation CComics

+ (char *)cStrDate:(short)theMonth
{
    strcpy(strdate, gmonths[(theMonth-1)%12]);
    strcat(strdate, gnums[(theMonth-1)/12]);
    return strdate;
}
+ (NSString *)nsStrDate:(short)theMonth
{
    return [NSString stringWithCString:[CComics cStrDate:theMonth]];
}

// Private helper to get database path
- (NSString *)dbPath
{
    NSMutableString *result = [[NSMutableString alloc] initWithString:[[NSBundle mainBundle] bundlePath]];
    [result autorelease];
    [result appendString:@"/ComicsDataBase"];

#if debug
    NSLog(@"dbPath = '%@'", result);
#endif

    return result;
}

// Fills the global arrays to speed up number to string conversion
- (void)initGlobals
{
    long i, j, n, *pi, *pl;
    char ns[] = "0123456789";
    char s[5], *p1, *p2;

    *(pi = &gnumstr.lens[0]) = 1;
    pl = &gnumstr.nstrs[0];
    *(p1 = (char *)pl) = '0';
    for(i=1; i<1001; i++)
      {
        j = i; p1 = s; n = 0;
        while (j>0)
          {
            *p1++ = ns[j % 10];
            j = j / 10;
            n++;
          }
        *++pi = n;
        p2 = (char *)(++pl);
        while (n--) *p2++ = *--p1;
      }
    strcpy(gmonths[ 0], "JAN");
    strcpy(gmonths[ 1], "FEB");
    strcpy(gmonths[ 2], "MAR");
    strcpy(gmonths[ 3], "APR");
    strcpy(gmonths[ 4], "MAY");
    strcpy(gmonths[ 5], "JUN");
    strcpy(gmonths[ 6], "JUL");
    strcpy(gmonths[ 7], "AUG");
    strcpy(gmonths[ 8], "SEP");
    strcpy(gmonths[ 9], "OCT");
    strcpy(gmonths[10], "NOV");
    strcpy(gmonths[11], "DEC");
    for(i=0; i<=9; i++)
      {gnums[i][0] = 32; gnums[i][1] = 32;
      gnums[i][2] = ns[i]; gnums[i][3] = 0;}
    for(i=1; i<=9; i++) for(j=0; j<= 9; j++)
      {gnums[i*10+j][0] = 32; gnums[i*10+j][1] = ns[i];
      gnums[i*10+j][2] = ns[j]; gnums[i*10+j][3] = 0;}
    for(i=1; i<=9; i++) for(j=0; j<= 9; j++) for(n=0; n<=9; n++)
      {gnums[i*100+j*10+n][0] = ns[i]; gnums[i*100+j*10+n][1] = ns[j];
      gnums[i*100+j*10+n][2] = ns[n]; gnums[i*100+j*10+n][3] = 0;}
}

// Private method to convert the Mac database which I already have.
// Better than reenter 22,000+ issues...
- (void)convertFromMacFormat
{
    CTitle *theTitle;
    CIssue *theIssue;
    short i, j, nbt, nbi, flag, ish, edit, buy;
    unsigned char *buf, sl;
    long pos, pos2;
    char thestring[100];
    NSString *theAbbStr, *theTitleStr;
    NSMutableString *fileName = [[NSMutableString alloc] initWithString:[[NSBundle mainBundle] pathForResource:@"Comics.mac" ofType:@""]];
    NSFileManager *nsfm = [NSFileManager defaultManager];
    NSData *data = [nsfm contentsAtPath:fileName];
    buf = (unsigned char *)[data bytes];

#if debug
    NSLog(@"fileName = '%@'", fileName);
#endif

    nbt = (buf[0] << 8) | buf [1];
#if debug
    NSLog(@"nbt = %d", nbt);
#endif
    pos = 14 + (4 * nbt);
    for(i = 0; i < nbt; i++)
      {
        sl = buf[pos]; pos2 = pos+1;
        for(j = 0; j < sl; j++) thestring[j] = buf[pos2++];
        thestring[sl] = 0;
        theAbbStr = [[NSString alloc] initWithCString:thestring];
        pos += 6;
        sl = buf[pos]; pos2 = pos+1;
        for(j = 0; j < sl; j++) thestring[j] = buf[pos2++];
        thestring[sl] = 0;
        theTitleStr = [[NSString alloc] initWithCString:thestring];
        pos += 50;
        flag = (buf[pos] << 8) | buf [pos+1]; pos += 2;
        theTitle = [[CTitle alloc] initWithAbb:theAbbStr withTitle:theTitleStr withFlag:flag];
        [comicsBase addTitle:theTitle];
        [theTitle release];
        [theAbbStr release];
        [theTitleStr release];
        nbi = (buf[pos] << 8) | buf [pos+1]; pos += 2;
        for(j = 0; j < nbi; j++)
          {
            ish = (buf[pos] << 8) | buf [pos+1]; pos += 2;
            edit = (buf[pos] << 8) | buf [pos+1]; pos += 2;
            buy = (buf[pos] << 8) | buf [pos+1]; pos += 2;
            flag = (buf[pos] << 8) | buf [pos+1]; pos += 2;
            theIssue = [[CIssue alloc] initWithIsh:ish withEdit:edit withBuy:buy withFlag:flag];
            [theTitle addIssue:theIssue];
            [theIssue release];
          }
      }
    [fileName release];
}

// if realLoad is 1, then get the data from the Yellow archived database using NSUnarchiver
// if realLoad is 0, then get the data from the Mac database
#define realLoad 0
#define withConvert 1
- (id)init
{
    if (self = [super init])
      {
        [self initGlobals];
        [self reset];

#if realLoad
        self = [NSUnarchiver unarchiveObjectWithFile:[self dbPath]];
        [self retain];
        comicsBase = self;
#else
        [self setTitles:[NSMutableArray array]];
        comicsBase = self;
#if withConvert
        [self convertFromMacFormat];
#endif
        [self save:nil];
#endif

#if debug
        NSLog(@"startEditMonth = %d", startEditMonth);
        NSLog(@"lastEditMonth = %d", lastEditMonth);
        NSLog(@"startBuyMonth = %d", startBuyMonth);
        NSLog(@"lastBuyMonth = %d", lastBuyMonth);
#endif
      }
    return self;
}
- (void)dealloc
{
    [titles release];
    [super dealloc];
}

- (void)reset
{
    NSCalendarDate *date = [NSCalendarDate calendarDate];
    nbIssues = 0;
    maxIssue = -1;
    startBuyMonth = [date monthOfYear] + 12 * ([date yearOfCommonEra] -1900);
    lastBuyMonth = startBuyMonth - 6;
    startEditMonth = startBuyMonth + 3;
    lastEditMonth = lastBuyMonth;
}
- (void)setIssue:(CIssue *)theIssue
{
    if ([theIssue issueNumber] > maxIssue) maxIssue = [theIssue issueNumber];
    if ([theIssue editMonth] < startEditMonth) startEditMonth = [theIssue editMonth];
    if ([theIssue editMonth] > lastEditMonth) lastEditMonth = [theIssue editMonth];
    if (!([theIssue issueFlags] & mskMiss))
        if ([theIssue buyMonth] < startBuyMonth) startBuyMonth = [theIssue buyMonth];
    if ([theIssue buyMonth] > lastBuyMonth) lastBuyMonth = [theIssue buyMonth];
}
- (void)setAll
{
    int i, j, n = [titles count], n2;
    NSMutableArray* theseIssues;
    [self reset];
    for(i = 0; i < n; i++)
      {
        CTitle *thisTitle = [titles objectAtIndex:i];
        nbIssues += [thisTitle nbIssues];
        theseIssues = [thisTitle issues];
        n2 = [theseIssues count];
        for(j = 0; j < n2; j++)
          {
            CIssue *thisIssue = [theseIssues objectAtIndex:j];
            [self setIssue:thisIssue];
          }
      }
}

- (void)modNbIssues:(short)delta { nbIssues += delta; }
- (short)nbIssues { return nbIssues; }
- (short)maxIssue { return maxIssue; }
- (short)startEditMonth { return startEditMonth; }
- (short)lastEditMonth { return lastEditMonth; }
- (short)startBuyMonth { return startBuyMonth; }
- (short)lastBuyMonth { return lastBuyMonth; }
- (short)nbTitles { return [titles count]; }
- (NSMutableArray *)titles { return titles; }
- (void)setTitles:(NSMutableArray *)theArray
{
    [theArray retain];
    [titles release];
    titles = theArray;
}

- (void)save:(id)sender
{
    [NSArchiver archiveRootObject:self toFile:[self dbPath]];
}
- (id)initWithCoder:(NSCoder *)coder
{
    [self setTitles: [coder decodeObject]];
    [self setAll];
    return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
    [coder encodeObject: [self titles]];
}

// This method first clears the array which is passed in as a parameter,
// then fills it only with the titles that conform to the criteria (also passed in as parameters),
// and then sort those title using the comparison methods of CTitle
- (void)sortArray:(NSMutableArray *)theArray withBrand:(short)brand withSeries:(short)series withKind:(short)kind withState:(short)state withSort:(short)sort
{
    int i, ok, n = [titles count];
    CTitle* thisTitle;
    short brands[2] = {mskMarvel, mskDC | mskOther};
    short seriesa[2] = {mskLong, mskMini};
    short states[2] = {mskDead, mskLive};
    short  kinds[2] = {mskMain, mskDual};
    
    // clear
    [theArray removeAllObjects];

    // fill
    for(i = 0; i < n; i++)
      {
        thisTitle = [titles objectAtIndex:i];
        ok = (brand == 0) || ([thisTitle titleFlags] & brands[brand-1]);
        if (ok) ok = (series == 0) || ([thisTitle titleFlags] & seriesa[series-1]);
        if (ok) ok = (state == 0) || ([thisTitle titleFlags] & states[state-1]);
        if (ok) ok = (kind == 0) || ([thisTitle titleFlags] & kinds[kind-1]);
        if (ok) [theArray addObject:thisTitle];
      }

    // sort
    switch(sort)
      {
        case 0: [theArray sortUsingSelector:@selector(compareAbb:)]; break;
        case 1: [theArray sortUsingSelector:@selector(compareTitle:)]; break;
        case 2: [theArray sortUsingSelector:@selector(compareChrono:)]; break;
        case 3: [theArray sortUsingSelector:@selector(compareMaxIssue:)]; break;
      }
}

- (short)findTitleByAbb:(NSString *)theAbb
{
    int i, j = -1, found = 0, n = [titles count];
    for(i = 0; (i < n) && (!found); i++)
        if (found = [[[titles objectAtIndex:i] abb] isEqualToString:theAbb]) j = i;
    return j;
}
- (short)findTitleByTitle:(NSString *)theTitle
{
    int i, j = -1, found = 0, n = [titles count];
    for(i = 0; (i < n) && (!found); i++)
        if (found = [[[titles objectAtIndex:i] title] isEqualToString:theTitle]) j = i;
    return j;
}
- (short)addTitle:(CTitle *)theTitle
{
    if ([self findTitleByAbb:[theTitle abb]] != -1) return -1;
    if ([self findTitleByTitle:[theTitle title]] != -1) return -2;
    [titles addObject:theTitle];
    return 0;
}
- (short)modTitle:(CTitle *)oldTitle withNewTitle:(CTitle *)newTitle
{
    short theIndex = [self findTitleByAbb:[oldTitle abb]];
    if (theIndex == -1) return -1;
    [newTitle setIssues:[oldTitle issues]];
    [titles replaceObjectAtIndex:theIndex withObject:newTitle];
    return 0;
}
- (short)deleteTitle:(CTitle *)theTitle
{
    short theIndex = [self findTitleByAbb:[theTitle abb]];
    if (theIndex == -1) return -1;
    [comicsBase modNbIssues:-[theTitle nbIssues]];
    [titles removeObjectAtIndex:theIndex];
    return 0;
}

@end