/* xdaliclock - a melting digital clock
 * Copyright (c) 1991-2013 Jamie Zawinski <jwz@jwz.org>
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  No representations are made about the suitability of this
 * software for any purpose.  It is provided "as is" without express or
 * implied warranty.
 */

#import "DaliClockSaverView.h"

char *progname = "Dali Clock";   // digital.c wants this for error messages.


/* Note that this is a subclass of "ScreenSaverView", not of "DaliClockView".
   Note that this situation is exactly why multiple inheritance would be nice.
 */

@implementation DaliClockSaverView


+ (void)initialize;
{
  static BOOL initialized_p = NO;
  if (initialized_p)
    return;
  initialized_p = YES;

  /* Figure out what the preferences module-name for this screen saver is,
     and create a preferences controller for it.
   */
  NSBundle *nsb = [NSBundle bundleForClass:[self class]];
  NSAssert1 (nsb, @"no bundle for class %@", [self class]);
  NSString *name = [nsb bundleIdentifier];
  NSUserDefaults *defs = [ScreenSaverDefaults defaultsForModuleWithName:name];

  /* Tell the DaliClockView class to initialize its default preferences.
     This can't just happen in [DaliClockView:initialize], because
     it has to happen before the NSUserDefaultsController is created.
   */
  [DaliClockView registerDefaults:defs];

  /* Now that the DaliClockView class has initialized its preferences,
     set the defaults for those preferences handled by DaliClockSaverView
     rather than by DaliClockView.

     This stuff has to be done at class-initialization time so that
     it happens before the instances are deserialized from the nib file,
     or else the preferences don't end up hooked up properly to the
     preferences dialog in SaverPrefs.nib. I'm not entirely sure why.

     We only make one change here: we change the default background color
     to be slightly more opaque than the default value that DaliClockView
     had assigned.
   */
  NSString *key = @"initialBackgroundColor";
  NSColor *defbg = [NSUnarchiver unarchiveObjectWithData:
                                   [defs objectForKey:key]];
  defbg = [defbg colorWithAlphaComponent:0.95];
  NSDictionary* extras = [NSDictionary dictionaryWithObjectsAndKeys:
    [NSArchiver archivedDataWithRootObject:defbg], key,
    nil];
  NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:100];
  [dict addEntriesFromDictionary:[defs dictionaryRepresentation]];
  [dict addEntriesFromDictionary:extras];
  [defs registerDefaults:dict];

  NSUserDefaultsController *controller =
    [[[NSUserDefaultsController alloc] initWithDefaults:defs
                                         initialValues:nil]
      retain];  // never freed

  [DaliClockView setUserDefaultsController: controller];
}


- (id)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
{
  self = [super initWithFrame:frame isPreview:isPreview];
  if (!self) return nil;

  NSRect r = frame;
  r.origin.x = r.origin.y = 0;

  // we may not overwrite the System Preferences window's background, 
  // so set ownWindow to false if we're running embedded.
  //
  clockView = [[[DaliClockView alloc] initWithFrame:r ownWindow:!isPreview]
                retain];
  [clockView setConstrainSizes:YES];  // Don't use huge bitmaps.

  [self addSubview:clockView];

  return self;
}


// Kludge so that this is runnable by xscreensaver's SaverTester.app
- (id) initWithFrame:(NSRect)f saverName:(NSString *)s isPreview:(BOOL)p
{
  return [self initWithFrame:f isPreview:p];
}


- (void) startAnimation
{
  [super startAnimation];

  /* Display the date every minute-and-a-bit.  That way it doesn't show up
     on the same second each minute.  Also, only do this at all on the
     screen that contains the menubar.
   */
  if ([self isPreview] ||
      [[self window] screen] == [[NSScreen screens] objectAtIndex:0])
    [clockView setAutoDate:67];


# if 0
  /* As of 10.8 or so, when the screen is locked, our background is no
     longer transparent. Well, it is, but there's a masking window below us.
     I'd like to find a way to make that window transparent too, so that if
     the user has selected a less-than-fully-opaque background color, their
     desktop will show through the locked screen.

     I've managed to identify the masking windows, but I don't know how to
     draw onto them with an opaque rectangle, or resize them, or unmap them,
     or anything.  How do I get the CGContextRef for a window that I didn't
     create?  Maybe that's not possible?
   */
  NSScreen *screen = [[self window] screen];
  NSDictionary *sinfo = [screen deviceDescription];
  NSNumber *sid = [sinfo objectForKey:@"NSScreenNumber"];
  NSArray *windows = (NSArray *)
    CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly,
                               kCGNullWindowID);

  int n = [windows count];
  for (int i = 0; i < n; i++) {
    NSDictionary *desc = [windows objectAtIndex:i];
    NSString *owner = [desc objectForKey:@"kCGWindowOwnerName"];
    if (owner &&
        ([owner isEqualToString:@"ScreenSaverEngine"] ||
         [owner isEqualToString:@"loginwindow"])) {
      if ([[desc objectForKey:@"kCGWindowLayer"] integerValue]
          >= kCGScreenSaverWindowLevelKey) {
        int id = [[desc objectForKey:@"kCGWindowNumber"] integerValue];
        
        // This just gives me [self window] of this saver or the saver on
        // the other screen, not the shielding window.
        NSWindow *w = [[NSApplication sharedApplication]
                        windowWithWindowNumber:id];
        NSLog(@"%d %@ %@", id, w);

//        CGContextRef cgc = ###;
//        CGRect r = CGRectMake (0, 100, 200, 300);
//        CGContextSetGrayFillColor (cgc, 1, 0);
//        CGContextFillRect (cgc, r);
      }
    }
  }
  CFRelease (windows);
# endif // 0
}

/*
- (void)stopAnimation
{
  // Nothing to do here
  [super stopAnimation];
}
*/

/*
- (void) animateOneFrame
{
  // nothing to do here: DaliClockView has its own timers.
}
*/


- (BOOL)isOpaque
{
  return NO;
}

- (void)drawRect:(NSRect)rect
{
  // do nothing here: do not call super, because that fills with black.
}

/* Called when this View is resized.
 */
- (void)setFrameSize:(NSSize)newSize
{
  [super setFrameSize:newSize];
  [clockView setFrameSize:newSize];
}

- (BOOL) hasConfigureSheet
{
  return YES;
}

- (NSWindow *) configureSheet
{
  if (!configSheet) {
    if (![NSBundle loadNibNamed:@"SaverPrefs" owner:self]) {
      NSAssert (0, @"unable to load prefs dialog");
    }
  }
  NSAssert (configSheet, @"loaded nib, but no prefs dialog");
  return configSheet;
}

// The Ok button in the preferences sheet
//
- (IBAction)okClick:(id)sender
{
  [[NSApplication sharedApplication] endSheet:configSheet];
  // Without this, preferences are not persistent in the screen saver.
  [[[[clockView class] userDefaultsController] defaults] synchronize];
}

/* The About button in the preferences sheet.
 */
- (IBAction)aboutClick:(id)sender
{
  [clockView aboutClick:sender];
}


/* When something tries to read or write preferences values in this object,
   just pass that along to the clockView, and also save it in the preferences.
 */
- (id)valueForKey:(NSString *)key
{
  return [clockView valueForKey:key];
}


- (void)setValue:(id)value forKey:(NSString *)key
{
  [self willChangeValueForKey:key];

  [clockView setValue:value forKey:key];

  if ([value isKindOfClass:[NSColor class]])  // encode colors in prefs
    value = [NSArchiver archivedDataWithRootObject:value];

  NSUserDefaultsController *controller =
    [[clockView class] userDefaultsController];
  [[controller defaults] setValue:value forKey:key];

  [self didChangeValueForKey:key];
}

@end
