/*
    FrameworkProjectType.m

    Implementation of the FrameworkProjectType class for the
    ProjectManager application.

    Copyright (C) 2005, 2006  Saso Kiselkov

    This program 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, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#import "FrameworkProjectType.h"

#import <Foundation/NSArray.h>
#import <Foundation/NSBundle.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSFileManager.h>
#import <Foundation/NSNotification.h>
#import <Foundation/NSSet.h>
#import <Foundation/NSString.h>
#import <Foundation/NSValue.h>

#import <AppKit/NSImage.h>
#import <AppKit/NSPanel.h>

#import "../../CommonFunctions/MakefileChecking.h"

#import "FrameworkGeneralAttributes.h"
#import "../../NSImageAdditions.h"
#import "../../ProjectDocument.h"
#import "../../ProjectModule.h"
#import "../../ProjectModules/FileManager/FileManager.h"
#import "../../ProjectModules/MakeBuilder/MakeBuilder.h"
#import "../../ProjectModules/SubprojectsManager/SubprojectsManager.h"

static inline void
InsertMakefileVariable(NSMutableString * makefileString,
                       NSString * name,
                       NSString * value)
{
  [makefileString appendString: [NSString stringWithFormat:
    @"\n"
    @"%@ = %@\n", name, value]];
}

static void
InsertMakefileEnumerationVariable(NSMutableString * makefileString,
                                  NSString * name,
                                  NSArray * values)
{
  NSEnumerator * e;
  NSString * value;

  [makefileString appendString: [NSString stringWithFormat:
    @"\n"
    @"%@ = ", name]];
  e = [values objectEnumerator];
  while ((value = [e nextObject]) != nil)
    {
      [makefileString appendString: [NSString stringWithFormat: @"%@ \\\n",
        value]];
    }
}

/**
 * Returns a string containg `string' prefixed with a '/' character.
 */
static inline NSString *
PreSlash(NSString * string)
{
  return [NSString stringWithFormat: @"/%@", string];
}

static NSString
  * const ClassFilesCategoryName = @"Class Files",
  * const HeaderFilesCategoryName = @"Header Files",
  * const OtherSourceFilesCategoryName = @"Other Source Files",
  * const InterfaceFilesCategoryName = @"Interfaces",
  * const ResourceFilesCategoryName = @"Resource Files",
  * const LocalizedResourceFilesCategoryName = @"Localized Resource Files",
  * const SupportingFilesCategoryName = @"Supporting Files",
  * const FrameworksCategoryName = @"Frameworks";

@interface FrameworkProjectType (Private)

+ (void) loadCategoryIcons;

- (BOOL) prepareMakefile;
- (BOOL) prepareInfoFile;

@end

static NSImage * ClassFilesIcon = nil,
               * HeaderFilesIcon = nil,
               * InterfaceFilesIcon = nil,
               * OtherSourceFilesIcon = nil,
               * ResourceFilesIcon = nil,
               * LocalizedResourceFilesIcon = nil,
               * SupportingFilesIcon = nil;

@implementation FrameworkProjectType (Private)

/**
 * Loads all icons for standard categories.
 */
+ (void) loadCategoryIcons
{
  NSBundle * myBundle = [NSBundle bundleForClass: self];

  ASSIGN(ClassFilesIcon, [NSImage imageNamed: @"ClassFiles" owner: self]);
  ASSIGN(HeaderFilesIcon, [NSImage imageNamed: @"HeaderFiles" owner: self]);
  ASSIGN(InterfaceFilesIcon, [NSImage imageNamed: @"InterfaceFiles"
                                           owner: self]);
  ASSIGN(OtherSourceFilesIcon, [NSImage imageNamed: @"OtherSourceFiles"
                                             owner: self]);
  ASSIGN(ResourceFilesIcon, [NSImage imageNamed: @"ResourceFiles"
                                          owner: self]);
  ASSIGN(LocalizedResourceFilesIcon, [NSImage
    imageNamed: @"LocalizedResourceFiles" owner: self]);
  ASSIGN(SupportingFilesIcon, [NSImage imageNamed: @"SupportingFiles"
                                            owner: self]);
}

/**
 * Prepares the project's makefile.
 */
- (BOOL) prepareMakefile
{
  NSString * buildDir = [owner projectDirectory];
  NSString * makefile = [buildDir stringByAppendingPathComponent:
    @"GNUmakefile"];
  NSMutableString * makefileString = [NSMutableString string];
  NSString * projectName = [owner projectName];
  NSMutableSet * languages;
  NSMutableSet * localizedResourceFiles;

  if (oldMakeHash != 0)
    {
      if (oldMakeHash != ComputeHashFromMakefile(makefile))
        {
          switch (NSRunAlertPanel(_(@"Warning: GNUmakefile changed"),
            _(@"It seems the build settings in the GNUmakefile have been\n"
              @"edited since I've last regenerated it. You shouldn't do\n"
              @"this. Please, put build customizations in GNUmakefile."
                @"preamble\n"
              @"or GNUmakefile.postamble. For now, I can backup your current\n"
              @"GNUmakefile (it will be named GNUmakefile~) or overwrite it."),
            _(@"Back it up"), _(@"Overwrite"), _(@"Cancel")))
            {
            case NSAlertDefaultReturn:
              if (![[NSFileManager defaultManager]
                movePath: makefile
                  toPath: [makefile stringByAppendingString: @"~"]
                 handler: nil])
                {
                  NSRunAlertPanel(_(@"Backup failed"),
                    _(@"Failed to move the old GNUmakefile to GNUmakefile~.\n"
                      @"Safety stop."), nil, nil, nil);

                  return NO;
                }
              break;
            case NSAlertAlternateReturn:
              break;
            case NSAlertOtherReturn:
              return NO;
            }
        }
    }

  [makefileString appendString: [NSString stringWithContentsOfFile:
    [[NSBundle bundleForClass: [self class]] pathForResource:
    @"MakefileEditWarning" ofType: @"txt"]]];

  [makefileString appendString:
    @"\n"
    @"include $(GNUSTEP_MAKEFILES)/common.make\n"];

  // prepend some definitions to turn on compiler warnings as necessary
  [makefileString appendString:
    @"\n"
    @"ifeq ($(warnings), yes)\n"
    @"ADDITIONAL_OBJCFLAGS += -W\n"
    @"ADDITIONAL_OBJCPPFLAGS += -W\n"
    @"ADDITIONAL_CFLAGS += -W\n"
    @"ADDITIONAL_CPPFLAGS += -W\n"
    @"endif\n"
    @"ifeq ($(allwarnings), yes)\n"
    @"ADDITIONAL_OBJCFLAGS += -Wall\n"
    @"ADDITIONAL_OBJCPPFLAGS += -Wall\n"
    @"ADDITIONAL_CFLAGS += -Wall\n"
    @"ADDITIONAL_CPPFLAGS += -Wall\n"
    @"endif\n"];

  if (buildsSharedLibrary)
    {
      InsertMakefileVariable(makefileString, @"LIBRARY_NAME", projectName);
    }
  else
    {
      InsertMakefileVariable(makefileString, @"FRAMEWORK_NAME", projectName);

      if (versionName != nil)
        {
          InsertMakefileVariable(makefileString, [NSString stringWithFormat:
            @"%@_CURRENT_VERSION_NAME", projectName], versionName);
        }
      InsertMakefileVariable(makefileString, [NSString stringWithFormat:
            @"%@_MAKES_CURRENT_VERSION", projectName], makesCurrentVersion ?
            @"yes" : @"no");
    }

  // OBJC_FILES, OBJCC_FILES and C_FILES
  {
    NSArray * sourceFiles = [[fileManager
      filesAtPath: PreSlash(ClassFilesCategoryName)
           ofType: FMFileTypePlain
        recursive: YES]
      arrayByAddingObjectsFromArray: [fileManager
      filesAtPath: PreSlash(OtherSourceFilesCategoryName)
           ofType: FMFileTypePlain
        recursive: YES]];
    unsigned int n = [sourceFiles count];
    NSMutableArray * objcFiles = [NSMutableArray arrayWithCapacity: n],
                   * objccFiles = [NSMutableArray arrayWithCapacity: n],
                   * cFiles = [NSMutableArray arrayWithCapacity: n];
    NSEnumerator * e;
    NSString * filename;

    // filter out files by extension
    e = [sourceFiles objectEnumerator];
    while ((filename = [e nextObject]) != nil)
      {
        if (![[filename pathExtension] caseInsensitiveCompare: @"m"])
          {
            [objcFiles addObject: filename];
          }
        else if (![[filename pathExtension] caseInsensitiveCompare: @"mm"])
          {
            [objccFiles addObject: filename];
          }
        else if (![[filename pathExtension] caseInsensitiveCompare: @"c"])
          {
            [cFiles addObject: filename];
          }
      }

    InsertMakefileEnumerationVariable(makefileString,
      [NSString stringWithFormat: @"%@_OBJC_FILES", projectName],
      objcFiles);
    InsertMakefileEnumerationVariable(makefileString,
      [NSString stringWithFormat: @"%@_OBJCC_FILES", projectName],
      objccFiles);
    InsertMakefileEnumerationVariable(makefileString,
      [NSString stringWithFormat: @"%@_C_FILES", projectName],
      cFiles);
  }
  // HEADER_FILES
  {
    NSArray * headerFiles = [fileManager
      filesAtPath: PreSlash(HeaderFilesCategoryName)
           ofType: FMFileTypePlain
        recursive: YES];

    InsertMakefileEnumerationVariable(makefileString,
      [NSString stringWithFormat: @"%@_HEADER_FILES", projectName],
      headerFiles);
  }

  if (buildsSharedLibrary == NO)
    {
      // RESOURCE_FILES
      {
        NSArray * files = [fileManager
          filesAtPath: PreSlash(ResourceFilesCategoryName)
               ofType: FMFileTypePlain
            recursive: YES];
        NSMutableArray * resourceFiles = [NSMutableArray arrayWithCapacity:
          [files count]];
        NSEnumerator * e;
        NSString * filename;

        e = [files objectEnumerator];
        while ((filename = [e nextObject]) != nil)
          {
            [resourceFiles addObject: [@"Resources"
              stringByAppendingPathComponent: filename]];
          }

        InsertMakefileEnumerationVariable(makefileString,
          [NSString stringWithFormat: @"%@_RESOURCE_FILES", projectName],
          resourceFiles);
      }

      // LANGUAGES
      languages = [[NSMutableSet new] autorelease];

      [languages addObjectsFromArray: [fileManager
        filesAtPath: PreSlash(LocalizedResourceFilesCategoryName)
             ofType: FMFileTypeCategory
          recursive: YES]];
      [languages addObjectsFromArray: [fileManager
        filesAtPath: PreSlash(InterfaceFilesCategoryName)
             ofType: FMFileTypeCategory
          recursive: YES]];

      InsertMakefileEnumerationVariable(makefileString,
        [NSString stringWithFormat: @"%@_LANGUAGES", projectName],
        [languages allObjects]);

      // LOCALIZED_RESOURCE_FILES
      localizedResourceFiles = [[NSMutableSet new] autorelease];

      [localizedResourceFiles addObjectsFromArray:
        [fileManager filesAtPath: PreSlash(LocalizedResourceFilesCategoryName)
                          ofType: FMFileTypePlain
                       recursive: YES]];
      [localizedResourceFiles addObjectsFromArray:
        [fileManager filesAtPath: PreSlash(InterfaceFilesCategoryName)
                          ofType: FMFileTypePlain
                       recursive: YES]];

      InsertMakefileEnumerationVariable(makefileString,
        [NSString stringWithFormat: @"%@_LOCALIZED_RESOURCE_FILES", projectName],
        [localizedResourceFiles allObjects]);

      if (principalClass != nil)
        {
          InsertMakefileVariable(makefileString,
            [NSString stringWithFormat: @"%@_PRINCIPAL_CLASS", projectName],
            principalClass);
        }
    }

  {
    NSMutableArray * frameworks;
    NSArray * origFrameworks;
    NSString * framework;
    NSEnumerator * e;

    origFrameworks = [fileManager filesAtPath: PreSlash(FrameworksCategoryName)
                                       ofType: FMFileTypeVirtual
                                    recursive: YES];
    frameworks = [NSMutableArray arrayWithCapacity: [origFrameworks count]];

    e = [origFrameworks objectEnumerator];
    while ((framework = [e nextObject]) != nil)
      {
        if (![framework isEqualToString: @"Foundation.framework"] &&
            ![framework isEqualToString: @"AppKit.framework"])
          {
            // strip an initial 'lib' prefix
            if ([framework hasPrefix: @"lib"])
              {
                framework = [framework stringByDeletingPrefix: @"lib"];
              }

            [frameworks addObject: [NSString
              stringWithFormat: @"-l%@", [framework
              stringByDeletingPathExtension]]];
          }
      }

    InsertMakefileEnumerationVariable(makefileString, [NSString
      stringWithFormat: @"%@_LDFLAGS", projectName], frameworks);
  }

  // SUBPROJECTS
  {
    SubprojectsManager * subprojectsManager = [owner
      projectModuleWithName: @"SubprojectsManager"];
    NSArray * subprojectNames = [subprojectsManager subprojectNames];
    NSEnumerator * e;
    NSString * subprojectName;
    NSMutableArray * subprojects = [NSMutableArray arrayWithCapacity:
      [subprojectNames count]];

    e = [subprojectNames objectEnumerator];
    while ((subprojectName = [e nextObject]) != nil)
      {
        [subprojects addObject: [@"Subprojects"
          stringByAppendingPathComponent: subprojectName]];
      }

    InsertMakefileEnumerationVariable(makefileString,
                                      @"SUBPROJECTS",
                                      subprojects);
  }

  if (buildsSharedLibrary)
    {
      [makefileString appendString:
        @"\n"
        @"-include GNUmakefile.preamble\n"
        @"include $(GNUSTEP_MAKEFILES)/library.make\n"
        @"include $(GNUSTEP_MAKEFILES)/aggregate.make\n"
        @"-include GNUmakefile.postamble\n"];
    }
  else
    {
      [makefileString appendString:
        @"\n"
        @"-include GNUmakefile.preamble\n"
        @"include $(GNUSTEP_MAKEFILES)/framework.make\n"
        @"include $(GNUSTEP_MAKEFILES)/aggregate.make\n"
        @"-include GNUmakefile.postamble\n"];
    }

  oldMakeHash = ComputeHashFromMakeString(makefileString);

  return [makefileString writeToFile: makefile atomically: NO];
}

/**
 * Prepares the project's info-plist file.
 */
- (BOOL) prepareInfoFile
{
  NSString * infoPath = [[owner projectDirectory]
    stringByAppendingPathComponent: [[[owner projectName]
    stringByAppendingString: @"Info"]
    stringByAppendingPathExtension: @"plist"]];
  NSMutableDictionary * infoDict;

  // try reading in an already existant <Project>Info.plist file
  infoDict = [[[NSDictionary dictionaryWithContentsOfFile: infoPath]
    mutableCopy] autorelease];
  if (infoDict == nil)
    {
      infoDict = [NSMutableDictionary dictionary];
    }

  [infoDict setObject: [owner projectName] forKey: @"FrameworkName"];
  if (authors != nil)
    {
      [infoDict setObject: authors forKey: @"Authors"];
    }
  if (copyright != nil)
    {
      [infoDict setObject: copyright forKey: @"Copyright"];
    }
  if (copyrightDescription != nil)
    {
      [infoDict setObject: copyrightDescription
                   forKey: @"CopyrightDescription"];
    }

  return [infoDict writeToFile: infoPath atomically: YES];
}

@end

@implementation FrameworkProjectType

+ (NSString *) projectTypeID
{
  return @"Framework";
}

+ (NSString *) humanReadableProjectTypeName
{
  return _(@"Framework");
}

+ (NSString *) projectTypeDescription
{
  return [NSString stringWithContentsOfFile:
    [[NSBundle bundleForClass: self]
    pathForResource: @"Description" ofType: @"txt"]];
}

+ (NSImage *) projectTypeIcon
{
  static NSImage * icon = nil;

  if (icon == nil)
    {
      ASSIGN(icon, [NSImage imageNamed: @"FrameworkProjectType" owner: self]);
    }

  return icon;
}

+ (NSArray *) projectModules
{
  return [NSArray arrayWithObjects:
    @"ProjectAttributes",
    @"FileManager",
    @"MakeBuilder",
//    @"ProjectIndexer",
    @"SubprojectsManager",
    nil];
}

+ (NSDictionary *) projectTemplateDescriptions
{
  NSBundle * myBundle = [NSBundle bundleForClass: self];

  return [NSDictionary dictionaryWithObjectsAndKeys:
    PMCreateTemplateDescription(_(@"An empty framework project."),  [NSImage
      imageNamed: @"FrameworkProjectType" owner: self]), _(@"Empty Framework"),
    PMCreateTemplateDescription(_(@"An empty library project."), [NSImage
      imageNamed: @"EmptyLibraryTemplate" owner: self]), _(@"Empty Library"),
    nil];
}

+ (NSString *) pathToProjectTemplate: (NSString *) templateName
{
  NSBundle * myBundle = [NSBundle bundleForClass: self];

  if ([templateName isEqualToString: _(@"Empty Framework")])
    {
      return [myBundle pathForResource: @"EmptyFramework" ofType: @"template"];
    }
  else
    {
      return [myBundle pathForResource: @"EmptyLibrary" ofType: @"template"];
    }
}

- initWithDocument: (ProjectDocument *) anOwner
    infoDictionary: (NSDictionary *) infoDict
    projectModules: (NSArray *) projectModules
{
  if ((self = [self init]) != nil)
    {
      NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];

      owner = anOwner;

      ASSIGN(principalClass, [infoDict objectForKey: @"PrincipalClass"]);
      ASSIGN(authors, [NSArray arrayWithArray:
        [infoDict objectForKey: @"Authors"]]);
      ASSIGN(copyright, [infoDict objectForKey: @"Copyright"]);
      ASSIGN(copyrightDescription, [infoDict objectForKey:
        @"CopyrightDescription"]);
      buildsSharedLibrary = [[infoDict objectForKey: @"BuildsSharedLibrary"]
        boolValue];

      ASSIGN(fileManager, [owner projectModuleWithName: @"FileManager"]);

      ASSIGN(builder, [owner projectModuleWithName: @"MakeBuilder"]);

      makesCurrentVersion = ![[infoDict objectForKey:
        @"DoesntMakeCurrentVersion"] boolValue];
      ASSIGN(versionName, [infoDict objectForKey: @"VersionName"]);

      oldMakeHash = [[infoDict objectForKey: @"MakefileHash"] intValue];
    }

  return self;
}

- (void) dealloc
{
  [[NSNotificationCenter defaultCenter] removeObserver: self];

  TEST_RELEASE(principalClass);
  TEST_RELEASE(authors);
  TEST_RELEASE(copyright);
  TEST_RELEASE(copyrightDescription);
  TEST_RELEASE(versionName);

  TEST_RELEASE(attributes);

  TEST_RELEASE(fileManager);
  TEST_RELEASE(builder);

  [super dealloc];
}

- (NSDictionary *) infoDictionary
{
  NSMutableDictionary * dict = [NSMutableDictionary dictionary];

  if (principalClass != nil)
    {
      [dict setObject: principalClass forKey: @"PrincipalClass"];
    }
  [dict setObject: authors forKey: @"Authors"];
  if (copyright != nil)
    {
      [dict setObject: copyright forKey: @"Copyright"];
    }
  if (copyrightDescription != nil)
    {
      [dict setObject: copyrightDescription forKey: @"CopyrightDescription"];
    }
  if (versionName != nil)
    {
      [dict setObject: versionName forKey: @"VersionName"];
    }

  [dict setObject: [NSNumber numberWithBool: buildsSharedLibrary]
           forKey: @"BuildsSharedLibrary"];
  // inverted so that when the key is not set when the info dict
  // is read back in, the value defaults to YES
  [dict setObject: [NSNumber numberWithBool: !makesCurrentVersion]
           forKey: @"DoesntMakeCurrentVersion"];
  [dict setObject: [NSNumber numberWithInt: oldMakeHash]
           forKey: @"MakefileHash"];

  return dict;
}

- (BOOL) regenerateDerivedFiles
{
  return [self prepareMakefile] && [self prepareInfoFile];
}

- (NSString *) pathToSubprojectsDirectory
{
  return [[owner projectDirectory]
    stringByAppendingPathComponent: @"Subprojects"];
}

- (BOOL) canCreateCategoriesAtPath: (NSString *) aPath
{
  return YES;
}

- (BOOL) canCreatePlainFilesAtPath: (NSString *) aPath
{
  aPath = [aPath stringByStandardizingPath];

  // don't allow creating plain files in the following categories:
  // - "/"
  // - "/Interfaces"
  // - "/Localized Resource Files"
  // - "/Frameworks" and any of it's descendents
  if ([aPath isEqualToString: @"/"] ||
    [aPath isEqualToString: PreSlash(InterfaceFilesCategoryName)] ||
    [aPath isEqualToString: PreSlash(LocalizedResourceFilesCategoryName)] ||
    [aPath hasPrefix: PreSlash(FrameworksCategoryName)])
    {
      return NO;
    }
  else
    {
      return YES;
    }
}

- (BOOL) canCreateLinksAtPath: (NSString *) aPath
{
  // same rules for links as for plain files
  return [self canCreatePlainFilesAtPath: aPath];
}

- (BOOL) canCreateVirtualFilesAtPath: (NSString *) aPath
{
  // allow creating virtual files only in the /Frameworks category
  // and it's subcategories
  return [aPath hasPrefix: PreSlash(FrameworksCategoryName)];
}

- (BOOL) canDeletePath: (NSString *) aPath
{
  aPath = [aPath stringByStandardizingPath];

  // don't allow deleting any of the mandatory top-level categories
  // and the AppKit and Foundation frameworks
  if ([aPath isEqualToString: PreSlash(ClassFilesCategoryName)] ||
    [aPath isEqualToString: PreSlash(HeaderFilesCategoryName)] ||
    [aPath isEqualToString: PreSlash(OtherSourceFilesCategoryName)] ||
    [aPath isEqualToString: PreSlash(InterfaceFilesCategoryName)] ||
    [aPath isEqualToString: PreSlash(ResourceFilesCategoryName)] ||
    [aPath isEqualToString: PreSlash(LocalizedResourceFilesCategoryName)] ||
    [aPath isEqualToString: PreSlash(SupportingFilesCategoryName)] ||
    [aPath isEqualToString: PreSlash(FrameworksCategoryName)])
    {
      return NO;
    }
  else
    {
      return YES;
    }
}

- (FileOpenResult) openFile: (NSString *) aPath
{
  // TODO
  return FileOpenCannotHandle;
}

- (NSString *) pathToFile: (NSString *) fileName
               isCategory: (BOOL) isCategory
{
  NSArray * pathComponents;
  NSString * path;

  if ([fileName hasPrefix: PreSlash(ClassFilesCategoryName)] ||
      [fileName hasPrefix: PreSlash(HeaderFilesCategoryName)] ||
      [fileName hasPrefix: PreSlash(OtherSourceFilesCategoryName)] ||
      [fileName hasPrefix: PreSlash(SupportingFilesCategoryName)])
    {
      path = [owner projectDirectory];
    }
  else if ([fileName hasPrefix: PreSlash(ResourceFilesCategoryName)])
    {
      path = [[owner projectDirectory]
        stringByAppendingPathComponent: @"Resources"];
    }
  else if ([fileName hasPrefix: PreSlash(InterfaceFilesCategoryName)] ||
           [fileName hasPrefix: PreSlash(LocalizedResourceFilesCategoryName)])
    {
      NSArray * pathComponents = [fileName pathComponents];

        // we need at least 3 path components here - the last one indicates
        // the language
      if ([pathComponents count] >= 3)
        {
          path = [[owner projectDirectory]
            stringByAppendingPathComponent:
            [NSString stringWithFormat: @"%@.lproj", [pathComponents
            objectAtIndex: 2]]];
        }
      else
        {
          path = nil;
        }
    }
  else if ([fileName hasPrefix: PreSlash(FrameworksCategoryName)])
    {
      path = nil;
    }
  else
    {
      path = [[owner projectDirectory]
        stringByAppendingPathComponent: @"OtherFiles"];
    }

  if (isCategory == NO)
    {
      path = [path stringByAppendingPathComponent: [fileName
        lastPathComponent]];
    }

  return path;
}

- (NSImage *) iconForPath: (NSString *) aPath
{
  if (ClassFilesIcon == nil)
    {
      [[self class] loadCategoryIcons];
    }

  if ([aPath isEqualToString: PreSlash(ClassFilesCategoryName)])
    {
      return ClassFilesIcon;
    }
  else if ([aPath isEqualToString: PreSlash(HeaderFilesCategoryName)])
    {
      return HeaderFilesIcon;
    }
  else if ([aPath isEqualToString: PreSlash(InterfaceFilesCategoryName)])
    {
      return InterfaceFilesIcon;
    }
  else if ([aPath isEqualToString: PreSlash(OtherSourceFilesCategoryName)])
    {
      return OtherSourceFilesIcon;
    }
  else if ([aPath isEqualToString: PreSlash(ResourceFilesCategoryName)])
    {
      return ResourceFilesIcon;
    }
  else if ([aPath isEqualToString:
    PreSlash(LocalizedResourceFilesCategoryName)])
    {
      return LocalizedResourceFilesIcon;
    }
  else if ([aPath isEqualToString: PreSlash(SupportingFilesCategoryName)])
    {
      return SupportingFilesIcon;
    }
  else
    {
      return nil;
    }
}

- (NSArray *) permissibleFileExtensionsInCategory: (NSString *) category
{
  if ([category hasPrefix: PreSlash(ClassFilesCategoryName)])
    {
      return [NSArray arrayWithObjects: @"m", @"mm", @"cc", @"cpp",
        @"java", nil];
    }
  else if ([category hasPrefix: PreSlash(OtherSourceFilesCategoryName)])
    {
      return [NSArray arrayWithObjects: @"c", @"m", @"mm", @"cc", @"cpp",
        @"java", nil];
    }
  else if ([category hasPrefix: PreSlash(HeaderFilesCategoryName)])
    {
      return [NSArray arrayWithObject: @"h"];
    }
  else if ([category hasPrefix: PreSlash(InterfaceFilesCategoryName)])
    {
      return [NSArray arrayWithObjects: @"gorm", @"nib", @"gsmarkup",
        @"gmodel", nil];
    }
  else if ([category hasPrefix: PreSlash(FrameworksCategoryName)])
    {
      return [NSArray arrayWithObjects: @"framework", @"so", nil];
    }
  else
    {
      return nil;
    }
}

- (FileImportResult) importFile: (NSString *) aFile
                   intoCategory: (NSString *) aCategory
                          error: (NSError **) error
{
  if ([aCategory hasPrefix: PreSlash(FrameworksCategoryName)])
    {
      return [fileManager createVirtualFileNamed: [aFile lastPathComponent]
                                          atPath: aCategory
                                           error: error];
    }
  else
    {
      return FileImportCannotHandle;
    }
}

- (NSString *) pathToFileTemplatesDirectoryForCategory: (NSString *) category
{
  NSArray * pathComponents = [category pathComponents];

  if ([pathComponents count] <= 1)
    {
      return nil;
    }
  else
    {
      // the name of the templates directory is the same as the category
      // name, but with ' ' substituted for '_' (as gnustep-make doesn't
      // allow for spaces in resource filenames)

      return [[NSBundle bundleForClass: [FrameworkProjectType class]]
        pathForResource: [[pathComponents objectAtIndex: 1]
        stringByReplacingString: @" " withString: @"_"]
                 ofType: @"templates"];
    }
}

- (NSDictionary *) filesAssociatedWithTemplateFile: (NSString *) aFile
                            fromTemplatesDirectory: (NSString *) templatesDir
                                       forCategory: (NSString *) targetCategory
{
  NSString * templatesDirName = [templatesDir lastPathComponent];
  NSString * newFilename = nil;
  NSString * category = nil;

  if ([templatesDirName isEqualToString: @"Class_Files.templates"])
    {
      NSMutableArray * temp;

      newFilename = [[templatesDir stringByDeletingLastPathComponent]
        stringByAppendingPathComponent: @"Header_Files.templates"];
      newFilename = [newFilename stringByAppendingPathComponent: [aFile
        stringByDeletingPrefix: templatesDir]];
      newFilename = [[newFilename stringByDeletingPathExtension]
        stringByAppendingPathExtension: @"h"];
      category = PreSlash(HeaderFilesCategoryName);

      temp = [[targetCategory pathComponents] mutableCopy];
      [temp replaceObjectAtIndex: 1 withObject: HeaderFilesCategoryName];
      category = [NSString pathWithComponents: temp];
    }
  else if ([templatesDirName isEqualToString: @"Header_Files.templates"])
    {
      NSMutableArray * temp;

      newFilename = [[templatesDir stringByDeletingLastPathComponent]
        stringByAppendingPathComponent: @"Class_Files.templates"];
      newFilename = [newFilename stringByAppendingPathComponent: [aFile
        stringByDeletingPrefix: templatesDir]];
      newFilename = [[newFilename stringByDeletingPathExtension]
        stringByAppendingPathExtension: @"m"];
      category = PreSlash(ClassFilesCategoryName);

      temp = [[targetCategory pathComponents] mutableCopy];
      [temp replaceObjectAtIndex: 1 withObject: ClassFilesCategoryName];
      category = [NSString pathWithComponents: temp];
    }

  // accept the file if it exists
  if (newFilename != nil && [[NSFileManager defaultManager]
    fileExistsAtPath: newFilename])
    {
      return [NSDictionary dictionaryWithObject: category forKey: newFilename];
    }
  else
    {
      return nil;
    }
}

- (NSArray *) tabIdentifiersForProjectAttributes: (id) sender
{
  return [NSArray arrayWithObjects:
    @"FrameworkInfo",
    nil];
}

- (NSString *)  projectAttributes:  (id) sender
 toolbarItemLabelForTabIdentifier: (NSString *) tab
{
  return _(@"Framework Info");
}

- (NSString *)    projectAttributes:  (id) sender
 toolbarItemToolTipForTabIdentifier: (NSString *) tab
{
  return _(@"General framework settings");
}

- (NSImage *)  projectAttributes:  (id) sender
 toolbarItemIconForTabIdentifier: (NSString *) tab
{
  return [NSImage imageNamed: @"FrameworkInfoAttributes" owner: self];
}

- (NSView *) projectAttributes: (id) sender
          viewForTabIdentifier: (NSString *) tab
{
  if (attributes == nil)
    {
      attributes = [[FrameworkGeneralAttributes alloc]
        initWithOwner: self];
    }

  return [attributes view];
}

- (void) setPrincipalClass: (NSString *) aClass
{
  ASSIGN(principalClass, aClass);
  [owner updateChangeCount: NSChangeDone];
}

- (NSString *) principalClass
{
  return principalClass;
}

- (void) setAuthors: (NSArray *) anArray
{
  ASSIGNCOPY(authors, anArray);
  [owner updateChangeCount: NSChangeDone];
}

- (NSArray *) authors
{
  return authors;
}

- (void) setCopyright: (NSString *) aCopyright
{
  ASSIGN(copyright, aCopyright);
  [owner updateChangeCount: NSChangeDone];
}

- (NSString *) copyright
{
  return copyright;
}

- (void) setCopyrightDescription: (NSString *) aCopyrightDescription
{
  ASSIGN(copyrightDescription, aCopyrightDescription);
  [owner updateChangeCount: NSChangeDone];
}

- (NSString *) copyrightDescription
{
  return copyrightDescription;
}

- (void) setVersionName: (NSString *) aName
{
  ASSIGN(versionName, aName);
  [owner updateChangeCount: NSChangeDone];
}

- (NSString *) versionName
{
  return versionName;
}

- (void) setMakesCurrentVersion: (BOOL) flag
{
  if (makesCurrentVersion != flag)
    {
      makesCurrentVersion = flag;
      [owner updateChangeCount: NSChangeDone];
    }
}

- (BOOL) makesCurrentVersion
{
  return makesCurrentVersion;
}

- (void) setBuildsSharedLibrary: (BOOL) flag
{
  if (buildsSharedLibrary != flag)
    {
      buildsSharedLibrary = flag;
      [owner updateChangeCount: NSChangeDone];
    }
}

- (BOOL) buildsSharedLibrary
{
  return buildsSharedLibrary;
}

- (NSArray *) buildTargetsForMakeBuilder: (id) sender
{
  return [NSArray arrayWithObjects:
    _(@"Default"),
    _(@"Debug"),
    _(@"Profile"),
    _(@"Install"),
    nil];
}

- (BOOL) prepareForBuildByBuilder: (id) sender
                           target: (NSString *) aTarget
{
  return [self prepareMakefile] && [self prepareInfoFile];
}

- (NSArray *) buildArgumentsForBuilder: (id) sender
                                target: (NSString *) aTarget
{
  if ([aTarget isEqualToString: _(@"Default")])
    {
      return nil;
    }
  else if ([aTarget isEqualToString: _(@"Debug")])
    {
      return [NSArray arrayWithObject: @"debug=yes"];
    }
  else if ([aTarget isEqualToString: _(@"Profile")])
    {
      return [NSArray arrayWithObject: @"profile=yes"];
    }
  else if ([aTarget isEqualToString: _(@"Install")])
    {
      return [NSArray arrayWithObject: @"install"];
    }
  else
    {
      NSLog(@"Unknown target \"%@\" passed to "
        @"-prepareForBuildByBuilder:target:", aTarget);

      return nil;
    }
}

- (BOOL) prepareForCleanByBuilder: (id) sender
                           target: (NSString *) aTarget
{
  return [self prepareMakefile];
}

- (NSArray *) cleanArgumentsForBuilder: (id) sender
                                target: (NSString *) aTarget
{
  if ([aTarget isEqualToString: _(@"Default")] ||
      [aTarget isEqualToString: _(@"Install")])
    {
      return [NSArray arrayWithObject: @"clean"];
    }
  else
    {
      return [NSArray arrayWithObject: @"distclean"];
    }
}

@end
