/* Copyright (C) 1992, 1993 Aladdin Enterprises.  All rights reserved.
  
  This file is part of Aladdin Ghostscript.
  
  Aladdin Ghostscript is distributed with NO WARRANTY OF ANY KIND.  No author
  or distributor accepts any responsibility for the consequences of using it,
  or for whether it serves any particular purpose or works at all, unless he
  or she says so in writing.  Refer to the Aladdin Ghostscript Free Public
  License (the "License") for full details.
  
  Every copy of Aladdin Ghostscript must include a copy of the License,
  normally in a plain ASCII text file named PUBLIC.  The License grants you
  the right to copy, modify and redistribute Aladdin Ghostscript, but only
  under certain conditions described in the License.  Among other things, the
  License requires that the copyright notice and this notice be preserved on
  all copies.
*/

/* zcspace2.c */
/* Level 2 color space operators */
#include "string_.h"			/* for strlen */
#include "ghost.h"
#include "errors.h"
#include "oper.h"
#include "gscolor.h"
#include "gscspace.h"
#include "gscolor2.h"
#include "dstack.h"		/* for systemdict */
#include "estack.h"
#include "idict.h"
#include "idparam.h"
#include "igstate.h"
#include "iname.h"		/* for name_eq */
#include "store.h"

/* Imported from gscolor2.c */
extern const gs_color_space_type
	gs_color_space_type_Separation,
	gs_color_space_type_Pattern;

/* Imported from zcie.c */
extern int
	zcolorspace_CIEBasedABC(P3(const ref *, gs_color_space *, ref_cie_procs *)),
	zcolorspace_CIEBasedA(P3(const ref *, gs_color_space *, ref_cie_procs *));
/* Imported from zcsindex.c */
extern int zcolorspace_Indexed(P3(const ref *, gs_color_space *, ref *));
extern int zcolorspace_Separation(P3(const ref *, gs_color_space *,
                                     ref_separation_params *));

/* Forward references */
typedef enum { cs_allow_base, cs_allow_paint, cs_allow_all } cs_allowed;
private int cspace_param(P4(const ref *, gs_color_space *, ref_color_procs *, cs_allowed));

/* Initialization */
private void
zcspace2_init(void)
{	/* Create the names of the color spaces. */
	ref csna;
	typedef struct { const char _ds *s; int i; } csni;
	static const csni nia[] = {
		{"DeviceGray", (int)gs_color_space_index_DeviceGray},
		{"DeviceRGB", (int)gs_color_space_index_DeviceRGB},
		{"DeviceCMYK", (int)gs_color_space_index_DeviceCMYK},
		{"CIEBasedABC", (int)gs_color_space_index_CIEBasedABC},
		{"CIEBasedA", (int)gs_color_space_index_CIEBasedA},
		{"Separation", (int)gs_color_space_index_Separation},
		{"Indexed", (int)gs_color_space_index_Indexed},
		{"Pattern", (int)gs_color_space_index_Pattern}
	};
	int i;
	ialloc_ref_array(&csna, a_readonly, countof(nia), "ColorSpaceNames");
	for ( i = 0; i < countof(nia); i++ )
	{	const char _ds *str = nia[i].s;
		name_ref((const byte *)str, strlen(str), csna.value.refs + nia[i].i, 0);
	}
	dict_put_string(systemdict, "ColorSpaceNames", &csna);
}


/* - currentcolorspace <cspace> */
int
zcurrentcolorspace(register os_ptr op)
{	push(1);
	if ( r_has_type(&istate->colorspace.array, t_null) )
	{	/* Create the 1-element array on the fly. */
		const gs_color_space *pcs = gs_currentcolorspace(igs);
		ref *pcsna;
		dict_find_string(systemdict, "ColorSpaceNames", &pcsna);
		make_tasv(op, t_array, a_readonly, 1, refs,
			  pcsna->value.refs + (int)pcs->type->index);
	}
	else
		*op = istate->colorspace.array;
	return 0;
}

/* <cspace> setcolorspace - */
int
zsetcolorspace(register os_ptr op)
{	gs_color_space cs;
	ref_color_procs procs;
	ref_colorspace cspace_old;
	es_ptr ep = esp;
	int code;
	procs = istate->colorspace.procs;
	code = cspace_param((const ref *)op, &cs, &procs, cs_allow_all);
	if ( code < 0 )
	{	esp = ep;
		return code;
	}
	/* The color space installation procedure may refer to */
	/* istate->colorspace.procs. */
	cspace_old = istate->colorspace;
	if ( r_has_type(op, t_name) )
		make_null(&istate->colorspace.array);	/* no params */
	else
		istate->colorspace.array = *op;
	istate->colorspace.procs = procs;
	code = gs_setcolorspace(igs, &cs);
	if ( code < 0 )
	{	istate->colorspace = cspace_old;
		esp = ep;
		return code;
	}
	pop(1);
	return (esp == ep ? 0 : o_push_estack);
}

/* ------ Initialization procedure ------ */

op_def zcspace2_l2_op_defs[] = {
		op_def_begin_level2(),
	{"0currentcolorspace", zcurrentcolorspace},
	{"1setcolorspace", zsetcolorspace},
	op_def_end(zcspace2_init)
};

/* ------ Internal procedures ------ */

/* Extract the parameters for a color space. */
private int
cspace_param(const ref *pcsref, gs_color_space *pcs,
  ref_color_procs *pcprocs, cs_allowed allow)
{	const ref *pcsa;
	uint asize;
	ref *pcsna;
	int csi;
	int code;
	if ( r_has_type(pcsref, t_array) )
	{	check_read(*pcsref);
		pcsa = pcsref->value.const_refs;
		asize = r_size(pcsref);
		if ( asize == 0 )
			return_error(e_rangecheck);
	}
	else
	{	pcsa = pcsref;
		asize = 1;
	}
	check_type(*pcsa, t_name);
	dict_find_string(systemdict, "ColorSpaceNames", &pcsna);
	for ( csi = 0; !name_eq(pcsa, pcsna->value.refs + csi); )
	{	if ( ++csi == r_size(pcsna) )
			return_error(e_rangecheck);
	}
	pcsa++;
	asize--;
	/* Note: to avoid having to undo allocations, we should make all */
	/* checks before any recursive calls of cspace_params. */
	/* Unfortunately, we can't do this in the case of Indexed spaces. */
	switch ( (gs_color_space_index)csi )
	{
	case gs_color_space_index_DeviceGray:
		if ( asize != 0 )
			return_error(e_rangecheck);
		pcs->type = &gs_color_space_type_DeviceGray;
		break;
	case gs_color_space_index_DeviceRGB:
		if ( asize != 0 )
			return_error(e_rangecheck);
		pcs->type = &gs_color_space_type_DeviceRGB;
		break;
	case gs_color_space_index_DeviceCMYK:
		if ( asize != 0 )
			return_error(e_rangecheck);
		pcs->type = &gs_color_space_type_DeviceCMYK;
		break;
	case gs_color_space_index_CIEBasedABC:
		if ( asize != 1 )
			return_error(e_rangecheck);
		code = zcolorspace_CIEBasedABC(pcsa, pcs, &pcprocs->cie);
		if ( code < 0 )
			return code;
		/*pcs->type = &gs_color_space_type_CIEBasedABC;*/	/* set by zcolorspace... */
		break;
	case gs_color_space_index_CIEBasedA:
		if ( asize != 1 )
			return_error(e_rangecheck);
		code = zcolorspace_CIEBasedA(pcsa, pcs, &pcprocs->cie);
		if ( code < 0 )
			return code;
		/*pcs->type = &gs_color_space_type_CIEBasedA;*/	/* set by zcolorspace... */
		break;
	case gs_color_space_index_Separation:
		if ( allow == cs_allow_base )
			return_error(e_rangecheck);
		if ( asize != 3 )
			return_error(e_rangecheck);
		switch ( r_type(pcsa) )
		{
		default:
			return_error(e_typecheck);
		case t_string:
		case t_name:
			;
		}
		check_proc(pcsa[2]);
		code = cspace_param(pcsa + 1, (gs_color_space *)&pcs->params.separation.alt_space, pcprocs, cs_allow_base);
		if ( code < 0 )
			return code;
		code = zcolorspace_Separation(pcsa, pcs, &pcprocs->special.separation);
		if ( code < 0 )
			return code;
		/*pcs->type = &gs_color_space_type_Separation;*/	/* set by zcolorspace... */
		break;
	case gs_color_space_index_Indexed:
		if ( allow == cs_allow_base )
			return_error(e_rangecheck);
		if ( asize != 3 )
			return_error(e_rangecheck);
		check_type(pcsa[1], t_integer);
		if ( pcsa[1].value.intval < 0 || pcsa[1].value.intval > 4095 )
			return_error(e_rangecheck);
		/* We must get the base space now, rather than later, */
		/* so we can check the length of the table against */
		/* the num_components of the base space. */
		code = cspace_param(pcsa, (gs_color_space *)&pcs->params.indexed.base_space, pcprocs, cs_allow_base);
		if ( code < 0 )
			return code;
		code = zcolorspace_Indexed(pcsa, pcs, &pcprocs->special.index_proc);
		if ( code < 0 )
			return code;
		/*pcs->type = &gs_color_space_type_Indexed;*/	/* set by zcolorspace... */
		break;
	case gs_color_space_index_Pattern:
		if ( allow != cs_allow_all )
			return_error(e_rangecheck);
		switch ( asize )
		{
		case 0:		/* no base space */
			pcs->params.pattern.has_base_space = false;
			break;
		default:
			return_error(e_rangecheck);
		case 1:
			pcs->params.pattern.has_base_space = true;
			code = cspace_param(pcsa,
			  (gs_color_space *)&pcs->params.pattern.base_space,
			  pcprocs, cs_allow_paint);
			if ( code < 0 )
				return code;
		}
		pcs->type = &gs_color_space_type_Pattern;
		break;
	default:
		return_error(e_typecheck);
	}
	return 0;
}
