/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */


#include "xp.h"
#include "net.h"
#include "pa_parse.h"
#include "layout.h"
#include "laylayer.h"
#include "np.h"
#include "laystyle.h"
#include "layers.h"
#include "libevent.h"
#include "libmocha.h"

#define BUILTIN_DEF_DIM			50
#define BUILTIN_DEF_BORDER		0
#define BUILTIN_DEF_VERTICAL_SPACE	0
#define BUILTIN_DEF_HORIZONTAL_SPACE	0

void lo_FinishBuiltin(MWContext *, lo_DocState *, LO_BuiltinStruct *);
static void lo_FormatBuiltinInternal(MWContext *, lo_DocState *, PA_Tag *,
								   LO_BuiltinStruct *, Bool, Bool);
void lo_FillInBuiltinGeometry(lo_DocState *state, LO_BuiltinStruct *builtin,
							  Bool relayout);
void lo_UpdateStateAfterBuiltinLayout (lo_DocState *state, LO_BuiltinStruct *builtin,
									   int32 line_inc, int32 baseline_inc);

uint
LO_EnumerateBuiltins(MWContext *context, int32 layer_id)
{
    lo_TopState *top_state;
    int count, index;
    LO_BuiltinStruct *builtin;
    lo_DocLists *doc_lists;

    top_state = lo_GetMochaTopState(context);
    if (top_state == NULL)
        return 0;

    doc_lists = lo_GetDocListsById(top_state->doc_state, layer_id);
    if (!doc_lists)
        return 0;

    /* count 'em */
    count = 0;
    builtin = doc_lists->builtin_list;
    while (builtin) {
        builtin = builtin->nextBuiltin;
        count++;
    }

    /* reflect all builtins in reverse order */
    builtin = doc_lists->builtin_list;
    for (index = count-1; index >= 0; index--) {
	if (builtin->mocha_object == NULL)
        	LM_ReflectBuiltin(context, (void *) builtin, NULL, layer_id, index);
        builtin = builtin->nextBuiltin;
    }

    return count;
}

void
lo_FormatBuiltin (MWContext *context, lo_DocState *state, PA_Tag *tag)
{
	LO_BuiltinStruct *builtin;
	PA_Block buff;
	char *str;

#ifdef DEBUG_SPENCE
	printf ("lo_FormatBuiltin\n");
#endif

	/* get SRC */
	buff = lo_FetchParamValue(context, tag, PARAM_SRC);
	PA_LOCK(str, char *, buff);

#ifdef DEBUG_SPENCE
	printf ("FormatBuiltin: str = %s\n", str);
#endif

	builtin = (LO_BuiltinStruct *) lo_NewElement (context, state, LO_BUILTIN, NULL, 0);
	if (builtin == NULL) {
		return;
	}

	builtin->type = LO_BUILTIN;
	builtin->ele_id = NEXT_ELEMENT;
	builtin->x = state->x;
	builtin->x_offset = 0;
	builtin->y = state->y;
	builtin->y_offset = 0;
	builtin->width = 0;
	builtin->height = 0;
	builtin->next = NULL;
	builtin->prev = NULL;
	
	LO_NVList_Init(&builtin->attributes);
	
	builtin->attributes.n = PA_FetchAllNameValues (tag, &builtin->attributes.names,
													&builtin->attributes.values, CS_FE_ASCII);

	lo_FormatBuiltinInternal (context, state, tag, builtin, FALSE, FALSE);
}				  

void
lo_appendParams (LO_BuiltinStruct *builtin,  uint32 param_count, 
                 char **param_names, char **param_values) {
  lo_NVList* attributes = &builtin->attributes;
  int32 newcount =  attributes->n + param_count;
  int32 n = attributes->n;
  attributes->names = (char**)XP_REALLOC(attributes->names, newcount * sizeof(char*));
  attributes->values = (char**)XP_REALLOC(attributes->values, newcount * sizeof(char*));
  while (n < newcount) {
    *(attributes->names + n) = XP_STRDUP(*(param_names + n - attributes->n));
    *(attributes->values + n) = XP_STRDUP(*(param_values + n - attributes->n));
    n++;
  }
  attributes->n = newcount;
}
    

void
lo_FormatBuiltinObject (MWContext *context, lo_DocState* state,
						PA_Tag* tag , LO_BuiltinStruct *builtin, Bool streamStarted,
						uint32 param_count, char **param_names, char **param_values)
{

#ifdef DEBUG_SPENCE
	printf ("lo_FormatBuiltinObject\n");
#endif

	LO_NVList_Init(&builtin->attributes);

	builtin->attributes.n = PA_FetchAllNameValues (tag, &builtin->attributes.names,
													&builtin->attributes.values, CS_FE_ASCII);

    if (param_count > 0) lo_appendParams(builtin, param_count, param_names, param_values);
	lo_FormatBuiltinInternal (context, state, tag, builtin, TRUE, streamStarted);
}

void
lo_FillInBuiltinGeometry(lo_DocState *state,
						 LO_BuiltinStruct *builtin,
						 Bool relayout)
{
	int32 doc_width;

	if (relayout == TRUE)
	{
		builtin->ele_id = NEXT_ELEMENT;
	}
	builtin->x = state->x;
	builtin->y = state->y;

	doc_width = state->right_margin - state->left_margin;

	/*
	 * Get the width parameter, in absolute or percentage. 
	 * If percentage, make it absolute.
	 */

	if (builtin->percent_width > 0) {
		int32 val = builtin->percent_width;
		if (state->allow_percent_width == FALSE) {
			val = 0;
		}
		else {
			val = doc_width * val / 100;
		}
		builtin->width = val;
	}

	/* Set builtin->height if builtn has a % height specified */
	if (builtin->percent_height > 0) {
		int32 val = builtin->percent_height;
		if (state->allow_percent_height == FALSE) {
			val = 0;
		}
		else {
			val = state->win_height * val / 100;
		}
		builtin->height = val;
	}
}

static void
lo_FormatBuiltinInternal (MWContext *context, lo_DocState *state, PA_Tag *tag,
						  LO_BuiltinStruct *builtin, Bool isObject, Bool streamStarted)
{
	PA_Block buff;
	char *str;
	int32 val;
	int32 doc_width;
	XP_Bool widthSpecified = FALSE;
	XP_Bool heightSpecified = FALSE;

#ifdef DEBUG_SPENCE
	printf ("lo_FormatBuiltinInternal\n");
#endif

    builtin->nextBuiltin = NULL;
    builtin->mocha_object = NULL;

	builtin->FE_Data = NULL;
	builtin->session_data = NULL;
	builtin->line_height = state->line_height;
	builtin->builtin_src = NULL;
	builtin->alignment = LO_ALIGN_BASELINE;
	builtin->border_width = BUILTIN_DEF_BORDER;
	builtin->border_vert_space = BUILTIN_DEF_VERTICAL_SPACE;
	builtin->border_horiz_space = BUILTIN_DEF_HORIZONTAL_SPACE;
	builtin->tag = tag;
	builtin->ele_attrmask = 0;

	if (streamStarted)
		builtin->ele_attrmask |= LO_ELE_STREAM_STARTED;

	/* Convert any js in the values */
	lo_ConvertAllValues (context, builtin->attributes.values, builtin->attributes.n,
						 tag->newline_count);

	/* double-counting builtins? XXX */

#if 0
	/* Assign a unique index and inc the master index */
	builtin->builtin_index = state->top_state->builtin_count++;
#endif

	/*
	 * Check for an align parameter
	 */
	buff = lo_FetchParamValue(context, tag, PARAM_ALIGN);
	if (buff != NULL)
	{
		Bool floating;

		floating = FALSE;
		PA_LOCK(str, char *, buff);
		builtin->alignment = lo_EvalAlignParam(str, &floating);
		if (floating != FALSE)
		{
			builtin->ele_attrmask |= LO_ELE_FLOATING;
		}
		PA_UNLOCK(buff);
		PA_FREE(buff);
	}

	/*
	 * Get the optional src parameter.
	 */
	buff = lo_FetchParamValue(context, tag, PARAM_SRC);
	if (buff != NULL)
	{
		PA_Block new_buff;
		char *new_str;
		int32 len;

		len = 1;
		PA_LOCK(str, char *, buff);
		if (str != NULL)
		{

			len = lo_StripTextWhitespace(str, XP_STRLEN(str));
		}
		if (str != NULL)
		{
			/* bing: make sure this deals with "data:" URLs */
			new_str = NET_MakeAbsoluteURL(
				state->top_state->base_url, str);
		}
		else
		{
			new_str = NULL;
		}

		if ((new_str == NULL)||(len == 0))
		{
			new_buff = NULL;
		}
		else
		{
			char *object_src;

			new_buff = PA_ALLOC(XP_STRLEN(new_str) + 1);
			if (new_buff != NULL)
			{
				PA_LOCK(object_src, char *, new_buff);
				XP_STRCPY(object_src, new_str);
				PA_UNLOCK(new_buff);
			}
			else
			{
				state->top_state->out_of_memory = TRUE;
			}
			XP_FREE(new_str);
		}
		PA_UNLOCK(buff);
		PA_FREE(buff);
		buff = new_buff;
	}
	builtin->builtin_src = buff;

	doc_width = state->right_margin - state->left_margin;

	/*
	 * Get the width parameter, in absolute or percentage.
	 * If percentage, make it absolute.  If the height is
	 * zero, make the builtin hidden so the FE knows that the
	 * size really is zero (zero size normally means that
	 * we're blocking).
	 */
	buff = lo_FetchParamValue(context, tag, PARAM_WIDTH);
	if (buff != NULL)
	{
		Bool is_percent;

		PA_LOCK(str, char *, buff);
		val = lo_ValueOrPercent(str, &is_percent);
		if (is_percent != FALSE)
		{
			builtin->percent_width = val;
		}
		else
		{
			builtin->percent_width = 0;
			builtin->width = val;
			val = FEUNITS_X(val, context);
		}
		PA_UNLOCK(buff);
		PA_FREE(buff);
		widthSpecified = TRUE;
	}
	val = LO_GetWidthFromStyleSheet(context, state);
	if(val)
	  {
		builtin->width = val;
		builtin->percent_width = 0;
		widthSpecified = TRUE;
	  }

	/*
	 * Get the height parameter, in absolute or percentage.
	 * If percentage, make it absolute.  If the height is
	 * zero, make the builtin hidden so the FE knows that the
	 * size really is zero (zero size normally means that
	 * we're blocking).
	 */
	buff = lo_FetchParamValue(context, tag, PARAM_HEIGHT);
	if (buff != NULL)
	{
		Bool is_percent;

		PA_LOCK(str, char *, buff);
		val = lo_ValueOrPercent(str, &is_percent);
		if (is_percent != FALSE)
		{
			builtin->percent_height = val;
		}
		else
		{
			builtin->percent_height = 0;
			val = FEUNITS_Y(val, context);
		}
		builtin->height = val;
		PA_UNLOCK(buff);
		PA_FREE(buff);
		heightSpecified = TRUE;
	}

	val = LO_GetHeightFromStyleSheet(context, state);
	if(val)
	  {
		builtin->height = val;
		builtin->percent_height = 0;
		heightSpecified = TRUE;
	  }

	/*
	 * If they forgot to specify a width or height, make one up.
	 */
	if (!widthSpecified)
	{
		if (heightSpecified)
			builtin->width = builtin->height;
		else
			builtin->width = BUILTIN_DEF_DIM;
	}
	
	if (!heightSpecified)
	{
		if (widthSpecified)
			builtin->height = builtin->width;
		else
			builtin->height = BUILTIN_DEF_DIM;
	}

	lo_FillInBuiltinGeometry(state, builtin, FALSE);

	/*
	 * Get the extra vertical space parameter.
	 */
	buff = lo_FetchParamValue(context, tag, PARAM_VSPACE);
	if (buff != NULL)
	{
		PA_LOCK(str, char *, buff);
		val = XP_ATOI(str);
		if (val < 0)
		{
			val = 0;
		}
		builtin->border_vert_space = val;
		PA_UNLOCK(buff);
		PA_FREE(buff);
	}
	builtin->border_vert_space = FEUNITS_Y(builtin->border_vert_space, context);

	/*
	 * Get the extra horizontal space parameter.
	 */
	buff = lo_FetchParamValue(context, tag, PARAM_HSPACE);
	if (buff != NULL)
	{
		PA_LOCK(str, char *, buff);
		val = XP_ATOI(str);
		if (val < 0)
		{
			val = 0;
		}
		builtin->border_horiz_space = val;
		PA_UNLOCK(buff);
		PA_FREE(buff);
	}
	builtin->border_horiz_space = FEUNITS_X(builtin->border_horiz_space,
						context);

	/* Create a layer for this builtin */
	builtin->layer =
	  lo_CreateEmbeddedObjectLayer(context, state, (LO_Element*)builtin);


	/* builtin_list for reflection? XXX */

	lo_FinishBuiltin (context, state, builtin);
}

void
lo_FinishBuiltin (MWContext *context, lo_DocState *state, LO_BuiltinStruct *builtin)
{
	int32 baseline_inc;
	int32 line_inc;

#ifdef DEBUG_SPENCE
	printf ("lo_FinishBuiltin\n");
#endif

	/*
	 * Figure out how to align this builtin.
	 * baseline_inc is how much to increase the baseline
	 * of previous element of this line.  line_inc is how
	 * much to increase the line height below the baseline.
	 */
	baseline_inc = 0;
	line_inc = 0;
	/*
	 * If we are at the beginning of a line, with no baseline,
	 * we first set baseline and line_height based on the current
	 * font, then place the builtin.
	 */
	if (state->baseline == 0) {
		state->baseline = 0;
	}

	builtin->x_offset += (int16)builtin->border_horiz_space;
	builtin->y_offset += (int32)builtin->border_vert_space;

	lo_LayoutInflowBuiltin(context, state, builtin, FALSE, &line_inc, &baseline_inc);

	lo_AppendToLineList(context, state,
						(LO_Element *)builtin, baseline_inc);

	lo_UpdateStateAfterBuiltinLayout(state, builtin, line_inc, baseline_inc);
}

void
lo_UpdateStateAfterBuiltinLayout (lo_DocState *state, LO_BuiltinStruct *builtin,
								  int32 line_inc, int32 baseline_inc)
{
	int32 builtin_width;
	int32 x, y;
	lo_DocLists *doc_lists;

#ifdef DEBUG_SPENCE
	printf ("lo_UpdateStateAfterBuiltinLayout\n");
#endif

	builtin_width = builtin->width + (2 * builtin->border_width) +
		(2 * builtin->border_horiz_space);

	state->baseline += (intn) baseline_inc;
	state->line_height += (intn) (baseline_inc + line_inc);

	/*
	 * Clean up state
	 */
	state->x = state->x + builtin->x_offset +
		builtin_width - builtin->border_horiz_space;
	state->linefeed_state = 0;
	state->at_begin_line = FALSE;
	state->trailing_space = FALSE;
	state->cur_ele_type = LO_BUILTIN;

	/* Determine the new position of the layer. */
	x = builtin->x + builtin->x_offset + builtin->border_width;
	y = builtin->y + builtin->y_offset + builtin->border_width;

    /* Move layer to new position */
    if (builtin->layer != NULL)
      CL_MoveLayer(builtin->layer, x, y);

    doc_lists = lo_GetCurrentDocLists(state);
    builtin->nextBuiltin = (LO_BuiltinStruct *)doc_lists->builtin_list;
    doc_lists->builtin_list = builtin;
    doc_lists->builtin_list_count++;
}


void
lo_LayoutInflowBuiltin(MWContext *context,
					 lo_DocState *state,
					 LO_BuiltinStruct *builtin,
					 Bool inRelayout,
					 int32 *line_inc,
					 int32 *baseline_inc)
{
  int32 builtin_width, builtin_height;
  Bool line_break;
  PA_Block buff;
  char *str;
  LO_TextStruct tmp_text;
  LO_TextInfo text_info;
  lo_TopState *top_state;
  
  top_state = state->top_state;
  
  /*
   * All this work is to get the text_info filled in for the current
   * font in the font stack. Yuck, there must be a better way.
   */
  memset (&tmp_text, 0, sizeof (tmp_text));
  buff = PA_ALLOC(1);
  if (buff == NULL)
	{
	  top_state->out_of_memory = TRUE;
	  return;
	}
  PA_LOCK(str, char *, buff);
  str[0] = ' ';
  PA_UNLOCK(buff);
  tmp_text.text = buff;
  tmp_text.text_len = 1;
  /* TEXTATTR HERE */
  tmp_text.text_attr =
	state->font_stack->text_attr;
  FE_GetTextInfo(context, &tmp_text, &text_info);
  PA_FREE(buff);
  
  builtin_width = builtin->width + (2 * builtin->border_width) +
	(2 * builtin->border_horiz_space);
  builtin_height = builtin->height + (2 * builtin->border_width) +
	(2 * builtin->border_vert_space);
  
  /*
   * Will this builtin make the line too wide.
   */
  if ((state->x + builtin_width) > state->right_margin)
	{
	  line_break = TRUE;
	}
  else
	{
	  line_break = FALSE;
	}
  
  /*
   * if we are at the beginning of the line.  There is
   * no point in breaking, we are just too wide.
   * Also don't break in unwrapped preformatted text.
   * Also can't break inside a NOBR section.
   */
  if ((state->at_begin_line != FALSE)||
	  (state->preformatted == PRE_TEXT_YES)||
	  (state->breakable == FALSE))
	{
	  line_break = FALSE;
	}
  
  /*
   * break on the builtin if we have
   * a break.
   */
  if (line_break != FALSE)
	{
	  /*
	   * We need to make the elements sequential, linefeed
	   * before builtin.
	   */
		top_state->element_id = builtin->ele_id;	  

		if (!inRelayout)
		{
			lo_SoftLineBreak(context, state, TRUE);
		}
		else 
		{
			lo_rl_AddSoftBreakAndFlushLine(context, state);
		}
		builtin->x = state->x;
		builtin->y = state->y;
		builtin->ele_id = NEXT_ELEMENT;
	}

  lo_CalcAlignOffsets(state, &text_info, (intn)builtin->alignment,
					  builtin_width, builtin_height,
					  &builtin->x_offset, &builtin->y_offset, line_inc, baseline_inc);
}


/* Utility function for hunting LO_Builtin_struct for stuff. */
char*
LO_GetBuiltInAttribute (LO_BuiltinStruct *pBuiltin_struct, char* att)
{
  int n = 0;
  if (!pBuiltin_struct || !att)
    return NULL;
  while (n < pBuiltin_struct->attributes.n) {
    char* attName = *(pBuiltin_struct->attributes.names + n);
    char* attValue = *(pBuiltin_struct->attributes.values + n);
    if (attName && (XP_FILENAMECMP(attName, att) == 0)) {
      return attValue; 
    }
    n++;
  }
  return NULL;
}

