/*
 * File:	Colormap.m
 * Module:	Mandelbrot
 * Author:	R. Dunbar Poor
 * Last Edit:	Mon Sep 10 1990
 *
 * You may freely copy, distribute and reuse the code in this example.  NeXT
 * disclaims any warranty of any kind, expressed or implied, as to its fitness
 * for any particular use.
 */

#import "Colormap.h"
#import <stdlib.h>		/* for malloc, free */
#import <appkit/color.h>	/* for NXSetColor stuff */
#import <appkit/graphics.h>	/* for NXRect, etc */
#import <appkit/Cell.h>		/* for floatValue, etc */
#import <appkit/View.h>		/* for -bounds */
#import <appkit/Menu.h>		/* for itemList */
#import <appkit/Matrix.h>	/* for selectedRow */
#import <dpsclient/psops.h>	/* for PSxxx functions */
#import <appkit/Application.h>	/* for appDidInit */
#import <appkit/NXColorWell.h>	/* for appDidInit */
#import <math.h>		/* for cos, M_PI */

static float warp(float x, float xp)
/*
 * Warp takes an x value between 0 and 1, and "warps" it towards 0
 * or 1 depending on the value of xp.  When xp is close to zero, all
 * but the smallest values of x are mapped to 1.0.  When xp is close
 * to 1, all but the largest values of x are mapped to 0.0.  When xp
 * is .5, warp is an identity function.
 *
 * Looked at another way, warp is a piecewise linear approximation of
 * the function x^(xp*k).
 */ 
{
  if (x > xp) {
    /* return a point on the line between (xp,1-xp) and (1,1) */
    return (xp * (x - 2) + 1) / (1 - xp);
  } else {
    /* return a point on the line between (0,0) and (xp,1-xp) */
    return x * (1 - xp) / xp;
  }
}

static void setup_map(float *map, int npoints,
		      float freq, float phase, float curve)
/*
 * setup_map is an auxiliary routine to initialize one color in the
 * color map.  map is the pointer to the base of the color map, we
 * assume the colors are interleaved N_COLORS to 1
 */
{
  int i;
  float phase0, freq0;

  phase0 = phase*2*M_PI;
  freq0 = freq*2*M_PI/npoints;

  for (i=0; i<npoints; i++) {
    float s = (1 + cos(phase0 + i*freq0)) * .5;
    map[N_COLORS*i] = warp(s,curve);
    
  }
}

@implementation Colormap:Object

- free
{
  if (map) free(map);
  if (cMap) free(cMap);
  return [super free];
}

- setup:sender
{
  id field;

  [self setMapLength:DEFAULT_MAP_LENGTH];
  [[[colorSelect target] itemList] selectCellWithTag:RED_TAG];
  field = [forms findCellWithTag:FREQUENCY_TAG];
  [field setFloatingPointFormat:YES left:2 right:3];
  field = [forms findCellWithTag:PHASE_TAG];
  [field setFloatingPointFormat:YES left:2 right:3];
  field = [forms findCellWithTag:CURVE_TAG];
  [field setFloatingPointFormat:YES left:2 right:3];
  return self;

}

- (int)mapLength
{
  return mapLength/N_COLORS;
}

- setMapLength:(int)length
{
  if (length <= 0) return nil;
  if ((length != mapLength) || (map == (float *)nil)) {
    mapLength = length;
    if (map) free(map);
    if (cMap) free(cMap);
    map = (float *)malloc(mapLength * sizeof(float) * N_COLORS);
    cMap = (NXColor *)malloc(mapLength * sizeof(NXColor));
  }
  return self;
}

- changeColor:sender
/*
 * change one of the frequency instance variables
 */
{
  float value, freq, phase, curve;
  int color, tag;

  color = [[[colorSelect target] itemList] selectedTag];
  tag = [[sender selectedCell] tag];
  value = [[sender selectedCell] floatValue];
  
  /* extract freq, phase, curve info */
  if (tag==FREQUENCY_TAG) {
    freq = value;
  } else {
    freq = [[forms findCellWithTag:FREQUENCY_TAG] floatValue];
  }
  if (tag==PHASE_TAG) {
    phase = value;
  } else {
    phase = [[forms findCellWithTag:PHASE_TAG] floatValue];
  }
  if (tag==CURVE_TAG) {
    curve = value;
  } else {
    curve = [[forms findCellWithTag:CURVE_TAG] floatValue];
  }

  /* write new values into the colormap and update the display */
  [self setColor:color :freq :phase :curve];
  [self updateFields:self];
  [self updateMap:self];

  return self;
}

- setColor:(int)color :(float)freq :(float)phase :(float)curve
/*
 * Build the color map.  Does NOT update the sliders, fields and mapView.
 */
{
  switch(color) {
    case RED_TAG:
      redFreq = freq; redPhase = phase; redCurve = curve;
      setup_map(&map[0],mapLength,redFreq,redPhase,redCurve);
      break;
#if (N_COLORS == 3)
    case GREEN_TAG:
      greenFreq = freq; greenPhase = phase; greenCurve = curve;
      setup_map(&map[1],mapLength,greenFreq,greenPhase,greenCurve);
      break;
    case BLUE_TAG:
      blueFreq = freq; bluePhase = phase; blueCurve = curve;
      setup_map(&map[2],mapLength,blueFreq,bluePhase,blueCurve);
      break;
#endif
  }
  return self;
}

- setColors:(float)rf :(float)rp :(float)rc
	   :(float)gf :(float)gp :(float)gc
	   :(float)bf :(float)bp :(float)bc
/*
 * Set up the internal colors.  DOES update the sliders, fields, and mapView.
 */
{
  [self setColor:RED_TAG :rf :rp :rc];
  [self setColor:GREEN_TAG :gf :gp :gc];
  [self setColor:BLUE_TAG :bf :bp :bc];

  [self updateFields:self];
  [self updateMap:self];
  [self updateChaosView:self];

  return self;
}

- updateFields:sender
/*
 * update the sliders, fields from the internal instance variables
 */
{
  float freq, phase, curve;
  
  /* extract the current freq, phase, curve information */
  switch ([[[colorSelect target] itemList] selectedTag]) {
    case RED_TAG:
    default:
      freq = redFreq; phase = redPhase; curve = redCurve;
      break;
    case GREEN_TAG:
      freq = greenFreq; phase = greenPhase; curve = greenCurve;
      break;
    case BLUE_TAG:
      freq = blueFreq; phase = bluePhase; curve = blueCurve;
      break;
  }

  /* update the sliders and form cells */
  [[sliders findCellWithTag:FREQUENCY_TAG] setFloatValue:freq];
  [[sliders findCellWithTag:PHASE_TAG] setFloatValue:phase];
  [[sliders findCellWithTag:CURVE_TAG] setFloatValue:curve];

  [[forms findCellWithTag:FREQUENCY_TAG] setFloatValue:freq];
  [[forms findCellWithTag:PHASE_TAG] setFloatValue:phase];
  [[forms findCellWithTag:CURVE_TAG] setFloatValue:curve];

  return self;
}

- updateMap:sender
/*
 * update the mapView from the internal color map
 */
{
  NXRect bbox;
  int i;

  [mapView lockFocus];
  [mapView getBounds:&bbox];
  bbox.size.width /= mapLength;
  for (i=0;i<mapLength;i++) 
    {
    if (![isMono state])
        cMap[i] = NXConvertRGBToColor(map[3*i+0],map[3*i+1],map[3*i+2]);
    else  // it IS mono...  All 3 rgb components the same = gray
        cMap[i] = NXConvertRGBToColor(map[3*i+0],map[3*i+0],map[3*i+0]);
    NXSetColor(cMap[i]);
    NXRectFill(&bbox);
    bbox.origin.x += bbox.size.width;
    }
  [mapView getBounds:&bbox];
  PSsetgray(NX_BLACK);
  NXFrameRect(&bbox);
  [[mapView window] flushWindow];
  [mapView unlockFocus];

  return self;
}

- updateChaosView:sender
{
float red, green, blue;
NXConvertColorToRGB([myWell color], &red, &green, &blue);  //We don't want that alpha and Cyan, etc...
[thechaosView setColors: cMap: mapLength: NXConvertRGBToColor(red, green, blue)];
return self;
}

- getnewChaosView:newView
{
thechaosView = newView;
return self;
}
@end
