/*  SVGATextMode -- An SVGA textmode manipulation/enhancement tool
 *
 *  Copyright (C) 1995  Koen Gadeyne
 *
 *  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 2 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, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */


/***
 *** validate.c, mode validation functions
 *** (c) 1995 Koen Gadeyne (kmg@barco.be)
 ***/


#include "misc.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <math.h>

#include "confdefs.h"
#include "parse_clock.h"
#include "parse_opt.h"
#include "validate.h"
#include "setclock.h"
#include "configfile.h"
#include "messages.h"



int check_range(float checkval, float range[MAX_RANGE_SPECS][2])
{
  int range_ok=FALSE;
  int rangepos=0;
  
  while ((range[rangepos][0]!=0) && (range[rangepos][1]!=0) && (!range_ok))
  {
    range_ok = ((checkval >= range[rangepos][0]) && (checkval <= range[rangepos][1]));
    PDEBUG(("Check_range(%1.3f): range from %1.3f to %1.3f %s", checkval, range[rangepos][0], range[rangepos][1], (range_ok==TRUE) ? "OK" : "failed"));
    rangepos++;
  }
  return(range_ok);
}


int validate_clock(int chipset, t_clockdef* ck, float req_clock, float* realclock, int optionmask, int report_error)
{
  float tempf;  
  int clock_valid=TRUE;

  if (GetClock(chipset, ck->clocks, ck->num_clocks, req_clock, realclock, optionmask, report_error) < 0)
  {
    clock_valid = FALSE;
  }
  else
  {  
    tempf = fabs(*realclock-req_clock);
    if( tempf > MAX_CLOCKDEVIATION )
    {
      clock_valid=FALSE;
      if (report_error)
      {
        if ((req_clock < 25.0) && ((optionmask & OPT_CLOCKDIV2)==0))
          PWARNING(("Selected clock is below standard VGA clocks, and is not available in 'clocks' line.\n\
                     UNLESS you enable division by 2 (Option 'ClockDiv2')"));
        PERROR(("The closest available clock %1.3f differs too much from specified clock %1.3f",*realclock,req_clock));
      }
    }
    PDEBUG(("Clock deviation (from requested clock) = |%1.2f-%1.2f|=%1.2f MHz (%1.2f%%).",
             req_clock, *realclock, tempf, (tempf*100)/req_clock));
  }
  return(clock_valid);
}


/*
 * validate the mode line, using H/V freq limits, clock definitions and max clock
 * abort with error message or just return error code depending on "report_error" flag
 * Also check if the required clock is available from clock generator. If it's not exactly 
 * the same (as with a clocks line, where the closest match will be used), replace
 * ck-> clock with REAL value that will be used from the clock generator.
 */


int validate_mode(int chipset, modestruct* mode, t_clockdef* ck,
                  float hsync_range[MAX_RANGE_SPECS][2],
                  float vsync_range[MAX_RANGE_SPECS][2],
                  int optionmask, int report_error)
{
  float realclock;  
  int mode_valid = TRUE;

 /*
  * clock speed (for VGA chip)
  */
   
  if (mode->pclock > ck->maxclock)
  {
    mode_valid=FALSE;
    PDEBUG(("Check clock speed: Too high"));
    if (report_error)
      PERROR(("Pixel Clock (%5.2f MHz) too high (max = %5.2f MHz) for this chipset", mode->pclock, ck->maxclock));
  }
  else PDEBUG(("Check clock speed: OK"));

 /*
  * clock value (for clock generator: can it produce that ?
  * if clock has to be approximated by closeby value, replace the value in the mode struct.
  * This assumes that ClockChips can create ANY clock close enough to ignore the error
  */

  if (ck->type & CK_SET_OF_CLOCKS)
  {
    if (!validate_clock(chipset, ck, mode->pclock, &realclock, optionmask, report_error)) mode_valid=FALSE;
    mode->pclock = realclock;   /* replace with actual clock from clocks line */
  }

 /*
  * sync ranges (for monitor)
  * Must be done AFTER clock checking, because clock checker changes pixel clock if it
  * requires a close approximation from the clocks line
  */
  
  mode->hor_freq = (mode->pclock*1000)/( ((int)(mode->totalh / 8)) * mode->font_width);
  
  mode->vert_refresh = (mode->hor_freq*1000)/mode->totalv;
  if (mode->mode_flags & INTERLACE) mode->vert_refresh *= 2;
  
  if (!check_range(mode->hor_freq, hsync_range)) 
  {
    mode_valid=FALSE;
    if (report_error) PERROR(("Horizontal Sync Frequency (%1.2fkHz) out of range.", mode->hor_freq));
  }
  if (!check_range(mode->vert_refresh, vsync_range))
  {
    mode_valid=FALSE;
    if (report_error) PERROR(("Vertical Refresh Frequency (%1.2fHz) out of range.", mode->vert_refresh));
  }
  return(mode_valid);
}


void scan_valid_modes(FILE* configfile, int chipset, t_clockdef* ck,
                      float hsync_range[MAX_RANGE_SPECS][2],
                      float vsync_range[MAX_RANGE_SPECS][2],
                      int optmask, int validate)
{
  modestruct mode;
  char modeline[1024];
  int mode_valid=TRUE;

  PDEBUG(("Scanning for valid Text Mode lines"));

  rewind(configfile); /* from start: we want them ALL! */

  while ( (fgets(modeline,1024,configfile)) && (!feof(configfile)) )
  {
    cleanupstring(modeline);
    /* only try mode lines that are long enough. This speeds up the scanning.
     * shortest could be "a 25 640 680 776 800 400 412 414 449"  */
    if (strlen(modeline) > 30)
    {
      if (parsemodeline(modeline, &mode))
      {
        /* must always do a "validate_mode", because it calculates H/V frequencies */
        mode_valid = validate_mode(chipset, &mode, ck, hsync_range, vsync_range, optmask, FALSE);
        if (!validate || mode_valid)
        {
          printf("%s  Clock: %3.2fMHz  Size: %dx%d  CharCell: %dx%d  Refresh: %3.2fkHz/%3.1fHz\n",
                  mode.idstring, mode.pclock, mode.cols, mode.rows, mode.font_width, mode.font_height,
                  mode.hor_freq, mode.vert_refresh);
        }
      }
    }
  }
}

void check_and_show_mode(int chipset, modestruct* p_mode, t_clockdef* ck,
                         float hsync_range[MAX_RANGE_SPECS][2],
                         float vsync_range[MAX_RANGE_SPECS][2],
                         int optmask, int checkit, const char* chipset_id)
{
 /*
  * First show what mode would be programmed.
  * this first call to validate_mode() will just fill in the H/V frequencies.
  * If the sync range validation bails out, you'll see the mode that caused it.
  * (instead of saying "it's wrong" without saying WHAT is wrong)
  */
  validate_mode(chipset, p_mode, ck, hsync_range, vsync_range, optmask, FALSE);
  
  printf("Chipset = '%s', Textmode clock = %3.2f MHz, %dx%d chars, CharCell = %dx%d. Refresh = %3.2fkHz/%3.1fHz.\n",
          chipset_id,p_mode->pclock,p_mode->cols,p_mode->rows,p_mode->font_width,
          p_mode->font_height,p_mode->hor_freq,p_mode->vert_refresh);
 /*
  * Now we should do some checking to see if the horizontal and vertical refresh frequencies are within limits
  * Don't do this when we will not be programming the hardware, so you can check a mode's frequencies with "-n".
  */
  if (checkit) validate_mode(chipset, p_mode, ck, hsync_range, vsync_range, optmask, TRUE);
}
