/*
**  CodeEditorView.m
**
**  Copyright (c) 2002, 2003
**
**  Author: Yen-Ju Chen <yjchenx@hotmail.com>
**          Bjoern Giesler <bjoern@giesler.de>
**
**  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "CodeEditorView.h"
#include "RulesetManager.h"
#include "CodeEditorViewPreference.h"
#include "CEView+BlockMarking.h"
#include "CEView+Indentation.h"
#include "CEView+TextUtils.h"
#include "CEView+Fontification.h"
#include "CEView+Inspector.h"
#include "NSAttributedString+FindAndReplace.h"
#include "BundleLoader.h"
#include "FontificationHandler.h"
#include <AppKit/AppKit.h>
#include "CEViewTypesetter.h"
#include "CEViewLayoutManager.h"
#include <math.h>

@implementation CodeEditorView

- (void) scrollSelectedRangeToTop: (id) sender
{
  int line;
  NSRect rect;
  [self sizeToFit];

  [self getCursorPosOverall: NULL
                     inLine: NULL
                     atLine: &line];

  rect = [layoutManager lineFragmentRectForGlyphAtIndex: 0
                        effectiveRange: NULL];
 
  rect.origin.y = (line-1) * rect.size.height;
  rect.size = [self visibleRect].size;
  //rect.size.height -= 2 * lineHeight;
  [self scrollRectToVisible: rect]; 
}


- (void) showSideTextView
{
  if (hasSideTextView)
    {
      [self setTextContainerInset: NSMakeSize(0,0)];
      hasSideTextView = NO;
    }
  else
    {
      [self setTextContainerInset: NSMakeSize(40, 0)];
      hasSideTextView = YES;
    }
}

- (BOOL) isDocumentEdited
{
  return documentEdited;
}

- (void) setDocumentEdited: (BOOL) bool
{
  documentEdited = bool;
  if (bool == YES)
    [[self window] setDocumentEdited: YES];
}

- (void) fontification
{
  if ([self supportLanguage: languageName])
    {
       [self unmarkPreviouslyMarkedBlock];

       fontifying = YES;
       [self fontifyAccordingToLanguage: languageName];
       fontifying = NO; 

       [self markBlockAroundCursor];
    }
  needsFontification = NO;
}

/* Private method */
-(NSTextContainer *) buildUpTextNetwork: (NSSize)aSize
{
  NSTextContainer *tc;
  NSLayoutManager *lm;
  NSTextStorage *ts;

  ts = [[NSTextStorage alloc] init];

  lm = [[CEViewLayoutManager alloc] init];

  [ts addLayoutManager: lm];
  RELEASE(lm);

  tc = [[NSTextContainer alloc] initWithContainerSize: aSize];
  [lm addTextContainer: tc];
  RELEASE(tc);

  /* The situation at this point is as follows: 

     textView (us) --RETAINs--> textStorage 
     textStorage   --RETAINs--> layoutManager 
     layoutManager --RETAINs--> textContainer */

  /* We keep a flag to remember that we are directly responsible for 
     managing the text network. */
  _tf.owns_text_network = YES;

  return tc;
}

- (id) initWithFrame: (NSRect) frame
{
  Class bundleClass;
  NSString *bundleName;
  int i, count;
  NSTextContainer *textContainer;

  /* Use built-in text system */
  textContainer = [self buildUpTextNetwork: frame.size];
  self = [self initWithFrame: frame textContainer: textContainer];

  markedStartRange.location = NSNotFound;
  markedEndRange.location = NSNotFound;

  languageName = nil;
  needsFontification = NO;
  hasBlockMarked = NO;

  lineHeight = 0;

  ASSIGN(normalFont, [[RulesetManager sharedRulesetManager] normalFont]);
  
  [self setFont: normalFont];
//  [self setString: @""];
  [self setSelectedRange: NSMakeRange(0,0)];
  [self setRichText: NO];
  [self setImportsGraphics: NO];
  [self setUsesFontPanel: NO];
  [self setUsesRuler: NO];

  [self performSelector: @selector(periodicTask:)
	withObject: self
	afterDelay: .2];

  [[self textStorage] setDelegate: self];

  preference = [CodeEditorViewPreference sharedCodeEditorViewPreference];
  
  hasSideTextView = NO;

  if ([preference displaySideView])
    {
      [self showSideTextView];
    }

  [[self textContainer] setContainerSize: NSMakeSize(1e7, 1e7)];

  layoutManager = [self layoutManager];

  /* Load language bundle */
  bundleLanguages = [NSMutableArray new];
  bundleNames = [[BundleLoader defaultLoader] allNamesOfBundles];
  RETAIN(bundleNames);

  count = [bundleNames count];
 
  for (i = 0; i < count; i++)
    {
      bundleName = [bundleNames objectAtIndex: i];
      bundleClass = [[BundleLoader defaultLoader] classNameOf: bundleName];
      [bundleLanguages addObject: [bundleClass language]];
    }

  return self;
}

- (NSString *)languageName
{
  if(!languageName)
    return @"Text";
  return languageName;
}

- (void)setLanguageName: (NSString *) name
{
  NSString *bundleName;
  unsigned int index;
  ASSIGN(languageName, name);
  needsFontification = YES;

  /* Get block handler */
  index = [bundleLanguages indexOfObject: name];
  if (index != NSNotFound)
    {
      bundleName = [[[BundleLoader defaultLoader] allNamesOfBundles] objectAtIndex: index];
      fontificationHandlerClass = [[BundleLoader defaultLoader] classNameOf: bundleName];
      blockHandlerClass = [fontificationHandlerClass blockHandlerClass];
      indentationHandlerClass = [fontificationHandlerClass indentationHandlerClass];
      methodHandlerClass = [fontificationHandlerClass methodHandlerClass];
    }
}

- (void)drawRect: (NSRect) rect
{
  NSPoint drawPoint;

  [super drawRect: rect];

  if (hasSideTextView)
    {
      /* draw side text view */
      unsigned int i, len;
      NSString *text;
      NSRect visibleRect;
      NSString *s;
      NSDictionary *attributes;

      text = [[self textStorage] string];
      len = [text length];

      visibleRect = [self visibleRect];
      [[NSColor lightGrayColor] set];
      [NSBezierPath fillRect: NSMakeRect(visibleRect.origin.x, 
                                         visibleRect.origin.y,
                                         40, visibleRect.size.height)];

      if (len)
        {
          attributes = [NSDictionary dictionaryWithObjectsAndKeys:
                          [NSColor blackColor], NSForegroundColorAttributeName,
                                   normalFont , NSFontAttributeName,
                              nil];
          /* Draw marked area first */
          if (hasSideMarked)
            {
              [[NSColor yellowColor] set];
              [NSBezierPath fillRect: sideMarkedRect];
            }

          /* FIXME: Assume all the line are the same height,
                    which is set in typesetter.
           */
          lineHeight = [layoutManager lineFragmentRectForGlyphAtIndex: 0
                                      effectiveRange: NULL].size.height;
        {
           int start, end;
           float delta;
           visibleRect.origin.y -= 5;
           visibleRect.size.height += 10;
           start = (int)floor(NSMinY(visibleRect)/lineHeight);
           end = (int)floor(NSMaxY(visibleRect)/lineHeight)+1;
           delta = (lineHeight-[normalFont pointSize])/2;
           if (start < 0) start = 0;
           for (i = start; i < end+1; i++)
             {
               drawPoint = NSMakePoint(3, i*lineHeight+delta);
               s = [[NSString alloc] initWithFormat: @"%d", i+1];
               [s drawAtPoint: drawPoint withAttributes: attributes];
               RELEASE(s);
             }
        }
        }
    }
}

- (void) finishedTypingWord: (NSString *) word
                      range: (NSRange) range
{
  if ([preference autoFontificationEachLine])
    {
      NSRange lineRange;
      NSColor *color;

      lineRange = [self rangeOfLineAtCursor];

      /* Delete word */
      if (NSMaxRange(range) > [[self textStorage] length])
        range.length = [[self textStorage] length] - range.location;

      if ([self supportLanguage: languageName])
        {
          [self unmarkPreviouslyMarkedBlock];

          fontifying = YES;

          color = [[RulesetManager sharedRulesetManager] colorForType: @"Normal"];

          [[self textStorage] addAttribute: NSFontAttributeName
                                     value: normalFont
                                     range: lineRange];
          [[self textStorage] addAttribute: NSForegroundColorAttributeName
                                     value: color
                                     range: lineRange];

          [self fontifyAccordingToLanguage: languageName inRange: lineRange];
          fontifying = NO; 
                    
          [self markBlockAroundCursor];
        }
      needsFontification = NO;
    }
}

- (void) keyDown: (NSEvent *) theEvent
{
  unichar fnkey;
  BOOL cursorMotion;
  NSRange wordAtBeginRange;
  NSString *wordAtBegin, *wordAtEnd;
  NSString *tabString;
  unsigned point;
  
  userPressingKey = YES;

  if (hasBlockMarked)
    {
      [self unmarkPreviouslyMarkedBlock];
      hasBlockMarked = NO;
    }

  wordAtBegin = [self wordAtCursor];
  wordAtBeginRange = [self rangeOfWordAtCursor];
  
  if([[theEvent characters] length])
    fnkey = [[theEvent characters] characterAtIndex: 0];
  else
    fnkey = (unichar)0;

  if((fnkey == NSLeftArrowFunctionKey) ||
     (fnkey == NSRightArrowFunctionKey) ||
     (fnkey == NSUpArrowFunctionKey) ||
     (fnkey == NSDownArrowFunctionKey))
    {
      cursorMotion = YES;

      [super keyDown: theEvent];
    }
  else
    {
      cursorMotion = NO;
      [self setDocumentEdited: YES];
    
      if([[theEvent characters] isEqualToString: @"\t"])
        {
          if ((tabString = [preference tabString]))
            [self insertText: tabString];
          else /* Auto Indentation */
            {
	      point = [self selectedRange].location;
	      point += [self syntacticallyIndentLineAtIndex: point];
	      [self setSelectedRange: NSMakeRange(point, 0)];
            }
        }
      else if([[theEvent characters] isEqualToString: @"\r"])
        {
          // Return
          [super keyDown: theEvent];
          if ([preference autoIndentation])
            {
              point = [self selectedRange].location;
              point += [self syntacticallyIndentLineAtIndex: point];
              [self setSelectedRange: NSMakeRange(point, 0)];
            }
        }
      else
        [super keyDown: theEvent];

      needsFontification = YES;
    }

    /* Mark delimiter */
    wordAtEnd = [self wordAtCursor];

    if ([preference autoMarkBlock])
      {
        if ([blockHandlerClass isDelimiter: wordAtEnd])
          [self markBlockAroundCursor];
      }
 
  if (cursorMotion == NO)
    {
      if (wordAtBegin) 
        {
          if([wordAtEnd rangeOfString: wordAtBegin].location == NSNotFound)
            [self finishedTypingWord: wordAtBegin
                               range: wordAtBeginRange];
        }
    }
  userPressingKey = NO;
}

- (void) markBlockAroundCursor
{
  [self findAndMarkBlockAtCursor];
  hasBlockMarked = YES;
}

- (void)periodicTask: (id) sender
{
  static float sinceFontify;
  static float sincePeriodicTask;
  static int lastLines;

  sinceFontify += .2;
  sincePeriodicTask += .2;

  if(userPressingKey)
    {
      /* Skip this one */
      sinceFontify = 0;
      sincePeriodicTask = 0;
      [self performSelector: @selector(periodicTask:)
	    withObject: self
	    afterDelay: .2];
      
      return;
    }
      
  if(sinceFontify > [preference fontificationInterval])
    {
      if([preference autoFontification] && needsFontification)
        {
          /* Not over threshold */
          if ( !([preference autoFontificationThreshold] &&
                 ([preference fontificationThreshold] < lastLines)) )
            {
              if ((documentEdited && [preference autoFontification]) ||
                  needsFontification)
                {
                  [self fontification];  
                 }
	      needsFontification = NO;
            }
        }
      sinceFontify = 0;
    }

  /* Check lines every 10 seconds in order to get accurate lines
   * so that it can apply to threshold */
  if (sincePeriodicTask > 10)
    {
      [self getNumberOfGlyphs: NULL
                        lines: &lastLines];
    }

  [self performSelector: @selector(periodicTask:)
	withObject: self
	afterDelay: .2];
}

- (BOOL) searchString: (NSString *) string
             backward: (BOOL) backward
      caseInsensitive: (BOOL) caseInsensitive
{
  NSRange cursor, range;
  cursor = [self selectedRange];

  if (cursor.location == NSNotFound)
    cursor = NSMakeRange(0, [[self textStorage] length]);

  if (backward == NO)
    {
      range = [[self textStorage] rangeOfString: string
                         inRange: NSMakeRange(cursor.location + cursor.length,
                                              [[self textStorage] length] - cursor.location - cursor.length)
                        backward: backward
                 caseInsensitive: caseInsensitive];
    }
  else
    {
      range = [[self textStorage] rangeOfString: string
                         inRange: NSMakeRange(0, cursor.location)
                        backward: backward
                 caseInsensitive: caseInsensitive]; 
    }

  if (range.location == NSNotFound)
    return NO;  

  [self setSelectedRange: range];
  [self scrollRangeToVisible: range];
  return YES;
}

- (void)textStorageDidProcessEditing: (NSNotification *) not
{
  if(!fontifying)
    {
      /* [not object] is the whole attributed string */
      [self setDocumentEdited: YES];
    }
}

- (NSArray *) supportedLanguages
{
  return bundleLanguages;
} 

- (void) dealloc
{
  RELEASE(languageName);
  RELEASE(normalFont);
  RELEASE(bundleLanguages);
  RELEASE(bundleNames);
  [super dealloc];
}

@end
