/*
 * Copyright (c) 2004 Nokia. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the
 * distribution.
 *
 * Neither the name of Nokia nor the names of its contributors may be
 * used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include <assert.h>
#include <glib.h>
#include <gdk/gdk.h>
#include <fontconfig/fontconfig.h>
#if defined(__WIN32__)
#include <windows.h>
#include <gdiplus.h>
#else
#include <gdk/gdkx.h>
#include <X11/Xft/Xft.h>
#include <X11/Xlib.h>
#endif

#include "Win32TextRenderer.h"
#include "Win32TextRendererFactory.h"
#include "Win32Context.h"

static
UniChar*
dupCharsAndRemoveLF(const UniChar* chars, unsigned int length)
{
    assert(length>0);
    assert(chars);

    UniChar uc_space(' ');
    UniChar* dup = new UniChar[length];
    const gunichar2* uc_chars = reinterpret_cast<const gunichar2*>(chars);

    for(unsigned int i = 0; i < length; i++) {
        if (g_unichar_isspace(uc_chars[i]))
	    dup[i] = uc_space;
	else
	    dup[i] = chars[i];
    }

    return dup;
}

// notice: this code is assuming that there is no consecutive spaces

// [a,b[ range of chars that make a word, ie "abcd dabcd" would produce (0,4) (5,9) 
typedef struct _WordRange { unsigned short start; unsigned short stop; } WordRange;

static
int scanRunForWords(const WebCoreTextRun* run, int from, WordRange* words, int maxWords, int* nWords, int* totWords)
{
    assert(run);
    assert(run->from <= run->to);
    assert(nWords);

    *nWords = 0;
    int tot = 0;

    if (run->from == run->to)
	return from;

    const gunichar2* ucs = reinterpret_cast<const gunichar2*>(run->characters);

    // scan until end of run or we run out of word slots
    int start, stop;
    start = from;
    while (start < run->to && tot < maxWords) {

        // move the stop to point at the last char before space or at the end of the buffer
	// stop == start if there's consecutive spaces ('words of lenght == 0')
	for (stop = start; stop < run->to && !g_unichar_isspace(*(ucs + stop)); stop++) /* empty */; 

	words[tot].start = start;
	words[tot].stop = stop;
	++tot;

	start = stop + 1; // skip the space 
	++(*nWords);
    }
    
    if (totWords) {
	*totWords = tot;
	if (start < run->to) { 
	    // ran out of words before finished scanning
	    // scan the total amount of words

	    for(int pos = start; pos < run->to; pos++) {
		if (g_unichar_isspace(*(ucs + pos))) 
		    ++(*totWords);
	    }
	}
    }

    return start;
}

inline static
void getGDIPlusColorFromGdkColor(const GdkColor* gdkc, Gdiplus::Color* winc)
{
   // Create an ARGB value from the four component values.
   Gdiplus::ARGB argb = Gdiplus::Color::MakeARGB( 0xff, 
				(BYTE) gdkc->red / 256, 
				(BYTE) gdkc->green / 256, 
				(byte) gdkc->blue / 256);
   winc->SetValue(argb);
}


Win32TextRenderer::Win32TextRenderer(Win32TextRendererFactory* aowner, Win32NSFont *afont)
    : owner(aowner)
    , gdkxoff(0)
    , gdkyoff(0)
    , font(afont)
{  
    font->retain();

    lineSpacing = font->lineSpacing;
    xHeight = font->xHeight;
}

Win32TextRenderer::~Win32TextRenderer()
{
    font->release();

    if (owner) 
	owner->rendererDeleted(this);
}

void Win32TextRenderer::setContext(CGContextRef acontext)
{
    Win32Context* context = static_cast<Win32Context*>(acontext);
    assert(context);
    gdkxoff = gdkyoff = 0;
}

float Win32TextRenderer::measureRange(const WebCoreTextRun *run, const WebCoreTextStyle *style, int from, int to, float* widths)
{
  HDC dc = GetDC(0L);
  Gdiplus::Graphics graphics(dc);
  Gdiplus::SizeF size;
  Gdiplus::RectF extents;
  Gdiplus::PointF point(0.0f, 0.0f);
  const Gdiplus::StringFormat * format = Gdiplus::StringFormat::GenericDefault();

  if (style->letterSpacing == 0 && !widths) {
    // Measure the string.
    graphics.MeasureString(reinterpret_cast<const WCHAR*>(run->characters + from),
			 to - from, font->win32Font, point, format, &extents);
    extents.GetSize(&size);
    return size.Width;
  }
  
  float w, totalLen = 0.0f;
  int i = 0;
  while (from < to) {
    // Measure the string.
    graphics.MeasureString(reinterpret_cast<const WCHAR *>(run->characters[from]), 
			   1, font->win32Font, point, format, &extents);
    extents.GetSize(&size);
    w = size.Width + style->letterSpacing;
    totalLen += w;
    if (widths)
      widths[i++] = w;
    ++from;
  }   
  ReleaseDC(0L, dc);
  return totalLen;
}

float Win32TextRenderer::drawRange(const WebCoreTextRun *run, const WebCoreTextStyle *style, 
				   int from, int to, int x, int y, 
                                   const Gdiplus::Color* color, bool shouldMeasure)
{
  HDC dc = GetDC(0L);
  Gdiplus::Graphics graphics(dc);
  Gdiplus::SizeF size;
  Gdiplus::RectF extents;
  Gdiplus::PointF point((float) x , (float) y);
  Gdiplus::SolidBrush brush(*color);
  const Gdiplus::StringFormat * format = Gdiplus::StringFormat::GenericDefault();

  int width = 0;
  if (style->letterSpacing) {
    while (from<to) {
      // Measure the string.
      graphics.MeasureString(reinterpret_cast<const WCHAR *>(run->characters[from]), 
			     1, font->win32Font, point, format, &extents);
      graphics.DrawString(reinterpret_cast<const WCHAR *>(run->characters[from]), 
			  1, font->win32Font, extents, format, &brush);
      extents.GetSize(&size);
      x += (int) size.Width + style->letterSpacing;
      ++from;
      width += (int) size.Width + style->letterSpacing;
    }
    ReleaseDC(0L, dc);
    return width;
  } else {
    if (shouldMeasure) {
      // Measure the string.
      graphics.MeasureString(reinterpret_cast<const WCHAR*>(run->characters + from),
			   to - from, font->win32Font, point, format, &extents);
      extents.GetSize(&size);
      width += (int) size.Width;
    }

    Gdiplus::Point pos(x, y);
    graphics.DrawString(reinterpret_cast<const WCHAR *>(run->characters[from]), 
			to - from, font->win32Font, extents, format, &brush);
    }
    ReleaseDC(0L, dc);
    return width;
}

float Win32TextRenderer::floatWidthForRun(const WebCoreTextRun *run,
					  const WebCoreTextStyle *style,
					  float *widths)
{
    assert(run);
    assert(style);
    assert(style->families);
    
    if (run->length == 0)
	return 0.0f;

    const int wordBufSz = 10;	// size of scan buffer
    WordRange words[wordBufSz]; // scan buffer
    int nWords;			// number of words in scan buffer
    int totWords = 0;		// total word count



    // needs always totWords because wordSpacing is applied only to spaces in the middle, not
    // for the last word. Is this correct practice?
    scanRunForWords(run, run->from, words, wordBufSz, &nWords, &totWords);

    // run->from == run->to == 0
    if (totWords == 0)
	return 0.0f;  
    
    const float wordPadding = (totWords>1) ? style->padding / (totWords-1) : 0.0f; 	// padding between words

    float totalWidth = 0.0f;
//    float wordWidth = 0.0f;
    int wordsMeasured = 0;
    int wordIndex;
    int whiteScanFrom = run->from;

    totalWidth += (words[0].start - run->from) * (font->spaceWidth+style->wordSpacing + style->letterSpacing);
    while (1) {
	// iterate over scan buffer
	for (wordIndex = 0; wordIndex < nWords; wordIndex++) {
	    totalWidth += (words[wordIndex].start - whiteScanFrom) * (font->spaceWidth+style->wordSpacing+style->letterSpacing);
		
	    totalWidth += measureRange(run, style, words[wordIndex].start, words[wordIndex].stop, widths);

	    if (wordsMeasured > 0) {  
		// has space-char after the word
		if (widths) {
		    widths[words[wordIndex].stop] = wordPadding + font->spaceWidth + style->wordSpacing + style->letterSpacing;
		}
	    }
	    whiteScanFrom = words[wordIndex].stop;
	    ++wordsMeasured;
	}

	if (nWords > 0 && words[nWords-1].stop+1 < run->to) {
            // update scan buffer (will happen if number of spaces > wordBufSz)
	    scanRunForWords(run, words[nWords-1].stop+1, words, wordBufSz, &nWords, NULL); 	
	} else 
	    break;
    }
    totalWidth += (run->to - words[wordIndex-1].stop) * (font->spaceWidth+style->wordSpacing+style->letterSpacing);

    return totalWidth + style->padding;
}

// static
// int indexToSeparator(const Win32Char16* str, unsigned int length)
// {
//     const gunichar2* uc_chars = reinterpret_cast<const gunichar2*>(str);

//     unsigned int i;
//     for( i = 0; i < length; i++) {
// 	if (g_unichar_isspace(*uc_chars))
// 	    break;
// 	uc_chars++;
//     }
//     return i;
// }

void Win32TextRenderer::drawRun(const WebCoreTextRun *run, const WebCoreTextStyle *style, int x, int y)
{
    assert(run);
    assert(style);
    assert(style->families);
    
    if (run->length == 0) 
	return;

    Gdiplus::Color xrc;
  
    getGDIPlusColorFromGdkColor(&style->textColor, &xrc);

    x -=  gdkxoff;
    y -=  gdkyoff;

    const int wordBufSz = 10;	// size of scan buffer
    WordRange words[wordBufSz]; // scan buffer
    int nWords;			// number of words in scan buffer
    int totWords = 0;		// total word count

    // needs always totWords because wordSpacing is applied only to spaces in the middle, not
    // for the last word. Is this correct practice?
    scanRunForWords(run, run->from, words, wordBufSz, &nWords, &totWords);

    if (totWords == 0) 
	return;

    float wordPadding =  style->padding / totWords;

    float wordWidth = 0.0f;
    int wordsDrawn = 0;
    int wordIndex;

    while (1) {

	// iterate over scan buffer
	for (wordIndex = 0; wordIndex < nWords; wordIndex++) {
	    wordWidth = drawRange(run, style, words[wordIndex].start, words[wordIndex].stop, x, y, &xrc, true);

	    // not last word
	    wordWidth += wordPadding;
	    wordWidth += font->spaceWidth + style->wordSpacing;

	    x += (int)wordWidth;

	    ++wordsDrawn;
	} 

	if (nWords > 0 && words[nWords-1].stop+1 < run->to) {
            // update scan buffer (will happen if number of spaces > wordBufSz)
	    scanRunForWords(run, words[nWords-1].stop+1, words, wordBufSz, &nWords, NULL); 	
	} else 
	    break;
    }

    if (style->rtl){
	g_warning("RTL painting not implemented"); 	// FIXME:: Handle right-to-left text
    }
}

void Win32TextRenderer::drawHighlightForRun(const WebCoreTextRun *run, const WebCoreTextStyle *style, const WebCoreTextGeometry *geom)
{
  assert(run);
  assert(style);
  assert(style->families);
  
  if (run->length == 0) 
    return ;
  
  int x=0, y=0, w=0, h=0;
  
  x = (int)geom->selectionMinX;
  y = (int)geom->y;
  w = (int)floatWidthForRun(run, style, 0); 
  h = (int)geom->selectionHeight;
  
  drawRect(x,y,w,h, &style->textColor);
}

void Win32TextRenderer::drawLineForCharacters(int x, int y, float yOffset, int width, GdkColor* color)
{
}

// selection point check
int Win32TextRenderer::pointToOffset(const WebCoreTextRun *run, const WebCoreTextStyle *style, int x, bool reversed, bool includePartialGlyphs)
{
  assert(run);
  assert(style);
  assert(style->families);
  
  if (run->length == 0) 
    return 0;
    
  HDC dc = GetDC(0L);
  Gdiplus::Graphics graphics(dc);
  Gdiplus::SizeF size;
  Gdiplus::RectF extents;
  Gdiplus::PointF point(0.0f, 0.0f);
  const Gdiplus::StringFormat * format = Gdiplus::StringFormat::GenericDefault();

  UniChar* uchars = dupCharsAndRemoveLF(run->characters, run->length);

  // binary search for offset in string for pixel position x
  int start, end;
  start = run->from;
  end = run->to;
  
  int xpos;
  int len_mid;
  while (start < end) {
    len_mid = ((end - start)+1) / 2;
    graphics.MeasureString(reinterpret_cast<const WCHAR*>(uchars + start),
			   len_mid, font->win32Font, point, format, &extents);	
    extents.GetSize(&size);
    xpos = (int) size.Width;
    if (xpos < x) {
      start += len_mid;
      x -= xpos;
    } else if (xpos > x) {
      end -= len_mid;
    }else {
      start += len_mid;
      break;
    }
  }
  
  delete [] uchars;
  ReleaseDC(0L, dc);
  return start - run->from;
}


void Win32TextRenderer::drawRect(int x, int y, int w, int h, const GdkColor* color)
{
    
#if defined(USE_XFT_DRAWRECT)
    g_warning("(%d,%d,%d,%d)", x,y,w,h);
    XftColor xft_c;
    XRenderColor xrender_c;
    
    getXRenderColorFromGdkColor(color, &xrender_c);    

    XftColorAllocValue(xdisplay,
		       xvisual,
		       xcmap,
		       &xrender_c,
		       &xft_c);

    x -= gdkxoff;
    y -= gdkyoff;

    XftDrawRect(xftdraw, &xft_c, x, y, w, h);
    
    XftColorFree(xdisplay,
		 xvisual,
		 xcmap,
		 &xft_c);
#else
    gdk_gc_set_rgb_fg_color(gdkgc, const_cast<GdkColor*>(color));

    x -= gdkxoff;
    y -= gdkyoff;

    gdk_gc_set_fill (gdkgc, GDK_SOLID);
    gdk_draw_rectangle(gdkdrawable, gdkgc, TRUE, x, y, w, h);

#endif

}



