/*
    FLAM3 - cosmic recursive fractal flames
    Copyright (C) 1992-2009 Spotworks LLC
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see .
*/
#include "private.h"
#include "palettes.h"
lib_palette *the_palettes = NULL;
int npalettes;
static void parse_palettes(xmlNode *node) {
   xmlAttrPtr attr;
   char *val;
   lib_palette *pal;
   int hex_error;
    
   while (node) {
      if (node->type == XML_ELEMENT_NODE && !xmlStrcmp(node->name, (const xmlChar *)"palette")) {
         attr = node->properties;
	      pal = &the_palettes[npalettes];
	      memset(pal, 0, sizeof(lib_palette));
         while (attr) {
            val = (char *) xmlGetProp(node, attr->name);
            if (!xmlStrcmp(attr->name, (const xmlChar *)"data")) {
               int count = 256;
               int c_idx = 0;
               int r,g,b;
               int col_count = 0;
               int sscanf_ret;
               
               c_idx=0;
               col_count = 0;
               hex_error = 0;
               
               do {
                  sscanf_ret = sscanf((char *)&(val[c_idx]),"00%2x%2x%2x",&r,&g,&b);
                  if (sscanf_ret != 3) {
                     fprintf(stderr,"error:  problem reading hexadecimal color data '%8s'\n",&val[c_idx]);
                     hex_error = 1;
                     break;
                  }
			         c_idx += 8;
                  while (isspace( (int)val[c_idx]))
                     c_idx++;
                  pal->colors[col_count][0] = (double)r/255.0;
                  pal->colors[col_count][1] = (double)g/255.0;
                  pal->colors[col_count][2] = (double)b/255.0;
                  
                  col_count++;
               } while (col_countname, (const xmlChar *)"number")) {
               pal->number = atoi(val);
            } else if (!xmlStrcmp(attr->name, (const xmlChar *)"name")) {
               strncpy(pal->name, val, flam3_name_len);
               pal->name[flam3_name_len-1] = 0;
            }
            
            xmlFree(val);
            attr = attr->next;
         }
         
         if (hex_error == 0) {
            npalettes++;
            the_palettes = realloc(the_palettes, (1 + npalettes) * sizeof(lib_palette));
         }
      } else
         parse_palettes(node->children);
         
	   node = node->next;
   }
}
static int init_palettes(char *filename) {
    FILE *fp;
    xmlDocPtr doc;
    xmlNode *rootnode;
    int i, c, slen = 5000;
    char *s;
    fp = fopen(filename, "rb");
    if (NULL == fp) {
        fprintf(stderr, "flam3: could not open palette file ");
        perror(filename);
        return(-1);
    }
   
   /* Incrementally read XML file into a string */
   s = malloc(slen);
   i = 0;
   do {
      c = getc(fp);
      if (EOF == c) {
         if (ferror(fp)) {
            perror(filename);
            return(-1);
         }
         break;
      }
      s[i++] = c;
      if (i == slen-1) {
         slen *= 2;
         s = realloc(s, slen);
      }
   } while (1);
   fclose(fp);
   s[i] = 0;
   
   doc = xmlReadMemory(s, (int)strlen(s), filename, NULL, XML_PARSE_NONET);
   if (NULL == doc) {
       fprintf(stderr, "error parsing %s (%s).\n", filename, s);
       return(-1);
   }
   rootnode = xmlDocGetRootElement(doc);
   the_palettes = malloc(sizeof(lib_palette));
   npalettes = 0;
   parse_palettes(rootnode);
   xmlFreeDoc(doc);
   
   free(s);
   xmlCleanupParser();
   return(1);
}
int flam3_get_palette(int n, flam3_palette c, double hue_rotation, randctx * const rc) {
   int cmap_len = 256;
   int idx, i, j, rcode;
   // set palette to all white in case there are problems
   for (i = 0; i < cmap_len; i++) {
   	c[i].index = i;
      for (j = 0; j < 4; j++)
         c[i].color[j] = 1.0;
   }
   if (NULL == the_palettes) {   
      rcode = init_palettes("flam3-palettes.xml");
      if (rcode<0) {
         fprintf(stderr,"error reading xml palette file, setting to all white\n");
         return(-1);
      }
   }
   
   if (flam3_palette_random == n)
      n = the_palettes[rand_mod(rc, npalettes)].number;
   for (idx = 0; idx < npalettes; idx++) {
      
      if (n == the_palettes[idx].number) {
      	/* Loop over elements of cmap */
	      for (i = 0; i < cmap_len; i++) {
            int ii = (i * 256) / cmap_len;
            double4 rgb, hsv;
            
             rgb = (double4) {
			       the_palettes[idx].colors[ii][0],
				   the_palettes[idx].colors[ii][1],
				   the_palettes[idx].colors[ii][2],
				   1.0,
				   };
	       
	         hsv = rgb2hsv(rgb);
	         hsv[0] += hue_rotation * 6.0;
	         rgb = hsv2rgb(hsv);
	         
	         c[i].index = i;
	       
		     c[i].color = rgb;
		         
		     c[i].color[3] = 1.0;
         }
	   
	      return n;
      }
   }
   
   fprintf(stderr, "warning: palette number %d not found, using white.\n", n);
   return(-1);
}
/* rgb 0 - 1,
   h 0 - 6, s 0 - 1, v 0 - 1 */
double4 rgb2hsv(double4 rgb)
 {
  double rd, gd, bd, h, s, v, max, min, del, rc, gc, bc;
  rd = rgb[0];
  gd = rgb[1];
  bd = rgb[2];
  /* compute maximum of rd,gd,bd */
  if (rd>=gd) { if (rd>=bd) max = rd;  else max = bd; }
         else { if (gd>=bd) max = gd;  else max = bd; }
  /* compute minimum of rd,gd,bd */
  if (rd<=gd) { if (rd<=bd) min = rd;  else min = bd; }
         else { if (gd<=bd) min = gd;  else min = bd; }
  del = max - min;
  v = max;
  if (max != 0.0) s = (del) / max;
             else s = 0.0;
  h = 0;
  if (s != 0.0) {
    rc = (max - rd) / del;
    gc = (max - gd) / del;
    bc = (max - bd) / del;
    if      (rd==max) h = bc - gc;
    else if (gd==max) h = 2 + rc - bc;
    else if (bd==max) h = 4 + gc - rc;
    if (h<0) h += 6;
  }
  return (double4) { h, s, v, rgb[3] };
}
/* h 0 - 6, s 0 - 1, v 0 - 1
   rgb 0 - 1 */
double4 hsv2rgb(double4 hsv)
{
   double h = hsv[0], s = hsv[1], v = hsv[2];
  int    j;
  double rd, gd, bd;
  double f, p, q, t;
   while (h >= 6.0) h = h - 6.0;
   while (h <  0.0) h = h + 6.0;
   j = (int) floor(h);
   f = h - j;
   p = v * (1-s);
   q = v * (1 - (s*f));
   t = v * (1 - (s*(1 - f)));
   
   switch (j) {
    case 0:  rd = v;  gd = t;  bd = p;  break;
    case 1:  rd = q;  gd = v;  bd = p;  break;
    case 2:  rd = p;  gd = v;  bd = t;  break;
    case 3:  rd = p;  gd = q;  bd = v;  break;
    case 4:  rd = t;  gd = p;  bd = v;  break;
    case 5:  rd = v;  gd = p;  bd = q;  break;
    default: rd = v;  gd = t;  bd = p;  break;
   }
   return (double4) { rd, gd, bd, hsv[3] };
}
double flam3_calc_alpha(double density, double gamma, double linrange) {
   double dnorm = density;
   double funcval = pow(linrange, gamma);
   double frac,alpha;
   
   if (dnorm>0) {
      if (dnorm < linrange) {
         frac = dnorm/linrange;
         alpha = (1.0-frac) * dnorm * (funcval / linrange) + frac * pow(dnorm,gamma);
      } else
         alpha = pow(dnorm,gamma);
   } else
      alpha = 0;
      
   return(alpha);
}
          
double4 flam3_calc_newrgb(double4 cbuf, double ls, double highpow) {
   int rgbi;
   double newls,lsratio;
   double a, maxa=-1.0, maxc=0;
   double adjhlp;
   
   if (ls==0.0 || (cbuf[0]==0.0 && cbuf[1]==0.0 && cbuf[2]==0.0)) {
      return (double4) { 0, 0, 0, 0 };
   }
   
   /* Identify the most saturated channel */
   for (rgbi=0;rgbi<3;rgbi++) {
      a = ls * (cbuf[rgbi]);
      if (a>maxa) {
         maxa = a;
         maxc = cbuf[rgbi];
      }
   }
      
   /* If a channel is saturated and we have a non-negative highlight power */
   /* modify the color to prevent hue shift                                */
   if (maxa > 1.0 && highpow>=0.0) {
      newls = 1.0/maxc;
      lsratio = pow(newls/ls,highpow);
      /* Calculate the max-value color (ranged 0 - 1) */
	  double4 newrgb = newls*(cbuf);
      /* Reduce saturation by the lsratio */
      double4 newhsv = rgb2hsv(newrgb);
      newhsv[1] *= lsratio;
      return hsv2rgb(newhsv);
   } else {
      newls = 1.0/maxc;
      adjhlp = -highpow;
      if (adjhlp>1)
         adjhlp=1;
      if (maxa<=1.0)
         adjhlp=1.0;
	  /* Calculate the max-value color (ranged 0 - 1) interpolated with the old
	   * behaviour */
		
      return ((1.0-adjhlp)*newls + adjhlp*ls)*(cbuf);
   }
}
static int random_xform(flam3_genome *g, int excluded, randctx * const rc) {
   int ntries = 0;
   while (ntries++ < 100) {
      int i = rand_mod(rc, g->num_xforms);
      if (g->xform[i].density > 0.0 && i != excluded)
         return i;
   }
   return -1;
}
static double try_colors(flam3_genome *g, int color_resolution) {
    int *hist;
    int i, hits, res = color_resolution;
    int res3 = res * res * res;
    flam3_frame f;
    unsigned char *image, *p;
    flam3_genome saved;
    double scalar;
    int pixtotal;
    stat_struct stats;
    memset(&saved, 0, sizeof(flam3_genome));
    flam3_copy(&saved, g);
    g->sample_density = 1;
    g->spatial_oversample = 1;
    
    /* Scale the image so that the total number of pixels is ~10000 */
    pixtotal = g->width * g->height;    
    scalar = sqrt( 10000.0 / (double)pixtotal);
    g->width *= scalar;
    g->height *= scalar;
    g->pixels_per_unit *= scalar;      
    
//    g->width = 100; // XXX keep aspect ratio
//    g->height = 100;
//    g->pixels_per_unit = 50;
    g->nbatches = 1;
    g->ntemporal_samples = 1;
//    f.temporal_filter_radius = 0.0;
    f.bytes_per_channel=1;
    f.verbose = 0;
    f.genomes = g;
    f.ngenomes = 1;
    f.earlyclip = 1;
    f.pixel_aspect_ratio = 1.0;
    f.progress = 0;
    f.nthreads = 1;
    f.sub_batch_size = 10000;
        
    image = (unsigned char *) calloc(g->width * g->height, 3);
    if (flam3_render(&f, image, flam3_field_both, &stats)) {
       fprintf(stderr,"Error rendering test image for trycolors.  Aborting.\n");
       return(-1);
    }
    hist = calloc(sizeof(int), res3);
    p = image;
    for (i = 0; i < g->height * g->width; i++) {
       hist[(p[0] * res / 256) +
            (p[1] * res / 256) * res +
            (p[2] * res / 256) * res * res]++;
       p += 3;
    }
    if (0) {
       int j, k;
       for (i = 0; i < res; i++) {
          fprintf(stderr, "\ni=%d: \n", i);
          for (j = 0; j < res; j++) {
             for (k = 0; k < res; k++) {
                fprintf(stderr, " %5d", hist[i * res * res + j * res + k]);
             }
             fprintf(stderr, "\n");
          }
       }
    }
    hits = 0;
    for (i = 0; i < res3; i++) {
       if (hist[i]) hits++;
    }
    free(hist);
    free(image);
    g->sample_density = saved.sample_density;
    g->width = saved.width;
    g->height = saved.height;
    g->spatial_oversample = saved.spatial_oversample;
    g->pixels_per_unit = saved.pixels_per_unit;
    g->nbatches = saved.nbatches;
    g->ntemporal_samples = saved.ntemporal_samples;
    /* Free xform storage */
    clear_cp(&saved,flam3_defaults_on);
    return (double) (hits / res3);
}
static void change_colors(flam3_genome *g, int change_palette, randctx * const rc) {
   int i;
   int x0, x1;
   if (change_palette) {
      g->hue_rotation = 0.0;
      g->palette_index = flam3_get_palette(flam3_palette_random, g->palette, 0.0, rc);
      if (g->palette_index < 0)
         fprintf(stderr,"error retrieving random palette, setting to all white\n");
   }
   for (i = 0; i < g->num_xforms; i++) {
      g->xform[i].color = rand_d01(rc);
   }
   x0 = random_xform(g, -1, rc);
   x1 = random_xform(g, x0, rc);
   if (x0 >= 0 && rand_bool(rc)) g->xform[x0].color = 0.0;
   if (x1 >= 0 && rand_bool(rc)) g->xform[x1].color = 1.0;
}
void flam3_improve_colors(flam3_genome *g, int ntries, int change_palette, int color_resolution, randctx * const rc) {
   int i;
   double best, b;
   flam3_genome best_genome;
   memset(&best_genome, 0, sizeof(flam3_genome));
   best = try_colors(g, color_resolution);
   if (best<0) {
      fprintf(stderr,"error in try_colors, skipping flam3_improve_colors\n");
      return;
   }
   flam3_copy(&best_genome,g);
   for (i = 0; i < ntries; i++) {
      change_colors(g, change_palette, rc);
      b = try_colors(g, color_resolution);
      if (b < 0) {
         fprintf(stderr,"error in try_colors, aborting tries\n");
         break;
      }      
      if (b > best) {
         best = b;
         flam3_copy(&best_genome,g);
      }
   }
   flam3_copy(g,&best_genome);
   clear_cp(&best_genome,flam3_defaults_on);
}