/*
    ProjectWindowController.m

    Implementation of the ProjectWindowController 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 "ProjectWindowController.h"

#import <AppKit/NSImage.h>
#import <AppKit/NSWindow.h>
#import <AppKit/NSWindow+Toolbar.h>
#import <AppKit/NSToolbar.h>
#import <AppKit/NSToolbarItem.h>
#import <AppKit/NSBox.h>
#import <AppKit/NSPopUpButton.h>
#import <AppKit/NSScrollView.h>
#import <AppKit/NSTextView.h>
#import <AppKit/NSTextStorage.h>

#import <Foundation/NSBundle.h>
#import <Foundation/NSException.h>
#import <Foundation/NSNotification.h>

#import "ProjectDocument.h"
#import "ProjectModule.h"
#import "ProjectType.h"

@implementation ProjectWindowController

static NSArray * standardToolbarItems = nil;

static NSString
  * const ProjectModuleSwitcherToolbarItemIdentifier =
  @"PMProjectModuleSwitcherToolbarItemIdentifier";

+ (void) initialize
{
  if (standardToolbarItems == nil)
    {
      standardToolbarItems = [[NSArray alloc] initWithObjects:
        ProjectModuleSwitcherToolbarItemIdentifier,
        NSToolbarSeparatorItemIdentifier,
        NSToolbarSpaceItemIdentifier,
        NSToolbarFlexibleSpaceItemIdentifier,
        NSToolbarShowColorsItemIdentifier,
        NSToolbarShowFontsItemIdentifier,
        NSToolbarCustomizeToolbarItemIdentifier,
        NSToolbarPrintItemIdentifier,
        nil];
    }
}

- initWithWindowNibName: (NSString *) nibName
          ownerDocument: (ProjectDocument *) anOwner
{
  // we must do this before we load the nib
  owner = anOwner;

  if ((self = [self initWithWindowNibName: nibName]) != nil)
    {
      [[NSNotificationCenter defaultCenter]
        addObserver: self
           selector: @selector(projectNameChanged:)
               name: ProjectNameDidChangeNotification
             object: owner];
    }

  return self;
}

- (void) dealloc
{
  [[NSNotificationCenter defaultCenter]
    removeObserver: self
              name: ProjectNameDidChangeNotification
            object: owner];

  TEST_RELEASE (currentModule);

  TEST_RELEASE (switcherItem);

  TEST_RELEASE (log);
  TEST_RELEASE (moduleSwitcher);

  /* N.b. we can't say [myWindow setToolbar: nil] here, because would
   * cause all the views inside the window to redraw, including some
   * views which use data sources (e.g. table views). However, since
   * these data sources are likely to have been the project modules
   * (which have been dealloc'ed earlier), they are no longer valid
   * and would cause segfaults. Therefore we just rely on the window
   * being so kind as the destroy the toolbar. */

  [super dealloc];
}

- (void) awakeFromNib
{
  NSUserDefaults * df = [NSUserDefaults standardUserDefaults];

  [myWindow setMiniwindowImage: [NSImage imageNamed: @"pmproj"]];

  toolbar = [[[NSToolbar alloc]
    initWithIdentifier: [owner projectDirectory]]
    autorelease];
  [toolbar setAutosavesConfiguration: NO];
  [toolbar setAllowsUserCustomization: NO];

  [toolbar setDelegate: self];
  [toolbar setVisible: [df boolForKey: @"DontShowProjectWindowToolbar"] == NO];

  [myWindow setToolbar: toolbar];

  if ([[owner projectModules] count] > 0)
    {
      [self selectModule: 0];
    }
}

/**
 * Action invoked when a module-switch has been requested, either from
 * the project window's switcher popup, or from a menu item (unused so far).
 */
- (void) switchView: sender
{
  // invoked by the module-switcher popup button
  if (sender == moduleSwitcher)
    {
      // bug in GNUstep - we have to select the proper item manually,
      // even though the popup button already knows which it has selected,
      // but it won't redraw unless we really force it to
      [moduleSwitcher selectItemAtIndex: [moduleSwitcher indexOfSelectedItem]];
      [self selectModule: [moduleSwitcher indexOfSelectedItem]];
    }
  // invoked by a menu item
  else
    {
      int tag = [sender tag];

      [moduleSwitcher selectItemAtIndex: tag];
      [self selectModule: tag];
    }
}

/**
 * Sets the current module displayed in the receiver's project window.
 *
 * @param aModule The module which to display. It must be one of the
 *      project's modules, otherwise an exception is thrown.
 */
- (void) setCurrentModule: (id <ProjectModule>) aModule
{
  NSString * moduleName;
  int i;
  NSArray * itemIdentifiers;
  NSEnumerator * e;
  NSString * identifier;

  NSAssert ([[owner projectModules] containsObject: aModule],
    _(@"Invalid module %@ passed to -setCurrentModule:"));

  if (currentModule == aModule)
    {
      // nothing to do

      return;
    }

  ASSIGN (currentModule, aModule);
  moduleName = [[currentModule class] moduleName];

  [box setContentView: [currentModule view]];

  // remove extra toolbar items
  for (i = [[toolbar items] count] - 1; i > 1; i--)
    {
      [toolbar removeItemAtIndex: i];
    }

  itemIdentifiers = [currentModule toolbarItemIdentifiers];
  e = [itemIdentifiers objectEnumerator];
  for (i = 2; (identifier = [e nextObject]) != nil; i++)
    {
      [toolbar insertItemWithItemIdentifier: identifier atIndex: i];
    }

  // and revalidate the toolbar's visible items
  [toolbar validateVisibleItems];

  [[NSNotificationCenter defaultCenter]
    postNotificationName: CurrentProjectModuleDidChangeNotification
                  object: self
                userInfo: [NSDictionary dictionaryWithObject: currentModule
                                                      forKey: @"Module"]];
}

/**
 * Returns the currently visible project module. To some stuff (e.g. the
 * debugger's inspectors) this information is better than the current tab.
 */
- (id <ProjectModule>) currentModule
{
  return currentModule;
}

/**
 * Puts the project module in the project module list at index `moduleNumber'
 * into the receiver's window.
 */
- (void) selectModule: (unsigned int) moduleNumber
{
  [self setCurrentModule: [[owner projectModules]
    objectAtIndex: moduleNumber]];
}

- (NSToolbarItem*)toolbar: (NSToolbar*)toolbar
    itemForItemIdentifier: (NSString*)itemIdentifier
willBeInsertedIntoToolbar: (BOOL)flag
{
  // if it's the project module switcher, set it up correctly
  if ([itemIdentifier isEqualToString:
    ProjectModuleSwitcherToolbarItemIdentifier])
    {
      if (switcherItem == nil)
        {
          int i, n;
          NSArray * projectModules;
          NSMenu * modulesMenu;
          NSMenuItem * menuItem;

          switcherItem = [[NSToolbarItem alloc]
            initWithItemIdentifier: itemIdentifier];
          moduleSwitcher = [[NSPopUpButton alloc]
            initWithFrame: NSMakeRect(0, 0, 100, 22)];
          modulesMenu = [[[NSMenu alloc]
            initWithTitle: _(@"Project Modules")]
            autorelease];
          modulesMenu = nil;

          projectModules = [owner projectModules];
          for (i = 0, n = [projectModules count]; i < n; i++)
            {
              id <ProjectModule> module = [projectModules objectAtIndex: i];
              NSString * name = [[module class] humanReadableModuleName];
              NSMenuItem * item;

              [moduleSwitcher addItemWithTitle: name];
              [[moduleSwitcher lastItem] setKeyEquivalent: [NSString
                stringWithFormat: @"%i", i + 1]];

              item = [[[NSMenuItem alloc]
                initWithTitle: name
                       action: @selector(switchView:)
                keyEquivalent: nil]
                autorelease];
              [item setTarget: self];
              [item setTag: i];

              [modulesMenu addItem: item];
            }

          [modulesMenu sizeToFit];
          menuItem = [[[NSMenuItem alloc]
            initWithTitle: _(@"Project Modules")
                   action: NULL
            keyEquivalent: nil]
            autorelease];
          [menuItem setSubmenu: modulesMenu];

          [moduleSwitcher setTarget: self];
          [moduleSwitcher setAction: @selector(switchView:)];
          [moduleSwitcher selectItemAtIndex: 0];

          [switcherItem setLabel: _(@"Switch Module")];
          [switcherItem setPaletteLabel: _(@"Switch Module")];
          [switcherItem setToolTip: _(@"Switches between project modules")];
          [switcherItem setMenuFormRepresentation: menuItem];

          [switcherItem setView: moduleSwitcher];
          [switcherItem setMinSize: NSMakeSize(100, NSHeight([moduleSwitcher
            frame]))];
          [switcherItem setMaxSize: NSMakeSize(200, NSHeight([moduleSwitcher
            frame]))];
        }

      return switcherItem;
    }

  // otherwise ask the module responsible for the toolbar to create it
  else
    {
      return [currentModule toolbarItemForItemIdentifier: itemIdentifier];
    }
}

/**
 * Returns the allowed item identifiers for a project window toolbar.
 * The allowed item identifiers are first our project module switcher,
 * then any of the standard toolbar items (NSToolbarSeparatorItemIdentifier
 * etc.) and then any item identifiers returned when sending the responsible
 * module a allowedToolbarItemIdentifiers message.
 */
- (NSArray*) toolbarAllowedItemIdentifiers: (NSToolbar*)toolbar
{
  NSMutableArray * array = [NSMutableArray array];
  NSEnumerator * e = [[owner projectModules] objectEnumerator];
  id <ProjectModule> module;

  [array addObjectsFromArray: standardToolbarItems];
  while ((module = [e nextObject]) != nil)
    {
      [array addObjectsFromArray: [module toolbarItemIdentifiers]];
    }

  return array;
}

/**
 * Returns the default item identifiers for a project window toolbar.
 * The default item identifiers are first our project module switcher,
 * then a separator and then any item identifiers returned when sending
 * the responsible module a defaultToolbarItemIdentifiers message.
 */
- (NSArray*) toolbarDefaultItemIdentifiers: (NSToolbar*)toolbar
{
  return [NSArray arrayWithObjects:
    ProjectModuleSwitcherToolbarItemIdentifier,
    NSToolbarSeparatorItemIdentifier,
    nil];
}

- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
{
  NSString * identifier = [toolbarItem itemIdentifier];

  // these two are always valid
  if ([identifier isEqualToString: ProjectModuleSwitcherToolbarItemIdentifier])
    {
      return YES;
    }
  else
    {
      return [currentModule validateToolbarItem: toolbarItem];
    }
}

/**
 * Notification method invoked when the project name changes - we then
 * update our window's title to hold it.
 */
- (void) projectNameChanged: (NSNotification *) notif
{
  [myWindow setTitle: [owner projectName]];
}

/**
 * Appends a message to the project log.
 *
 * @param aMessage The message which to append. It does not have
 *      to end with a newline character - one is automatically
 *      appended if necessary.
 */
- (void) logMessage: (NSString *) aMessage
{
  NSCalendarDate * calendarDate = [NSCalendarDate calendarDate];

  // append a trailing newline character if necessary
  if ([aMessage characterAtIndex: [aMessage length] - 1] != '\n')
    {
      aMessage = [aMessage stringByAppendingString: @"\n"];
    }

  // prepend the current time to the message
  aMessage = [NSString stringWithFormat: @"%.2i:%.2i:%.2i %@",
    [calendarDate hourOfDay], [calendarDate minuteOfHour], [calendarDate
    secondOfMinute], aMessage];

  [log replaceCharactersInRange: NSMakeRange([[log textStorage] length], 0)
                     withString: aMessage];
  [log scrollRangeToVisible: NSMakeRange([[log textStorage] length], 0)];
}

@end
