/*
 * File type conversion routines for CUPS.
 *
 * Copyright © 2020-2024 by OpenPrinting.
 * Copyright 2007-2011 by Apple Inc.
 * Copyright 1997-2007 by Easy Software Products, all rights reserved.
 *
 * Licensed under Apache License v2.0.  See the file "LICENSE" for more information.
 */

/*
 * Include necessary headers...
 */

#include <cups/string-private.h>
#include "mime.h"


/*
 * Debug macros that used to be private API...
 */

#define DEBUG_puts(x)
#define DEBUG_printf(...)


/*
 * Local types...
 */

typedef struct _mime_typelist_s		/**** List of source types ****/
{
  struct _mime_typelist_s *next;	/* Next source type */
  mime_type_t		*src;		/* Source type */
} _mime_typelist_t;


/*
 * Local functions...
 */

static int		mime_compare_filters(mime_filter_t *, mime_filter_t *);
static int		mime_compare_srcs(mime_filter_t *, mime_filter_t *);
static cups_array_t	*mime_find_filters(mime_t *mime, mime_type_t *src,
				      size_t srcsize, mime_type_t *dst,
				      int *cost, _mime_typelist_t *visited);


/*
 * 'mimeAddFilter()' - Add a filter to the current MIME database.
 */

mime_filter_t *				/* O - New filter */
mimeAddFilter(mime_t      *mime,	/* I - MIME database */
              mime_type_t *src,		/* I - Source type */
	      mime_type_t *dst,		/* I - Destination type */
              int         cost,		/* I - Relative time/resource cost */
	      const char  *filter)	/* I - Filter program to run */
{
  mime_filter_t	*temp;			/* New filter */


  DEBUG_printf(("mimeAddFilter(mime=%p, src=%p(%s/%s), dst=%p(%s/%s), cost=%d, "
                "filter=\"%s\")", mime,
		src, src ? src->super : "???", src ? src->type : "???",
		dst, dst ? dst->super : "???", dst ? dst->type : "???",
		cost, filter));

 /*
  * Range-check the input...
  */

  if (!mime || !src || !dst || !filter)
  {
    DEBUG_puts("1mimeAddFilter: Returning NULL.");
    return (NULL);
  }

 /*
  * See if we already have an existing filter for the given source and
  * destination...
  */

  if ((temp = mimeFilterLookup(mime, src, dst)) != NULL)
  {
   /*
    * Yup, does the existing filter have a higher cost?  If so, copy the
    * filter and cost to the existing filter entry and return it...
    */

    if (temp->cost > cost)
    {
      DEBUG_printf(("1mimeAddFilter: Replacing filter \"%s\", cost %d.",
                    temp->filter, temp->cost));
      temp->cost = cost;
      strlcpy(temp->filter, filter, sizeof(temp->filter));
    }
  }
  else
  {
   /*
    * Nope, add a new one...
    */

    if (!mime->filters)
      mime->filters = cupsArrayNew((cups_array_func_t)mime_compare_filters, NULL);

    if (!mime->filters)
      return (NULL);

    if ((temp = calloc(1, sizeof(mime_filter_t))) == NULL)
      return (NULL);

   /*
    * Copy the information over and sort if necessary...
    */

    temp->src  = src;
    temp->dst  = dst;
    temp->cost = cost;
    strlcpy(temp->filter, filter, sizeof(temp->filter));

    DEBUG_puts("1mimeAddFilter: Adding new filter.");
    cupsArrayAdd(mime->filters, temp);
    cupsArrayAdd(mime->srcs, temp);
  }

 /*
  * Return the new/updated filter...
  */

  DEBUG_printf(("1mimeAddFilter: Returning %p.", temp));

  return (temp);
}


/*
 * 'mimeFilter()' - Find the fastest way to convert from one type to another.
 */

cups_array_t *				/* O - Array of filters to run */
mimeFilter(mime_t      *mime,		/* I - MIME database */
           mime_type_t *src,		/* I - Source file type */
	   mime_type_t *dst,		/* I - Destination file type */
	   int         *cost)		/* O - Cost of filters */
{
  DEBUG_printf(("mimeFilter(mime=%p, src=%p(%s/%s), dst=%p(%s/%s), "
                "cost=%p(%d))", mime,
        	src, src ? src->super : "???", src ? src->type : "???",
		dst, dst ? dst->super : "???", dst ? dst->type : "???",
		cost, cost ? *cost : 0));

  return (mimeFilter2(mime, src, 0, dst, cost));
}


/*
 * 'mimeFilter2()' - Find the fastest way to convert from one type to another,
 *                   including file size.
 */

cups_array_t *				/* O - Array of filters to run */
mimeFilter2(mime_t      *mime,		/* I - MIME database */
            mime_type_t *src,		/* I - Source file type */
	    size_t      srcsize,	/* I - Size of source file */
	    mime_type_t *dst,		/* I - Destination file type */
	    int         *cost)		/* O - Cost of filters */
{
  cups_array_t	*filters;		/* Array of filters to run */


 /*
  * Range-check the input...
  */

  DEBUG_printf(("mimeFilter2(mime=%p, src=%p(%s/%s), srcsize=" CUPS_LLFMT
                ", dst=%p(%s/%s), cost=%p(%d))", mime,
        	src, src ? src->super : "???", src ? src->type : "???",
		CUPS_LLCAST srcsize,
		dst, dst ? dst->super : "???", dst ? dst->type : "???",
		cost, cost ? *cost : 0));

  if (cost)
    *cost = 0;

  if (!mime || !src || !dst)
    return (NULL);

 /*
  * (Re)build the source lookup array as needed...
  */

  if (!mime->srcs)
  {
    mime_filter_t	*current;	/* Current filter */

    mime->srcs = cupsArrayNew((cups_array_func_t)mime_compare_srcs, NULL);

    for (current = mimeFirstFilter(mime);
         current;
	 current = mimeNextFilter(mime))
      cupsArrayAdd(mime->srcs, current);
  }

 /*
  * Find the filters...
  */

  filters = mime_find_filters(mime, src, srcsize, dst, cost, NULL);

  DEBUG_printf(("1mimeFilter2: Returning %d filter(s), cost %d:",
                cupsArrayCount(filters), cost ? *cost : -1));
#ifdef DEBUG
  {
    mime_filter_t	*filter;	/* Current filter */

    for (filter = (mime_filter_t *)cupsArrayFirst(filters);
         filter;
	 filter = (mime_filter_t *)cupsArrayNext(filters))
      DEBUG_printf(("1mimeFilter2: %s/%s %s/%s %d %s", filter->src->super,
                    filter->src->type, filter->dst->super, filter->dst->type,
		    filter->cost, filter->filter));
  }
#endif /* DEBUG */

  return (filters);
}


/*
 * 'mimeFilterLookup()' - Lookup a filter.
 */

mime_filter_t *				/* O - Filter for src->dst */
mimeFilterLookup(mime_t      *mime,	/* I - MIME database */
                 mime_type_t *src,	/* I - Source type */
                 mime_type_t *dst)	/* I - Destination type */
{
  mime_filter_t	key,			/* Key record for filter search */
		*filter;		/* Matching filter */


  DEBUG_printf(("2mimeFilterLookup(mime=%p, src=%p(%s/%s), dst=%p(%s/%s))", mime,
                src, src ? src->super : "???", src ? src->type : "???",
		dst, dst ? dst->super : "???", dst ? dst->type : "???"));

  key.src = src;
  key.dst = dst;

  filter = (mime_filter_t *)cupsArrayFind(mime->filters, &key);
  DEBUG_printf(("3mimeFilterLookup: Returning %p(%s).", filter,
                filter ? filter->filter : "???"));
  return (filter);
}


/*
 * 'mime_compare_filters()' - Compare two filters.
 */

static int				/* O - Comparison result */
mime_compare_filters(mime_filter_t *f0,	/* I - First filter */
                     mime_filter_t *f1)	/* I - Second filter */
{
  int	i;				/* Result of comparison */


  if ((i = strcmp(f0->src->super, f1->src->super)) == 0)
    if ((i = strcmp(f0->src->type, f1->src->type)) == 0)
      if ((i = strcmp(f0->dst->super, f1->dst->super)) == 0)
        i = strcmp(f0->dst->type, f1->dst->type);

  return (i);
}


/*
 * 'mime_compare_srcs()' - Compare two filter source types.
 */

static int				/* O - Comparison result */
mime_compare_srcs(mime_filter_t *f0,	/* I - First filter */
                  mime_filter_t *f1)	/* I - Second filter */
{
  int	i;				/* Result of comparison */


  if ((i = strcmp(f0->src->super, f1->src->super)) == 0)
    i = strcmp(f0->src->type, f1->src->type);

  return (i);
}


/*
 * 'mime_find_filters()' - Find the filters to convert from one type to another.
 */

static cups_array_t *			/* O - Array of filters to run */
mime_find_filters(
    mime_t           *mime,		/* I - MIME database */
    mime_type_t      *src,		/* I - Source file type */
    size_t           srcsize,		/* I - Size of source file */
    mime_type_t      *dst,		/* I - Destination file type */
    int              *cost,		/* O - Cost of filters */
    _mime_typelist_t *list)		/* I - Source types we've used */
{
  int			tempcost,	/* Temporary cost */
			mincost;	/* Current minimum */
  cups_array_t		*temp,		/* Temporary filter */
			*mintemp;	/* Current minimum */
  mime_filter_t		*current,	/* Current filter */
			srckey;		/* Source type key */
  _mime_typelist_t	listnode,	/* New list node */
			*listptr;	/* Pointer in list */


  DEBUG_printf(("2mime_find_filters(mime=%p, src=%p(%s/%s), srcsize=" CUPS_LLFMT
                ", dst=%p(%s/%s), cost=%p, list=%p)", mime, src, src->super,
		src->type, CUPS_LLCAST srcsize, dst, dst->super, dst->type,
		cost, list));

 /*
  * See if there is a filter that can convert the files directly...
  */

  if ((current = mimeFilterLookup(mime, src, dst)) != NULL &&
      (current->maxsize == 0 || srcsize <= current->maxsize))
  {
   /*
    * Got a direct filter!
    */

    DEBUG_puts("3mime_find_filters: Direct filter found.");

    if ((mintemp = cupsArrayNew(NULL, NULL)) == NULL)
    {
      DEBUG_puts("3mime_find_filters: Returning NULL (out of memory).");
      return (NULL);
    }

    cupsArrayAdd(mintemp, current);

    mincost = current->cost;

    if (!cost)
    {
      DEBUG_printf(("3mime_find_filters: Returning 1 filter, cost %d:",
                    mincost));
      DEBUG_printf(("3mime_find_filters: %s/%s %s/%s %d %s",
                    current->src->super, current->src->type,
                    current->dst->super, current->dst->type,
		    current->cost, current->filter));
      return (mintemp);
    }
  }
  else
  {
   /*
    * No direct filter...
    */

    mintemp = NULL;
    mincost = 9999999;
  }

 /*
  * Initialize this node in the type list...
  */

  listnode.next = list;

 /*
  * OK, now look for filters from the source type to any other type...
  */

  srckey.src = src;

  for (current = (mime_filter_t *)cupsArrayFind(mime->srcs, &srckey);
       current && current->src == src;
       current = (mime_filter_t *)cupsArrayNext(mime->srcs))
  {
   /*
    * See if we have already tried the destination type as a source
    * type (this avoids extra filter looping...)
    */

    mime_type_t *current_dst;		/* Current destination type */

    if (current->maxsize > 0 && srcsize > current->maxsize)
      continue;

    for (listptr = list, current_dst = current->dst;
	 listptr;
	 listptr = listptr->next)
      if (current_dst == listptr->src)
	break;

    if (listptr)
      continue;

   /*
    * See if we have any filters that can convert from the destination type
    * of this filter to the final type...
    */

    listnode.src = current->src;

    cupsArraySave(mime->srcs);
    temp = mime_find_filters(mime, current->dst, srcsize, dst, &tempcost,
                             &listnode);
    cupsArrayRestore(mime->srcs);

    if (!temp)
      continue;

    if (!cost)
    {
      DEBUG_printf(("3mime_find_filters: Returning %d filter(s), cost %d:",
		    cupsArrayCount(temp), tempcost));

#ifdef DEBUG
      for (current = (mime_filter_t *)cupsArrayFirst(temp);
	   current;
	   current = (mime_filter_t *)cupsArrayNext(temp))
	DEBUG_printf(("3mime_find_filters: %s/%s %s/%s %d %s",
		      current->src->super, current->src->type,
		      current->dst->super, current->dst->type,
		      current->cost, current->filter));
#endif /* DEBUG */

      return (temp);
    }

   /*
    * Found a match; see if this one is less costly than the last (if
    * any...)
    */

    tempcost += current->cost;

    if (tempcost < mincost)
    {
      cupsArrayDelete(mintemp);

     /*
      * Hey, we got a match!  Add the current filter to the beginning of the
      * filter list...
      */

      mintemp = temp;
      mincost = tempcost;
      cupsArrayInsert(mintemp, current);
    }
    else
      cupsArrayDelete(temp);
  }

  if (mintemp)
  {
   /*
    * Hey, we got a match!
    */

    DEBUG_printf(("3mime_find_filters: Returning %d filter(s), cost %d:",
                  cupsArrayCount(mintemp), mincost));

#ifdef DEBUG
    for (current = (mime_filter_t *)cupsArrayFirst(mintemp);
         current;
	 current = (mime_filter_t *)cupsArrayNext(mintemp))
      DEBUG_printf(("3mime_find_filters: %s/%s %s/%s %d %s",
                    current->src->super, current->src->type,
                    current->dst->super, current->dst->type,
		    current->cost, current->filter));
#endif /* DEBUG */

    if (cost)
      *cost = mincost;

    return (mintemp);
  }

  DEBUG_puts("3mime_find_filters: Returning NULL (no matches).");

  return (NULL);
}
