/* Modified by Yen-Ju Chen <yjchenx at hotmail dot com> */
/*
   CEViewTypesetter.m

   Copyright (C) 2002, 2003 Free Software Foundation, Inc.

   Author: Alexander Malmberg <alexander@malmberg.org>
   Date: November 2002 - February 2003

   This file is part of the GNUstep GUI Library.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this library; see the file COPYING.LIB.
   If not, write to the Free Software Foundation,
   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include "CEViewTypesetter.h"

#include <math.h>

#include <Foundation/NSDebug.h>
#include <Foundation/NSException.h>
#include <Foundation/NSGeometry.h>
#include <Foundation/NSLock.h>
#include <Foundation/NSValue.h>

#include "AppKit/NSParagraphStyle.h"
#include "AppKit/NSTextContainer.h"
#include "AppKit/NSTextStorage.h"
#include "GNUstepGUI/GSLayoutManager.h"
#include "GNUstepGUI/GSLayoutManager_internal.h"

#include "RulesetManager.h"
#include "CEViewLayoutManager.h"

/*
Note that unless the user creates extra instances, there will only be one
instance of GSHorizontalTypesetter for all text typesetting, so we can
cache fairly aggressively without having to worry about memory consumption.
*/


@implementation CEViewTypesetter

- (id) init
{
  if (!(self = [super init])) return nil;
  lock = [[NSLock alloc] init];
  return self;
}

-(void) dealloc
{
  if (cache)
    {
      free(cache);
      cache = NULL;
    }
  DESTROY(lock);
  [super dealloc];
}

static CEViewTypesetter *shared;

+(GSTypesetter *) sharedSystemTypesetter
{
  return [CEViewTypesetter sharedInstance];
}

+(CEViewTypesetter *) sharedInstance
{
  if (!shared)
    shared = [[self alloc] init];
  return shared;
}

#define CACHE_INITIAL 192
#define CACHE_STEP 192

typedef struct GSHorizontalTypesetter_glyph_cache_s
{
  /* These fields are filled in by the caching: */
  NSGlyph g;
  unsigned int char_index;
  unichar ch;

  /* These fields are filled in during layout: */
  BOOL nominal;
  NSPoint pos;    /* relative to the line's baseline */
  NSSize size;    /* height is used only for attachments */
  BOOL dont_show;
} glyph_cache_t;

/* TODO: if we could know whether the layout manager had been modified since
the last time or not, we wouldn't need to clear the cache every time */
-(void) _cacheClear
{
  cache_length = 0;
}

-(void) _cacheMoveTo: (unsigned int)glyph
{
  BOOL valid;

  if (cache_base <= glyph && cache_base + cache_length > glyph)
    {
      int delta = glyph - cache_base;
      cache_length -= delta;
      memmove(cache,&cache[delta],sizeof(glyph_cache_t) * cache_length);
      cache_base = glyph;
      return;
    }

  cache_base = glyph;
  cache_length = 0;

  switch(cacheType) 
    {
      case CharacterCache:
        valid = (glyph > numberOfChar) ? NO : YES;
        break;
      default:
        valid = [curLayoutManager isValidGlyphIndex: glyph];
    }

  at_end = (valid) ? NO: YES;
}

static glyph_run_t run;

-(void) _cacheGlyphs: (unsigned int)new_length
{
  glyph_cache_t *g;
  BOOL valid;
 
  if (cache_size < new_length)
    {
      cache_size = new_length;
      cache = realloc(cache,sizeof(glyph_cache_t) * cache_size);
    }

  for (g = &cache[cache_length]; cache_length < new_length; cache_length++, g++)
    {
      if (cacheType == CharacterCache)
        {
          unsigned index = cache_base+cache_length;

          if ((valid = [curLayoutManager isValidGlyphIndex: index]))
            {
              NSGlyph gly;
              unichar ch;
              unsigned delta;

              ch = [[curTextStorage string] characterAtIndex: index];
              delta = ch-0x20;

              if ((delta < ('~'-' ')) && (gly = glyphCache[delta]))
                {
                  g->g = gly;
                }
              else
                {
                  [curLayoutManager _generateGlyphsForRun: &run at: index];

                  g->g = run.glyphs->g;

                  if ((ch >= 0x20) && (ch <= 0x7E))
                    {
                      glyphCache[ch-0x20] = run.glyphs->g;
                    }
                }
            }
        }
      else
        {
          g->g = [curLayoutManager glyphAtIndex: cache_base + cache_length
                                   isValidIndex: &valid];
        }

      if (!valid)
	{
	  at_end = YES;
	  break;
	}

      if (cacheType == CharacterCache)
        g->char_index = cache_base + cache_length;
      else
        g->char_index = [curLayoutManager characterIndexForGlyphAtIndex: cache_base + cache_length];

      g->ch = [[curTextStorage string] characterAtIndex: g->char_index];
      g->dont_show = NO;
    }
}

-(int) lastParagraph: (BOOL)newParagraph
{
  /*
  We've typeset all glyphs, and thus return 2. If we ended with a
  new-line, we set the extra line frag rect here so the insertion point
  will be properly positioned after a trailing newline in the text.
  */
  NSRect r, r2, remain;

  if (!newParagraph || !curGlyph)
    return 2;

  /*
  We aren't actually interested in the glyph data, but we want the
  attributes for the final character so we can make the extra line
  frag rect match it. This call makes sure that cur* are set.
  */
  [self _cacheMoveTo: curGlyph - 1];

  line_height = [__font defaultLineHeightForFont];

  r = NSMakeRect(0, curPoint.y,
                 [curTextContainer containerSize].width,
                 line_height + lineSpacing);

  r = [curTextContainer lineFragmentRectForProposedRect: r
                                         sweepDirection: NSLineSweepRight
                                      movementDirection: NSLineMoveDown
                                          remainingRect: &remain];

  if (!NSIsEmptyRect(r))
    {
      r2 = r;
      r2.size.width = 1;
      [curLayoutManager setExtraLineFragmentRect: r
                                        usedRect: r2
                                   textContainer: curTextContainer];
    }
  return 2;
}

/*
Return values 0, 1, 2 are mostly the same as from
-layoutGlyphsInLayoutManager:.... Additions:

  0   Last typeset character was not a newline; next glyph does not start
      a new paragraph.

  3   Last typeset character was a newline; next glyph starts a new
      paragraph.

  4   Last typeset character may or may not have been a newline; must
      test before next call.

*/
-(int) layoutLineNewParagraph: (BOOL)newParagraph
{
  NSRect rect;

  /*
  These are values for the line as a whole. We start out by initializing
  for the first glyph on the line and then update these as we add more
  glyphs.

  (TODO (optimization): if we're dealing with a "simple rectangular
  text container", we should try to extend the existing line frag in place
  before jumping back to do all the expensive checking).
  */

  /*
  This calculation should match the calculation in [GSFontInfo
  -defaultLineHeightForFont], or text will look odd.
  */

  /* TODO: doesn't have to be a simple horizontal container, but it's easier
  to handle that way. */
  if ([curTextContainer isSimpleRectangularTextContainer] &&
      [curLayoutManager _softInvalidateFirstGlyphInTextContainer: curTextContainer] == curGlyph)
    {
      /*
      We only handle the simple-horizontal-text-container case currently.
      */
      NSRect r0, r;
      NSSize shift;
      int i;
      unsigned int g, g2, first;
      float container_height;
      /*
      Ask the layout manager for soft-invalidated layout for the current
      glyph. If there is a set of line frags starting at the current glyph,
      and we can get rects with the same size and horizontal position, we
      tell the layout manager to use the soft-invalidated information.
      */

      r0 = [curLayoutManager _softInvalidateLineFragRect: 0
					      firstGlyph: &first
					       nextGlyph: &g
					 inTextContainer: curTextContainer];

      container_height = [curTextContainer containerSize].height;
      if (curPoint.y + r0.size.height <= container_height)
	{
	  /*
	  We can shift the rects and still have things fit. Find all the line
	  frags in the line and shift them.
	  */
	  shift.width = 0;
	  shift.height = curPoint.y - r0.origin.y;
	  i = 1;
	  curPoint.y = NSMaxY(r0) + shift.height;
	  for (; 1; i++)
	    {
	      r = [curLayoutManager _softInvalidateLineFragRect: i
						     firstGlyph: &first
						      nextGlyph: &g2
						inTextContainer: curTextContainer];

	      /*
	      If there's a gap in soft invalidated information, we need to
	      fill it in before we can continue.
	      */
	      if (first != g)
		{
		  break;
		}

	      if (NSIsEmptyRect(r) || NSMaxY(r) + shift.height > container_height)
		break;

	      g = g2;
	      curPoint.y = NSMaxY(r) + shift.height;
	    }

	  [curLayoutManager _softInvalidateUseLineFrags: i
					      withShift: shift
					inTextContainer: curTextContainer];

	  curGlyph = g;
	  return 4;
	}
    }


  [self _cacheMoveTo: curGlyph];
  if (!cache_length)
    [self _cacheGlyphs: CACHE_INITIAL];

  if (!cache_length && at_end)
    {
      return [self lastParagraph: newParagraph];
    }

  rect = NSMakeRect(0, curPoint.y,
                    [curTextContainer containerSize].width,
                    line_height + lineSpacing);

  {
    unsigned int i = 0, j;
    glyph_cache_t *g = cache;
    NSPoint p = NSMakePoint(0,0);
    BOOL prev_had_non_nominal_width = NO;;
    NSRect used_rect;
    unsigned delta;
    float advance;
    unichar ch;

    while (1)
      {
        /* Update the cache. */
        if (i >= cache_length)
          {
            if (at_end)
              {
                newParagraph = NO;
                break;
              }
            [self _cacheGlyphs: cache_length + CACHE_STEP];
            if (i >= cache_length)
              {
                newParagraph = NO;
                break;
              }
            g = cache + i;
          }

        /* At this point:

	  p is the current point (sortof); the point where a nominally
	  spaced glyph would be placed.

	  g is the current glyph. i is the current glyph index, relative to
	  the start of the cache.

	  Note that the variables tracking the previous glyph shouldn't be
	  updated until we know that the current glyph will fit in the line
	  frag rect.  */

	if (g->g == NSControlGlyph)
	  {
            ch = g->ch;

	    /* TODO: need to handle other control characters */

	    g->pos = p;
	    g->size.width = 0;
	    g->dont_show = YES;
	    g->nominal = !prev_had_non_nominal_width;
	    i++;
	    g++;

	    prev_had_non_nominal_width = NO;

	    if ((ch == 0x0A) || (ch == 0x0D))
	      {
	        newParagraph = YES;
	        break;
	      }

	    if (ch == 0x09)
	      {
		/*
		Handle tabs. This is a very basic and stupid implementation.
		TODO: implement properly
		*/
		NSArray *tabs = [curParagraphStyle tabStops];
		NSTextTab *tab = nil;
		int i, c = [tabs count];
		/* Find first tab beyond our current position. */
		for (i = 0; i < c; i++)
		  {
		    tab = [tabs objectAtIndex: i];
		    /*
		    We cannot use a tab at our exact location; we must
		    use one beyond it. The reason is that several tabs in
		    a row would get very odd behavior. Eg. given "\t\t",
		    the first tab would move (exactly) to the next tab
		    stop, and the next tab stop would move to the same
		    tab, thus having no effect.
		    */
		    if ([tab location] > p.x + rect.origin.x)
		      break;
		  }
		if (i == c)
		  {
		    /* TODO: we're already past all the tab stops. what
		    should we do?

		    Pretend that we have tabs every 100 points.
		    */
		    p.x = (floor(p.x / 100.0) + 1.0) * 100.0;
		  }
		else
		  {
		    p.x = [tab location] - rect.origin.x;
		  }
		prev_had_non_nominal_width = YES;
		continue;
	      }

	    NSDebugLLog(@"CEViewTypesetter",
	      @"ignoring unknown control character %04x\n", ch);

	    continue;
	  }

	/* Set up glyph information. */

	g->nominal = !prev_had_non_nominal_width;
        g->pos = p;

        ch = g->ch;
        delta = ch-0x20;
                 
        if ((delta < ('~'-' ')) && (advance = advancementCache[delta]))
          {
            g->size.width = advance;
          }                     
        else
          {
            g->size = [__font advancementForGlyph: g->g];
       
            if ((ch >= 0x20) && (ch <= 0x7E))
              {
                advancementCache[ch-0x20] = g->size.width;
              }
           }
        p.x += g->size.width;
        prev_had_non_nominal_width = NO;
        i++;
        g++;
      }
    /* Basic layout is done. */

    __line_last_glyph = i;
    __line_last_used = p.x;

    curGlyph = i + cache_base;

    i = 0;
    g = cache;

    used_rect = rect;
    used_rect.origin.x += g->pos.x;
    used_rect.size.width = __line_last_used - g->pos.x;

    [curLayoutManager setLineFragmentRect: rect
                            forGlyphRange: NSMakeRange(cache_base + i, __line_last_glyph - i)
			         usedRect: used_rect];

#if 0 /* FIXME: Greatly improve the speed, though may have unknown side effect */
    if (g->dont_show)
      {
        [curLayoutManager setNotShownAttribute: YES
                               forGlyphAtIndex: cache_base + i];
      }
#endif

    p = g->pos;
    p.y += baseline;
    j = i;
    while (i < __line_last_glyph)
      {
        if (!g->nominal && i != j)
          {
            [curLayoutManager setLocation: p
                     forStartOfGlyphRange: NSMakeRange(cache_base + j, i - j)];
            p = g->pos;
            p.y += baseline;
            j = i;
          }
        i++;
        g++;
      }
    if (i != j)
      {
        [curLayoutManager setLocation: p
 	         forStartOfGlyphRange: NSMakeRange(cache_base + j,i - j)];
      }
  }

  curPoint = NSMakePoint(0,NSMaxY(rect));

  if (newParagraph)
    return 3;
  else
    return 0;
}


-(int) layoutGlyphsInLayoutManager: (GSLayoutManager *)layoutManager
		   inTextContainer: (NSTextContainer *)textContainer
	      startingAtGlyphIndex: (unsigned int)glyphIndex
	  previousLineFragmentRect: (NSRect)previousLineFragRect
		    nextGlyphIndex: (unsigned int *)nextGlyphIndex
	     numberOfLineFragments: (unsigned int)howMany
{
  int ret = 4, real_ret;
  BOOL newParagraph;

  if (![lock tryLock])
    {
      /* Since we might be the shared system typesetter, we must be
      reentrant. Thus, if we are already in use and can't lock our lock,
      we create a new instance and let it handle the call. */
      CEViewTypesetter *temp;

      temp = [[isa alloc] init];
      ret = [temp layoutGlyphsInLayoutManager: layoutManager
			      inTextContainer: textContainer
			 startingAtGlyphIndex: glyphIndex
		     previousLineFragmentRect: previousLineFragRect
			       nextGlyphIndex: nextGlyphIndex
			numberOfLineFragments: howMany];
      DESTROY(temp);
      return ret;
    }

NS_DURING
  curLayoutManager = layoutManager;
  curTextContainer = textContainer;
  curTextStorage = [layoutManager textStorage];
  curParagraphStyle = [NSParagraphStyle defaultParagraphStyle];
  minimumLineHeight = [curParagraphStyle minimumLineHeight];
  maximumLineHeight = [curParagraphStyle maximumLineHeight];
  lineSpacing = [curParagraphStyle lineSpacing];

  /* Set up our initial baseline info. */
  max_line_height = maximumLineHeight;

  if (max_line_height < minimumLineHeight)
    max_line_height = minimumLineHeight;

  line_height = [__font defaultLineHeightForFont];
  descender = -[__font descender];
  baseline = line_height - descender;

  if (line_height < minimumLineHeight)
    line_height = minimumLineHeight;

  if (max_line_height > 0 && line_height > max_line_height)
    line_height = max_line_height;

  curGlyph = glyphIndex;

  [self _cacheClear];
  
  real_ret = 4;
  curPoint = NSMakePoint(0,NSMaxY(previousLineFragRect));

  __font = [[RulesetManager sharedRulesetManager] normalFont];
  run.font = __font;  
  run.head.char_length = 1;

  numberOfChar = [curTextStorage length];
  numberOfGlyph = [curLayoutManager numberOfGlyphs];

  if (numberOfChar == numberOfGlyph)
    cacheType = CharacterCache;
  else
    cacheType = NoCache;

  glyphCache = calloc(('~'-' '), sizeof(NSGlyph)); /* From 0x20 - 0x7E */
  advancementCache = calloc(('~'-' '), sizeof(float)); /* From 0x20 - 0x7E */

  [curLayoutManager setTextContainer: curTextContainer
                       forGlyphRange: NSMakeRange(0, numberOfGlyph)];

  while (1)
    {
      if (real_ret == 4)
	{
	  /*
	  -layoutLineNewParagraph: needs to know if the starting glyph is the
	  first glyph of a paragraph so it can apply eg. -firstLineHeadIndent and
	  paragraph spacing properly.
	  */
	  if (!curGlyph)
	    {
	      newParagraph = YES;
	    }
	  else
	    {
	      unsigned int chi;
	      unichar ch;
	      chi = [curLayoutManager characterRangeForGlyphRange: NSMakeRange(curGlyph - 1, 1)
						 actualGlyphRange: NULL].location;
	      ch = [[curTextStorage string] characterAtIndex: chi];
	
	      if (ch == '\n')
		newParagraph = YES;
	      else
		newParagraph = NO;
	    }
	}
      else if (real_ret == 3)
	{
	  newParagraph = YES;
	}
      else
	{
	  newParagraph = NO;
	}

      ret = [self layoutLineNewParagraph: newParagraph];

      real_ret = ret;

      if (ret == 3 || ret == 4)
	ret = 0;

      if (ret)
	break;

      if (howMany)
	if (!--howMany)
	  break;
   }

  free(glyphCache);

  *nextGlyphIndex = curGlyph;
NS_HANDLER
  [lock unlock];
  [localException raise];
NS_ENDHANDLER
  [lock unlock];
  return ret;
}


@end

