/*
  MulleScionist - MulleScion template editor
  Copyright (c) 2013, 2014 Mulle kybernetiK, code by Nat!
 */

#import "MulleScionistDokument.h"

#import <Foundation/NSDebug.h>
#import <MulleHTMLTidy.h>

#import "UKSyntaxColoredTextViewController.H"
#import "MarkerLineNumberView.h"
#import "NSTask+MullePATH.h"


@implementation MulleScionistDokument

- (void) loadDefaultScionAndPlistIfNeeded
{
   NSBundle   *bundle;
   NSString   *path;
   id         scion;
   id         plist;
   
   if( ! self->loadDefault_)
      return;
   
   bundle = [NSBundle mainBundle];
   path   = [bundle pathForResource:@"default"
                             ofType:@"scion"];
   scion = [NSString stringWithContentsOfFile:path];
   path   = [bundle pathForResource:@"default"
                             ofType:@"plist"];
   plist = [NSString stringWithContentsOfFile:path];
   
   [textView setString:scion];
   [plistTextView setString:plist];
}


- (id) initWithType:(NSString *) typeName
              error:(NSError **)outError;
{
   self = [super initWithType:typeName
                        error:outError];
   if( self)
   {
      self->loadDefault_ = YES;
   }
   return( self);
}


- (NSString *) windowNibName
{
   return( @"MulleScionistDokument");
}


- (void) windowControllerDidLoadNib:(NSWindowController *) aController
{
   [self loadDefaultScionAndPlistIfNeeded];
   
   // set up line numbering for text view
   scrollView = [textView enclosingScrollView];
   NSParameterAssert( scrollView);
   
   lineNumberView = [[[MarkerLineNumberView alloc] initWithScrollView:scrollView] autorelease];
   [scrollView setVerticalRulerView:lineNumberView];
   [scrollView setRulersVisible:YES];
   
   [textView setSmartInsertDeleteEnabled:YES];
   if( [textView respondsToSelector:@selector( setAutomaticQuoteSubstitutionEnabled:)])
      [textView setAutomaticQuoteSubstitutionEnabled:NO];
   if( [textView respondsToSelector:@selector( setAutomaticDashSubstitutionEnabled:)])
      [textView setAutomaticDashSubstitutionEnabled:NO];
   if( [textView respondsToSelector:@selector( setAutomaticTextReplacementEnabled:)])
      [textView setAutomaticTextReplacementEnabled:NO];
   [refreshTypePopupButton selectItemWithTitle: [[NSUserDefaults standardUserDefaults] objectForKey: @"Refresh"]];
   
   // Register for "text changed" notifications of the text storage:
   [[NSNotificationCenter defaultCenter] addObserver:self
                                            selector:@selector(textDidChange:)
                                                name:NSTextStorageDidProcessEditingNotification
                                              object:[textView textStorage]];
   
   [super windowControllerDidLoadNib: aController];
}


- (void) close
{
   [[NSNotificationCenter defaultCenter] removeObserver:self];
   
   [webView setFrameLoadDelegate:nil];
   webView = nil;
   [textView setDelegate:nil];
   textView = nil;
   [plistTextView setDelegate:nil];
   plistTextView = nil;

   [super close];
}


- (void) dealloc
{
   NSParameterAssert( ! webView);
   
   [mulleScionDirPath_ release];
   [environment_ release];
   
   [super dealloc];
}

#pragma mark Web Preview

- (IBAction) refresh:(id)sender
{
   [NSObject cancelPreviousPerformRequestsWithTarget:self
                                            selector:@selector( updatePreview)
                                              object:nil];
   [refreshProgressIndicator startAnimation: self];
   [self drawWebView];
   [refreshProgressIndicator stopAnimation: self];
}


- (IBAction) refreshChanged:(id) sender
{
   [[NSUserDefaults standardUserDefaults] setObject: [refreshTypePopupButton titleOfSelectedItem] forKey: @"Refresh"];
}


- (void) textDidChange:(NSNotification *) aNotification
{
   NSString *refreshText = [refreshTypePopupButton titleOfSelectedItem];
   
   // use delayed timer, this will delay until "idle"
   
   if ([refreshText isEqualToString: @"delayed"])
   {
      [NSObject cancelPreviousPerformRequestsWithTarget:self
                                               selector:@selector( updatePreview)
                                                 object:nil];
      [self performSelector:@selector( updatePreview)
                 withObject:nil
                 afterDelay:0.5];
      return;
   }
   
   // or else do it for every change
   if ([refreshText isEqualToString: @"live"])
   {
      [self refresh: self];
   }
}


- (NSString *) commandStringWithTemplateString:(NSString *) s
                            propertyListString:(NSString *) plistString
{
   //   NSString   *catPath;
   NSBundle               *bundle;
   NSData                 *data;
   NSError                *error;
   NSPropertyListFormat   format;
   NSString               *cmdString;
   NSString               *mulleScionPath;
   NSString               *plistPath;
   id                     plist;
   
   if( ! mulleScionDirPath_)
   {
      bundle = [NSBundle mainBundle];
   //   catPath         = [bundle pathForResource: @"cat2html" ofType: NULL];
      mulleScionPath    = [bundle pathForResource: @"mulle-scion" ofType: NULL];
      mulleScionDirPath_ = [[mulleScionPath stringByDeletingLastPathComponent] copy];
   }
   
   plistPath = @"none";
   if( [plistString length])
   {
      data  = [plistString dataUsingEncoding:NSUTF8StringEncoding];
      plist = [NSPropertyListSerialization propertyListWithData:data
                                                        options:NSPropertyListImmutable
                                                         format:&format
                                                          error:&error];
      if( ! plist)
      {
         NSLog( @"plist error: %@", error);
         return( nil);
      }
      
      plistPath = [plist objectForKey:@"__BUNDLE__"];
      if( ! plistPath)
      {
         plistPath = @"/tmp/MulleScionistTemp.plist";
         [plistString writeToFile:plistPath  atomically: YES encoding: NSUTF8StringEncoding error: NULL];
      }
   }
   
   [s writeToFile: @"/tmp/MulleScionistTemp.scion" atomically: YES encoding: NSUTF8StringEncoding error: NULL];
   
   //
   // generate command string to create html from scion text using mulle-scion
   // give preference to "OS" mulle-scion, using included mulle-scion only as
   // a fallback
   //
   cmdString = [NSString stringWithFormat: @"PATH=\"$PATH:/usr/local/bin:%@\" mulle-scion /tmp/MulleScionistTemp.scion %@ > /tmp/MulleScionistTemp.html", mulleScionDirPath_, plistPath];
   return( cmdString);
}

#pragma mark -
#pragma mark WebView

- (NSView <WebDocumentView> *) webViewDocumentView
{
   return( [[[webView mainFrame] frameView] documentView]);
}


- (void) loadHTMLIntoWebView
{
   NSScrollView   *webScrollView;
   NSURLRequest   *request;
   NSURL          *url;
   
   // get the current scroll position of the document view of the web view
   webScrollView         = [[self webViewDocumentView] enclosingScrollView];
   currentScrollPosition = [[webScrollView contentView] bounds].origin;
   currentResponder_     = [[textView window] firstResponder];
   
   url     = [NSURL fileURLWithPath: @"/tmp/MulleScionistTemp.html"];
   request = [NSURLRequest requestWithURL: url];
   // tell the web view to load the generated, local html file
   [[webView mainFrame] loadRequest:request];
}


- (void) drawWebView
{
   NSString       *cmdString;
   NSString       *log;
   
   cmdString = [self commandStringWithTemplateString:[textView string]
                                  propertyListString:[plistTextView string]];
   if( ! cmdString)
      return;
   
   if( ! environment_)
      environment_ = [[[NSProcessInfo processInfo] environment] mutableCopy];

   if( [traceEnabledButton state] == NSOnState)
   {
      [environment_ setObject:@"egal"
                       forKey:@"MulleScionTrace"];
   }
   else
      [environment_ removeObjectForKey:@"MulleScionTrace"];
   
   // run the command
#ifndef DEBUG
   if( NSDebugEnabled)
#endif
      NSLog( @"%@", cmdString);
   log = [NSTask mulleSystem:cmdString
                 environment:environment_
             returningStderr:YES];
   
   [logTextView setString:log ? log : @"No errors"];
   
   [self loadHTMLIntoWebView];
}



- (void) protectCurrentResponderFocus:(NSTimer *) timer
{
   NSTimeInterval  now;
   
   now = [NSDate timeIntervalSinceReferenceDate];
   if( [[timer userInfo] timeIntervalSinceReferenceDate] + 1 < now)
   {
      [timer invalidate];
      return;
   }
   [[currentResponder_ window] makeFirstResponder:currentResponder_];
}


- (void) protectCurrentResponderFocusForDuration:(NSTimeInterval) duration
{
   [NSTimer scheduledTimerWithTimeInterval:0.05
                                    target:self
                                  selector:@selector( protectCurrentResponderFocus:)
                                  userInfo:[NSDate date]
                                   repeats:YES];
}

// delegate method we receive when it's done loading the html file.
- (void) webView:(WebView *)sender
didFinishLoadForFrame:(WebFrame *)frame
{
   [[self webViewDocumentView] scrollPoint:currentScrollPosition];
   
   //
   // a stupid hack to prevent the WebKit inspector from grabbing the
   // keyboard, due to reloading...
   //
   if( currentResponder_  == textView || currentResponder_  == plistTextView)
      [self protectCurrentResponderFocusForDuration:1.0];
}


- (void) updatePreview
{
   [NSObject cancelPreviousPerformRequestsWithTarget:self
                                            selector:@selector( updatePreview)
                                              object:nil];
   [self refresh: self];
}


#pragma mark -
#pragma mark UKSyntaxColored stuff

- (NSString *) syntaxDefinitionFilename
{
   return( @"MulleScionSyntax");
}


- (NSStringEncoding) stringEncoding
{
   return( NSUTF8StringEncoding);
}


- (BOOL) readFromData:(NSData *) data
               ofType:(NSString *) typeName
               error:(NSError **) outError
{
   return( [super readFromData:data
                        ofType:typeName
                         error:outError]);
}


#pragma mark -
#pragma mark UKSyntaxColoredTextViewDelegate methods

- (NSString *) syntaxDefinitionFilenameForTextViewController: (UKSyntaxColoredTextViewController *) sender
{
   return( [self syntaxDefinitionFilename]);
}


- (NSDictionary*) syntaxDefinitionDictionaryForTextViewController: (UKSyntaxColoredTextViewController*) sender
{
   NSBundle       *bundle;
   NSDictionary   *dict;
   NSString       *path;
   
   bundle = [NSBundle mainBundle];
   path   = [bundle pathForResource:[self syntaxDefinitionFilename]
                             ofType:@"plist"];
   dict = [NSDictionary dictionaryWithContentsOfFile:path];
   if( ! dict)
   {
      NSLog(@"Failed to find the syntax dictionary at \"%@\"", path);
   }
   return( dict);
}


# pragma mark -
# pragma mark Datasource

// needz moar error handling
- (id) openPlist:(NSURL *) url
{
   NSPropertyListFormat  format;
   id           plist;
   NSData       *data;
   NSError      *error;
   
   data  = [NSData dataWithContentsOfURL:url];
   error = nil;
   plist = [NSPropertyListSerialization propertyListWithData:data
                                                     options:NSPropertyListImmutable
                                                      format:&format
                                                       error:&error];
   // could also show XML here if needed
   return( plist);
   //   return( [plist description]);
}


// needz moar error handling
- (id) openBundle:(NSURL *) url
{
   NSBundle   *bundle;
   
   // avoid loading bundle...
   bundle = [NSBundle bundleWithURL:url];
   if( ! [[bundle bundleIdentifier] length])
      return( nil);
   
   return( [NSDictionary dictionaryWithObjectsAndKeys:[url path], @"__BUNDLE__", nil]);
}


# pragma mark -
# pragma mark Menu

- (IBAction) openDataSource:(id)sender
{
   NSOpenPanel  *panel;
   NSInteger    choice;
   NSURL        *url;
   NSString     *type;
   id           plist;
   
   panel = [NSOpenPanel openPanel];

   [panel setCanChooseFiles:YES];
   [panel setCanChooseDirectories:NO];
   [panel setAllowsMultipleSelection:NO]; // yes if more than one dir is allowed
   [panel setAllowedFileTypes:[NSArray arrayWithObjects:@"plist", @"bundle", @"framework", nil]]; //  @"bundle", nil]];

   choice = [panel runModal];
   if (choice != NSFileHandlingPanelOKButton)
      return;
   
   url = [[panel URLs] lastObject];
   if( ! url)
      return;
   
   plist = @"{}";
   type  = [[url path] pathExtension];
   if( [type isEqualToString:@"plist"])
      plist = [self openPlist:url];
   else
      if( [type isEqualToString:@"bundle"] || [type isEqualToString:@"framework"])
         plist = [self openBundle:url];
   
   if( ! plist)
   {
      NSRunAlertPanel(@"Load Error", @"failed to load dataSource", @"OK", nil, nil);
      return;
   }
   
   [plistTextView setString:[plist description]];
   [self refresh:self];
}


- (IBAction) prettyPrint:(id)sender
{
   NSString        *s;
   NSDictionary    *options;
   MulleHTMLTidy   *tidy;
   NSData          *data;
   NSNumber        *no;
   NSNumber        *yes;
   NSNumber        *_auto_;

   // It would actually be better to move this into a separate executable,
   // for stability and memory usage

   // use options to make tidy as harmless as possible, just pretty print
   no      = [NSNumber numberWithBool:NO];
   yes     = [NSNumber numberWithBool:YES];
   _auto_  = [NSNumber numberWithBool:2 /* auto */];
   options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:3], @"indent-spaces",
              _auto_, @"indent",
              [NSNumber numberWithInt:0], @"wrap",
              yes, @"markup",
              no, @"coerce-endtags",
              no, @"drop-empty-paras",
              no, @"drop-empty-elements",
              no, @"fix-bad-comments",
              no, @"quote-nbsp",
              no, @"quote-ampersand",
              no, @"wrap-sections",
              no, @"wrap-asp",
              no, @"wrap-jste",
              no, @"wrap-php",
              no, @"fix-backslash",
              no, @"quote-ampersand",
              no, @"tidy-mark",
              no, @"fix-uri",
              no, @"lower-literals",
              no, @"join-styles",
              no, @"ncr",
              no, @"merge-emphasis",
              no, @"merge-divs",
              no, @"merge-spans",
              yes, @"preserve-entities",
              no, @"anchor-as-name",
              nil];
   
   data = [[textView string] dataUsingEncoding:NSUTF8StringEncoding];
   tidy = [[MulleHTMLTidy new] autorelease];
   [tidy setOptions:options];

   if( [tidy parseXHTMLData:data
   allowLossyConversion:NO])
   {
      data = [tidy prettyPrintedData];
      s    = [[[NSString alloc] initWithData:data
                                    encoding:NSUTF8StringEncoding] autorelease];
      if( [s length])
         [textView setString:s];
   }
   
   [NSObject cancelPreviousPerformRequestsWithTarget:self
                                            selector:@selector( updatePreview)
                                               object:nil];
   s = [tidy diagnostics];
   if( ! s)
      s = @"No errors";
   [logTextView setString:s];
}

@end