/*  dvdisaster: Additional error correction for optical media.
 *  Copyright (C) 2004-2015 Carsten Gnoerlich.
 *
 *  Email: carsten@dvdisaster.org  -or-  cgnoerlich@fsfe.org
 *  Project homepage: http://www.dvdisaster.org
 *
 *  This file is part of dvdisaster.
 *
 *  dvdisaster 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.
 *
 *  dvdisaster 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 dvdisaster. If not, see <http://www.gnu.org/licenses/>.
 */

#include "dvdisaster.h"

/***
 *** Constructors and destructors
 ***/

/*
 * Initialize the curve
 */

Curve* CreateCurve(GtkWidget *widget, char *left_label, char *left_format, int n_values, int bottom_format)
{  Curve *curve = g_malloc0(sizeof(Curve));

   curve->widget     = widget;
   curve->layout     = gtk_widget_create_pango_layout(widget, NULL);
   curve->leftLabel  = g_strdup(left_label); 
   curve->leftFormat = g_strdup(left_format);
   curve->bottomFormat = bottom_format;   

   curve->fvalue       = g_malloc0(sizeof(gdouble)*(n_values+1));
   curve->ivalue       = g_malloc0(sizeof(gint)*(n_values+1));
   curve->lvalue       = g_malloc0(sizeof(gint)*(n_values+1));
   curve->lastValueIdx = n_values;

   curve->maxX      = 1;
   curve->maxY      = 1;
   curve->logMaxY   = 1;

   if(bottom_format != CURVE_PERCENT)
     curve->margin = 2;

   return curve;
} 

/*
 * Get rid of it
 */

void FreeCurve(Curve *curve)
{
   g_object_unref(curve->layout);
   g_free(curve->leftLabel);
   if(curve->leftLogLabel)
      g_free(curve->leftLogLabel);
   g_free(curve->leftFormat);
   g_free(curve->fvalue);
   g_free(curve->ivalue);
   g_free(curve->lvalue);
   g_free(curve);
}

/*
 * Reset the values
 */

void ZeroCurve(Curve *curve)
{  int i;

   if(curve)
     for(i=0; i<=curve->lastValueIdx; i++)
     {  curve->fvalue[i] = -1.0;
        curve->ivalue[i] = 0;
        curve->lvalue[i] = 0;
     }
}

/***
 *** Auxiliary functions
 ***/

/*
 * Calculate pixel coords from curve values
 */

int CurveX(Curve *curve, gdouble x)
{  gdouble width = (curve->rightX - curve->leftX - curve->margin);

   return 1 + curve->leftX + ((gdouble)x * width) / 1000.0;
}

int CurveLX(Curve *curve, gdouble x)
{  gdouble width = (curve->rightX - curve->leftX - curve->margin);

   return 1 + curve->leftX + (x * width) / (gdouble)curve->maxX;
}

int CurveY(Curve *curve, gdouble y)
{  gdouble hfact;

   hfact =   (gdouble)(curve->bottomY - curve->topY) 
           / (gdouble)curve->maxY;

   return curve->bottomY - y * hfact;
}

int CurveLogY(Curve *curve, gdouble y) /* not really a log */
{  gdouble hfact;

   if(y<1) return curve->bottomLY;

   hfact = (gdouble)(curve->bottomLY - curve->topLY);
 
   if(y==1) return curve->bottomLY - ((log(2)/log(curve->logMaxY)) * hfact)/2;
            return curve->bottomLY - (log(y)/log(curve->logMaxY)) * hfact;
}

/***
 *** Calculate the curve geometry
 ***/

void UpdateCurveGeometry(Curve *curve, char *largest_left_label, int right_padding)
{  GtkAllocation *a = &curve->widget->allocation;
   int w,h; 

   /* Top and bottom margins */

   SetText(curve->layout, curve->leftLabel, &w, &h);
   curve->topY = h + 10;

   SetText(curve->layout, "0123456789", &w, &h);
   curve->bottomY = a->height - h - 10;

   /* Left and right margins */

   SetText(curve->layout, largest_left_label, &w, &h);
   curve->leftX   = 5 + 6 + 3 + w;
   curve->rightX  = a->width - right_padding;

   /* Add space for the lograithmic curve */

   if(curve->enable & DRAW_LCURVE)
   {  int height = curve->bottomY - curve->topY;

      curve->bottomLY = curve->bottomY;
      curve->bottomY -= height/4;
      curve->topLY = curve->bottomY + h + 15; 
   }
}

/***
 *** Redraw the coordinate axes
 ***/

void RedrawAxes(Curve *curve)
{  GdkDrawable *d = curve->widget->window;
   int i,w,h,x,y; 
   int yg=0;
   int step;
   int bottom_y;

   /* Draw and label the left coordinate axis */

   gdk_gc_set_rgb_fg_color(Closure->drawGC, Closure->foreground);

   gdk_draw_line(d, Closure->drawGC,
		 curve->leftX, curve->topY, curve->leftX, curve->bottomY);

   if(curve->enable & DRAW_LCURVE)
   {  gdk_draw_line(d, Closure->drawGC,
		    curve->leftX, curve->topLY, curve->leftX, curve->bottomLY);
   }

   gdk_gc_set_rgb_fg_color(Closure->drawGC, Closure->curveColor);
   SetText(curve->layout, curve->leftLabel, &w, &h);
   x = curve->leftX - w/2;
   if(x < 5) x = 5;
   gdk_draw_layout(d, Closure->drawGC, 
		   x, curve->topY - h - 5, curve->layout);


   /* Draw and label the grid lines for the log curve */

   if(curve->enable & DRAW_LCURVE)
   {  int val;
      char buf[16];

      gdk_gc_set_rgb_fg_color(Closure->drawGC, Closure->logColor);
      SetText(curve->layout, curve->leftLogLabel, &w, &h);

      x = curve->leftX - w/2;
      if(x < 5) x = 5;
      gdk_draw_layout(d, Closure->drawGC, 
		      x, curve->topLY - h - 5, curve->layout);

      
      for(val=400; val>3; val/=2)
      {  y = CurveLogY(curve, val);
	 sprintf(buf,"%d",val);
	 SetText(curve->layout, buf, &w, &h);
	 gdk_gc_set_rgb_fg_color(Closure->drawGC, Closure->logColor);
	 gdk_draw_layout(d, Closure->drawGC, curve->leftX-9-w, y-h/2, curve->layout);
	 gdk_gc_set_rgb_fg_color(Closure->drawGC, Closure->foreground);
	 gdk_draw_line(d, Closure->drawGC, curve->leftX-6, y, curve->leftX, y);
	 gdk_gc_set_rgb_fg_color(Closure->drawGC, Closure->grid);
	 gdk_draw_line(d, Closure->drawGC, curve->leftX, y, curve->rightX, y);

	 val /=2;
	 y = CurveLogY(curve, val);
	 gdk_gc_set_rgb_fg_color(Closure->drawGC, Closure->foreground);
	 gdk_draw_line(d, Closure->drawGC, curve->leftX-3, y, curve->leftX, y);

	 if(curve->bottomLY-curve->topLY > 8*h)
	 {  sprintf(buf,"%d",val);
	    SetText(curve->layout, buf, &w, &h);
	    gdk_gc_set_rgb_fg_color(Closure->drawGC, Closure->logColor);
	    gdk_draw_layout(d, Closure->drawGC, curve->leftX-9-w, y-h/2, curve->layout);
	 }
      }
   }

   /* Draw and label the grid lines for the normal curve */

   if(curve->maxY < 20) step = 4;
   else step = 10;

   for(i=0; i<=curve->maxY; i+=step)
   {  char buf[4];
   
      g_snprintf(buf, 4, curve->leftFormat, i);
      SetText(curve->layout, buf, &w, &h);

      y = yg = CurveY(curve, i);
      gdk_gc_set_rgb_fg_color(Closure->drawGC, Closure->curveColor);
      gdk_draw_layout(d, Closure->drawGC, curve->leftX-9-w, y-h/2, curve->layout);
      gdk_gc_set_rgb_fg_color(Closure->drawGC, Closure->foreground);
      gdk_draw_line(d, Closure->drawGC, curve->leftX-6, y, curve->leftX, y);

      gdk_gc_set_rgb_fg_color(Closure->drawGC, Closure->grid);
      gdk_draw_line(d, Closure->drawGC, curve->leftX, y, curve->rightX, y);

      gdk_gc_set_rgb_fg_color(Closure->drawGC, Closure->foreground);
      y = CurveY(curve, i+step/2);
      if(y >= curve->topY)
        gdk_draw_line(d, Closure->drawGC, curve->leftX-3, y, curve->leftX, y);
   }


   /* Draw the right coordinate axis */

   gdk_gc_set_rgb_fg_color(Closure->drawGC, Closure->foreground);

   gdk_draw_line(d, Closure->drawGC,
		 curve->rightX, curve->topY, curve->rightX, curve->bottomY);

   if(curve->enable & DRAW_LCURVE)
      gdk_draw_line(d, Closure->drawGC,
		    curve->rightX, curve->topLY, curve->rightX, curve->bottomLY);

   /* Draw and label the bottom coordinate axis */

   gdk_gc_set_rgb_fg_color(Closure->drawGC, Closure->foreground);

   gdk_draw_line(d, Closure->drawGC,
		 curve->leftX, curve->bottomY, curve->rightX, curve->bottomY);

   if(curve->enable & DRAW_LCURVE)
   {  gdk_draw_line(d, Closure->drawGC,
		    curve->leftX, curve->bottomLY, curve->rightX, curve->bottomLY);
      bottom_y = curve->bottomLY;
   }
   else bottom_y = curve->bottomY;

   if(curve->maxX <= 100) step = 20;           /* <100M */
   else if(curve->maxX < 1000)  step = 100;    /* 100M ... 1000M */
   else if(curve->maxX < 8000)  step = 1024;   /* 1G .. 8G */
   else if(curve->maxX < 15000) step = 2560;   /* 8G .. 15G */
   else if(curve->maxX < 25000) step = 5120;   /* 15G .. 25G */
   else step = 10240;

   for(i=0; i<=curve->maxX; i+=step)
   {  char buf[10];
   
      switch(curve->bottomFormat)
      {  case CURVE_PERCENT:
 	   g_snprintf(buf, 10, "%d%%", i);
	   break;

         case CURVE_MEGABYTES:
	   if(step <= 100)
	        g_snprintf(buf, 10, "%dM",i);
	   else g_snprintf(buf, 10, "%3.1fG",(gdouble)i/1024.0);
	   break;
      }
      SetText(curve->layout, buf, &w, &h);

      x = CurveLX(curve,i)-1;
      gdk_draw_line(d, Closure->drawGC, x, bottom_y+6, x, bottom_y);
      gdk_draw_layout(d, Closure->drawGC, x-w/2, bottom_y+8, curve->layout);

      if(i && x < curve->rightX)
      {  gdk_gc_set_rgb_fg_color(Closure->drawGC, Closure->grid);
         gdk_draw_line(d, Closure->drawGC, x, curve->bottomY-1, x, yg);

	 if(curve->enable & DRAW_LCURVE)
	    gdk_draw_line(d, Closure->drawGC, x, curve->bottomLY-1, x, curve->topLY);
      }

      gdk_gc_set_rgb_fg_color(Closure->drawGC, Closure->foreground);
      x = CurveLX(curve,i+step/2)-1;
      if(x < curve->rightX)
        gdk_draw_line(d, Closure->drawGC, x, bottom_y+3, x, bottom_y);
   }
}

/*
 * Redraw the curve
 */

void RedrawCurve(Curve *curve, int last)
{  int i,x0,x1,fy0,fy1;

   x0  = CurveX(curve, 0);
   fy0 = CurveY(curve, curve->fvalue[0]);

   gdk_gc_set_rgb_fg_color(Closure->drawGC, Closure->curveColor);

   /* Draw the curve */

   for(i=1; i<=last; i++)
   {  x1  = CurveX(curve, i);

      if(curve->enable & DRAW_ICURVE)
      {  int iy  = CurveY(curve, curve->ivalue[i]);

	 if(curve->ivalue[i] > 0)
	 {  gdk_gc_set_rgb_fg_color(Closure->drawGC, Closure->barColor);
	    gdk_draw_rectangle(curve->widget->window,
			       Closure->drawGC, TRUE,
			       x0, iy, x0==x1 ? 1 : x1-x0, curve->bottomY-iy);
	 }
      }

      if(curve->enable & DRAW_LCURVE)
      {  int iy  = CurveLogY(curve, curve->lvalue[i]);

	 if(curve->lvalue[i] > 0)
	 {  gdk_gc_set_rgb_fg_color(Closure->drawGC, Closure->logColor);
	    gdk_draw_rectangle(curve->widget->window,
			       Closure->drawGC, TRUE,
			       x0, iy, x0==x1 ? 1 : x1-x0, curve->bottomLY-iy);
	 }
      }

      if(curve->enable & DRAW_FCURVE && curve->fvalue[i] >= 0)
      {  fy1 = CurveY(curve, curve->fvalue[i]);

	 if(x0 < x1)
	 {  gdk_gc_set_rgb_fg_color(Closure->drawGC, Closure->curveColor);
	    gdk_draw_line(curve->widget->window, Closure->drawGC, x0, fy0, x1, fy1);
	    fy0 = fy1;
	 }
      }

      x0 = x1;
   }
}
