/*
    DAAC_FilenameUtilities.m

    Copyright 2008,2009 Josh Freeman
    http://www.twilightedge.com

    This file is part of DAA Converter v1.3 for Mac OS X.

    DAA Converter is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#import "DAAC_FilenameUtilities.h"


#define kDAAFileExtensionsList                      @"daa", @"gbi", nil // must end with nil
#define kISOFileExtension                           @"iso"

#define kSplitArchivePartExtensionPrefix            @"part"

#define kMaxNumTriesToFindNonexistantFilepath       99
#define kAlternateFilenameTemplate                  @"%@-%d"


static bool InitializeFilenameGlobals(void);
static bool IsDAAFilenamePartOfSplitArchive(NSString *filename);
static bool IsSplitArchiveExtension(NSString *extension);
static NSString *PrimarySplitArchiveExtensionForSplitArchiveExtension(NSString *extension);


static NSCharacterSet *gNonDigitCharacterSet = nil;
static NSArray *gDAAFileExtensions;
static int gLengthOfSplitArchivePartExtensionPrefix = 0, gNumDAAFileExtensions = 0;
static bool gFilenameGlobalsAreInitialized = NO;


static bool InitializeFilenameGlobals(void)
{
    NSCharacterSet *nonDigitCharacterSet;
    NSArray *daaFileExtensions;

    if (gFilenameGlobalsAreInitialized)
        return YES;

    nonDigitCharacterSet = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
    daaFileExtensions = [NSArray arrayWithObjects: kDAAFileExtensionsList];

    if (!nonDigitCharacterSet || ![daaFileExtensions count])
        return NO;

    gNonDigitCharacterSet = [nonDigitCharacterSet retain];
    gDAAFileExtensions = [daaFileExtensions retain];

    gLengthOfSplitArchivePartExtensionPrefix = [kSplitArchivePartExtensionPrefix length];
    gNumDAAFileExtensions = [gDAAFileExtensions count];

    gFilenameGlobalsAreInitialized = YES;

    return YES;
}

NSArray *DAAFileExtensions(void)
{
    if (!gFilenameGlobalsAreInitialized)
        InitializeFilenameGlobals();

    return gDAAFileExtensions;
}

bool IsDAAFilename(NSString *filename)
{
    NSString *extension;
    int i;

    if (!gFilenameGlobalsAreInitialized && !InitializeFilenameGlobals())
        return NO;

    extension = [filename pathExtension];

    if (![extension length])
        return NO;

    for (i=0; i<gNumDAAFileExtensions; i++)
    {
        if ([extension compare: [gDAAFileExtensions objectAtIndex: i]
                        options: NSCaseInsensitiveSearch]
                == NSOrderedSame)
        {
            return YES;
        }
    }

    return NO;
}

// PrimaryDAAFilepathForDAAFilepath() receives the path of a DAA file - if the file is a
// single-file DAA archive, it returns the same path; if the file is part of a multi-file
// split archive, it returns the path to the archive's primary (first) file

NSString *PrimaryDAAFilepathForDAAFilepath(NSString *daaFilepath)
{
    NSString *daaFilename, *extensionlessFilename, *daaExtension;
    NSString *splitArchiveExtension;

    daaFilename = [daaFilepath lastPathComponent];

    if (!IsDAAFilename(daaFilename))
        goto ERROR;

    if (!IsDAAFilenamePartOfSplitArchive(daaFilename))
        return daaFilepath;

    daaExtension = [daaFilename pathExtension];

    extensionlessFilename = [daaFilename stringByDeletingPathExtension];
    splitArchiveExtension = [extensionlessFilename pathExtension];

    extensionlessFilename = [extensionlessFilename stringByDeletingPathExtension];

    splitArchiveExtension =
                PrimarySplitArchiveExtensionForSplitArchiveExtension(splitArchiveExtension);

    if (!daaExtension || !splitArchiveExtension || !extensionlessFilename)
        goto ERROR;

    daaFilename = [[extensionlessFilename
                        stringByAppendingPathExtension: splitArchiveExtension]
                            stringByAppendingPathExtension: daaExtension];

    if (!daaFilename)
        goto ERROR;

    return [[daaFilepath stringByDeletingLastPathComponent]
                                stringByAppendingPathComponent: daaFilename];

ERROR:
    return nil;
}

// IsDAAFilenamePartOfSplitArchive() returns YES if the input filename fits the pattern for
// a split-archive DAA file (the daa file extension is preceded by a split-archive part
// extension, ex: "filename.part01.daa")

static bool IsDAAFilenamePartOfSplitArchive(NSString *filename)
{
    if (IsDAAFilename(filename)
        && IsSplitArchiveExtension([[filename stringByDeletingPathExtension] pathExtension]))
    {
        return YES;
    }

    return NO;
}

// IsSplitArchiveExtension() returns YES if the input string fits the pattern for a
// split-archive part extension: "partX", where 'X' is one or more digits;
// ex: "Part9", "part004"

static bool IsSplitArchiveExtension(NSString *extension)
{
    if (!gFilenameGlobalsAreInitialized && !InitializeFilenameGlobals())
        return NO;

    if (([extension length] > gLengthOfSplitArchivePartExtensionPrefix)
            && ([extension compare: kSplitArchivePartExtensionPrefix
                            options: NSCaseInsensitiveSearch
                            range: NSMakeRange(0, gLengthOfSplitArchivePartExtensionPrefix)]
                    == NSOrderedSame)
            && (![[extension substringFromIndex: gLengthOfSplitArchivePartExtensionPrefix]
                        rangeOfCharacterFromSet: gNonDigitCharacterSet].length))
    {
        return YES;
    }

    return NO;
}

// PrimarySplitArchiveExtensionForSplitArchiveExtension() receives a split-archive part
// extension and returns an extension corresponding to the primary (first) part by changing
// the value of the trailing digits to 1 (with leading zeros to preserve the number of digits);
// ex: "Part9" -> "Part1", "part004" -> "part001"

static NSString *PrimarySplitArchiveExtensionForSplitArchiveExtension(NSString *extension)
{
    NSRange rangeOfLastNonDigitChar;
    int numDigits;
    NSString *nondigitsPrefix, *trailingDigitsString, *trailingDigitsStringTemplate;

    if ((!gFilenameGlobalsAreInitialized && !InitializeFilenameGlobals())
        || !IsSplitArchiveExtension(extension))
    {
        goto ERROR;
    }

    rangeOfLastNonDigitChar = [extension rangeOfCharacterFromSet: gNonDigitCharacterSet
                                                        options: NSBackwardsSearch];

    if (!rangeOfLastNonDigitChar.length)
        goto ERROR;

    numDigits = [extension length] - rangeOfLastNonDigitChar.location - 1;

    if (numDigits <= 0)
        goto ERROR;

    trailingDigitsStringTemplate = [NSString stringWithFormat: @"%%0%dd", numDigits];

    if (!trailingDigitsStringTemplate)
        goto ERROR;

    nondigitsPrefix = [extension substringToIndex: rangeOfLastNonDigitChar.location + 1];

    trailingDigitsString = [NSString stringWithFormat: trailingDigitsStringTemplate, 1];

    if (!nondigitsPrefix || !trailingDigitsString)
        goto ERROR;

    return [nondigitsPrefix stringByAppendingString: trailingDigitsString];

ERROR:
    return nil;
}

NSString *ISOFilenameForDAAFilename(NSString *daaFilename)
{
    NSString *extensionlessFilename;

    if (!IsDAAFilename(daaFilename))
        goto ERROR;

    extensionlessFilename = [daaFilename stringByDeletingPathExtension];

    if (IsDAAFilenamePartOfSplitArchive(daaFilename))
        extensionlessFilename = [extensionlessFilename stringByDeletingPathExtension];

    if (![extensionlessFilename length])
        goto ERROR;

    return [extensionlessFilename stringByAppendingPathExtension: kISOFileExtension];

ERROR:
    return nil;
}

NSString *PathToNonexistentFileInDirectoryWithPreferredFilename(NSString *directoryPath,
                                                                NSString *filename)
{
    NSFileManager *fileManager;
    NSString *filepath, *extensionlessFilename, *extension;
    int counter;
    BOOL isDirectory;

    if (![directoryPath length] || ![filename length])
        goto ERROR;

    filepath = [directoryPath stringByAppendingPathComponent: filename];
    fileManager = [NSFileManager defaultManager];

    if (!filepath
        || ![fileManager fileExistsAtPath: directoryPath isDirectory: &isDirectory]
        || !isDirectory)
    {
        goto ERROR;
    }

    if (![fileManager fileExistsAtPath: filepath])
        return filepath;

    extensionlessFilename = [filename stringByDeletingPathExtension];
    extension = [filename pathExtension];

    if (![extensionlessFilename length] || ![extension length])
        goto ERROR;

    for (counter = 1; counter <= kMaxNumTriesToFindNonexistantFilepath; counter++)
    {
        filename = [[NSString stringWithFormat: kAlternateFilenameTemplate,
                                                    extensionlessFilename, counter]
                            stringByAppendingPathExtension: extension];

        if (!filename)
            goto ERROR;

        filepath = [directoryPath stringByAppendingPathComponent: filename];

        if (!filepath)
            goto ERROR;

        if (![fileManager fileExistsAtPath: filepath])
            return filepath;
    }

ERROR:
    return nil;
}

