/*
 * SMILANIM specifies the following rules for animation function values:
 *
 * (1) if values is set, it overrides everything
 * (2) for from/to/by animation at least to or by must be specified, from on its
 *     own (or nothing) is an error--which we will ignore
 * (3) if both by and to are specified only to will be used, by will be ignored
 * (4) if by is specified without from (by animation), forces additive behaviour
 * (5) if to is specified without from (to animation), special care needs to be
 *     taken when compositing animation as such animations are composited last.
 *
 * This helper method applies these rules to fill in the values list and to set
 * some internal state.
 */
nsresult
nsSMILAnimationFunction::GetValues(const nsISMILAttr& aSMILAttr,
                                   nsSMILValueArray& aResult)
{
  if (!mAnimationElement)
    return NS_ERROR_FAILURE;

  mValueNeedsReparsingEverySample = PR_FALSE;
  nsSMILValueArray result;

  // If "values" is set, use it
  if (HasAttr(nsGkAtoms::values)) {
    nsAutoString attValue;
    GetAttr(nsGkAtoms::values, attValue);
    PRBool preventCachingOfSandwich;
    nsresult rv = nsSMILParserUtils::ParseValues(attValue, mAnimationElement,
                                                 aSMILAttr, result,
                                                 preventCachingOfSandwich);
    if (NS_FAILED(rv))
      return rv;

    if (preventCachingOfSandwich) {
      mValueNeedsReparsingEverySample = PR_TRUE;
    }
  // Else try to/from/by
  } else {
    PRBool preventCachingOfSandwich = PR_FALSE;
    PRBool parseOk = PR_TRUE;
    nsSMILValue to, from, by;
    parseOk &= ParseAttr(nsGkAtoms::to,   aSMILAttr, to,
                         preventCachingOfSandwich);
    parseOk &= ParseAttr(nsGkAtoms::from, aSMILAttr, from,
                         preventCachingOfSandwich);
    parseOk &= ParseAttr(nsGkAtoms::by,   aSMILAttr, by,
                         preventCachingOfSandwich);
    
    if (preventCachingOfSandwich) {
      mValueNeedsReparsingEverySample = PR_TRUE;
    }

    if (!parseOk)
      return NS_ERROR_FAILURE;

    result.SetCapacity(2);
    if (!to.IsNull()) {
      if (!from.IsNull()) {
        result.AppendElement(from);
        result.AppendElement(to);
      } else {
        result.AppendElement(to);
      }
    } else if (!by.IsNull()) {
      nsSMILValue effectiveFrom(by.mType);
      if (!from.IsNull())
        effectiveFrom = from;
      // Set values to 'from; from + by'
      result.AppendElement(effectiveFrom);
      nsSMILValue effectiveTo(effectiveFrom);
      if (!effectiveTo.IsNull() && NS_SUCCEEDED(effectiveTo.Add(by))) {
        result.AppendElement(effectiveTo);
      } else {
        // Using by-animation with non-additive type or bad base-value
        return NS_ERROR_FAILURE;
      }
    } else {
      // No values, no to, no by -- call it a day
      return NS_ERROR_FAILURE;
    }
  }

  result.SwapElements(aResult);

  return NS_OK;
}
/*------------------------------------------------------------------------------
 * function: ParseCatPhysical
 * Read and parse a physical schema from a catalog file.
 * physicalOnly should be true if only a physical schema (not a physical
 * schema and a logical schema) is being read.
 */
static char *
ParseCatPhysical(char *catFile, char *dataFile, Boolean physicalOnly,
	TData *&tDataP)
{
	FILE *file= NULL;
	Boolean hasSource = false;
	char *source = 0; /* source of data. Which interpreter we use depends
			     on this */

	char buf[LINESIZE];
	Boolean hasFileType = false;
	Boolean hasSeparator = false;
	Boolean hasWhitespace = false;
	Boolean hasComment = false;

	Boolean isAscii = false;
	Boolean GLoad = true;
	char *fileType = 0;
	int numArgs;
	char **args;
	int recSize = 0;
	char *sep = 0;
	int numSep = 0;
	char *commentString = 0;
	Group *currgrp = NULL;

#if 0
	if (attrs != NULL) delete attrs;
#endif
	attrs = NULL;
	numAttrs = 0;

	/*
	printf("opening file %s\n", catFile);
	*/
	file = fopen(catFile, "r");
	if (file == NULL){
		fprintf(stderr,"ParseCat: can't open file %s\n", catFile);
		goto error;
	}
	_line = 0;
	while ((fgets(buf,LINESIZE, file) != NULL) && strcmp(buf, "endSchema\n"))
	{
		StripTrailingNewline(buf);

		_line++;
		/*
		printf("getting line %s\n", buf);
		*/
		if (buf[0] == '#' || buf[0] == '\n' || buf[0] == '\r')
			continue;
		Parse(buf,numArgs, args);
		if (numArgs == 0)
			continue;

#ifdef DEBUG
		printf("parse: ");
		for(int ind = 0; ind < numArgs; ind++)
		  printf("'%s' ", args[ind]);
		printf("\n");
#endif

		if (strcmp(args[0],"end")== 0)
		{
			break;
		}
		else if (strcmp(args[0],"source") == 0)
		{
			source = CopyString(args[1]);
			hasSource = true;
		}
		else if (strcmp(args[0],"separator")== 0)
		{
			/* parse separator */
			hasSeparator = ParseSeparator(numArgs, args);
			if (!hasSeparator){
				fprintf(stderr,"can't parse separator\n");
				goto error;
			}
		}
		else if (strcmp(args[0],"whitespace")== 0)
		{
			/* parse separator */
			hasWhitespace = ParseWhiteSpace(numArgs, args);
		}
		else if (strcmp(args[0],"comment") == 0)
		{
			if (numArgs != 2){
				fprintf(stderr,"can't parse comment string\n");
				goto error;
			}
			hasComment = true;
			commentString = CopyString(args[1]);
		}
		else if (strcmp(args[0],"type") == 0)
		{
			if (numArgs != 3)
			{
				fprintf(stderr,"can't parse file type need 3 args\n");
				goto error;
			}
			if (strcmp(args[2],"ascii") == 0)
			{
				isAscii = true;
			}
			else if (strcmp(args[2],"binary") == 0)
			{
				isAscii = false;
			}
			else
			{
				fprintf(stderr,"don't know file type %s, must be ascii or binary", args[2]);
				goto error;
			}
			fileType = CopyString(args[1]);
			hasFileType = true;
			if (physicalOnly)
			{
				/* Let's add the schema name to the directory now */
				/* First check if the schema is already loaded, in
				   which case we do nothing more */
				if (gdir->find_entry(StripPath(catFile)))
				{
				  GLoad = false;
				}
				else
				{
#ifdef    DEBUG
				  printf("Adding schema %s to directory\n", StripPath(catFile));
#endif
				  gdir->add_entry(StripPath(catFile));
				  GLoad = true;
				}
			}
		}
		else if (strcmp(args[0],"attr") == 0 ||
			   strcmp(args[0],"compattr") == 0 ||
			   strcmp(args[0],"sorted") == 0)
		{
			if (ParseAttr(numArgs, args, recSize, hasFileType, fileType) !=
				StatusOk) goto error;
		}
		else if (physicalOnly && !strcmp(args[0], "group"))
		{
		  if (GLoad) {
		      if (!currgrp)		/* Top level */
		      {
			currgrp = new Group(args[1], NULL, TOPGRP);
			gdir->add_topgrp(StripPath(catFile), currgrp);
		      }
		      else
			currgrp = currgrp->insert_group(args[1]);
		    }
		}
		else if (physicalOnly && !strcmp(args[0], "item"))
		{
		  if (GLoad)
		  {
		      currgrp->insert_item(args[1]);
		  }
		}
		else if (physicalOnly && !strcmp(args[0], "endgroup"))
		{
		  if (GLoad)
		  {
		      if (!currgrp)
		      {
			fprintf(stderr, "Group begins and ends not matched\n");
			goto error;
		      }
		      currgrp = currgrp->parent_group();
		    }
		}
		else
		{
	    	fprintf(stderr,"ParseCat: unknown command %s\n", args[0]);
	    	goto error;
		}
	}

	/* round record size */
	if (recSize/8*8 != recSize){
		/* round to rounding boundaries */
		recSize = (recSize/8+1)*8;
	}

	if (!hasFileType ){
		fprintf(stderr,"ParseCat: no file type specified\n");
		goto error;
	}

	if (numAttrs == 0){
		fprintf(stderr,"ParseCat: no attribute specified\n");
		goto error;
	}

	int i,j;

	if (physicalOnly)
	{
	/* If no group has been defined, create a default group */
	if (GLoad && (gdir->num_topgrp(StripPath(catFile)) == 0))
	{
	  Group *newgrp = new Group("__default", NULL, TOPGRP);
	  gdir->add_topgrp(StripPath(catFile), newgrp);
	  for (i=0; i < numAttrs; i++) {
	    AttrInfo *iInfo = attrs->Get(i);
	    if (iInfo->type != StringAttr)
	      newgrp->insert_item(iInfo->name);
	  }
	}
	}

	/* test attribute names */
	for (i=0 ; i < numAttrs-1;i++) {
		AttrInfo *iInfo = attrs->Get(i);
		if (strcmp(iInfo->name,"recId") == 0){
			fprintf(stderr,"attribute name 'recId' is reserved\n");
			goto error;
		}
		for (j=i+1; j < numAttrs; j++){
			AttrInfo *jInfo = attrs->Get(j);
			if (strcmp(iInfo->name,jInfo->name)== 0){
				fprintf(stderr,"ParseCat:duplicate attribute name %s\n",
					iInfo->name);
				goto error;
			}
		}
	}

	if (isAscii) {
	  if (hasSeparator && hasWhitespace){
	    fprintf(stderr,"can't specify both whitespace and separator\n");
	    goto error;
	  }
	  if (!(hasSeparator || hasWhitespace)){
	    fprintf(stderr,"must specify either whitespace or separator\n");
	    goto error;
	  }
	}

	if (hasSeparator) {
	  sep = new char[numSeparators];
	  for (i=0; i < numSeparators; i++){
	    sep[i] = separators[i];
	  }
	  numSep = numSeparators;
	}
	if (hasWhitespace) {
	  sep = new char[numWhitespace];
	  for (i=0; i < numWhitespace; i++){
	    sep[i] = whitespaces[i];
	    }
	  numSep = numWhitespace;
	}
	
	if (!hasComment)
	  commentString = "#";
	  
	if (hasSource)
	{
#ifndef NO_GEN_CLASS_INFO
		if (physicalOnly)
		{
			printf("source: %s\n",source);
		}
		else
		{
			printf("schema: %s\n",source);
		}
		GenClassInfo *genInfo = FindGenClass(source);
		ControlPanel::RegisterClass(
			genInfo->Gen(source, isAscii, fileType,
			attrs, recSize,sep, numSep, hasSeparator, commentString),
			true);
#else
		fprintf(stderr, "Illegal token 'source' in schema\n");
		Exit::DoExit(1);
#endif
	}
	else
	{
		// strdups because TData destructor will try to free type
		// strings -- make sure they're dynamic.
		if (isAscii) {
#ifdef    DEBUG
		  printf("default source, recSize %d\n",recSize);
#endif

#ifdef   USE_SEQ
		  tDataP = new TDataSeqAsciiInterp(catFile, strdup("UNIXFILE"),
		    dataFile, recSize, attrs, sep, numSep,
		    hasSeparator, commentString);
#else
		  tDataP = new TDataAsciiInterp(catFile, strdup("UNIXFILE"),
		    dataFile, recSize, attrs, sep, numSep,
		    hasSeparator, commentString);
#endif
		}
		else
		{
#ifdef    DEBUG
		  printf("default binary source, recSize %d\n",recSize);
#endif
		  // Note: the second use of recSize is for the physical
		  // record size.  This needs to get changed.  RKW 96/06/27.
		  tDataP = new TDataBinaryInterp(catFile, strdup("UNIXFILE"), dataFile,
			recSize, recSize/*TEMP*/, attrs);
		}
	}

	fclose(file);

	if (Init::PrintTDataAttr()) attrs->Print();
	return fileType;

error:
	if (file != NULL) fclose(file);

	if (attrs != NULL) delete attrs;
	fprintf(stderr,"error at line %d\n", _line);
	return NULL;
}