//
// Epson driver
//

#include <sane/sane.h>
#include <sane/saneopts.h>

#include <math.h>

#include "scanner.h"
#include "SaneInterface.h"

int stackSize = 100000;

char* versionString = "$VER: "DEVNAME".device 0.40 ("__DATE__")";
char  DevName[] = DEVNAME".device";
char  DevIdString[] = DEVNAME;

static int modeDepth[] = {1,8,24};
static int halfToneActive[] = {TRUE,FALSE,FALSE};
static int brightnessValues[] = {60,40,20,0,-20,-40,-60};

static SANE_Option_Descriptor* getGammaDescr(SANE_Handle handle,SANE_Int option);
static SANE_Status getGamma(SANE_Handle handle,SANE_Int option,void* val,ULONG* flags);
static SANE_Status setGamma(SANE_Handle handle,SANE_Int option,void* val,ULONG* flags);
static int saneNum(SANE_Handle handle,char* str);

struct MicrotekInfo
{
  OptionId          ac_id;
  struct OptionInfo ac_info;
} info[] =
  {
    {ID_SCANMODE,       {0,SANE_NAME_SCAN_MODE,        modeDepth,NULL,NULL,NULL}},
    {ID_TL_X,           {0,SANE_NAME_SCAN_TL_X,             NULL,NULL,NULL,NULL}},
    {ID_TL_Y,           {0,SANE_NAME_SCAN_TL_Y,             NULL,NULL,NULL,NULL}},
    {ID_BR_X,           {0,SANE_NAME_SCAN_BR_X,             NULL,NULL,NULL,NULL}},
    {ID_BR_Y,           {0,SANE_NAME_SCAN_BR_Y,             NULL,NULL,NULL,NULL}},
    {ID_RESOLUTION,     {0,SANE_NAME_SCAN_RESOLUTION,       NULL,NULL,NULL,NULL}},
    {ID_HALFTONEPATTERN,{0,SANE_NAME_HALFTONE,    halfToneActive,NULL,NULL,NULL}},
    {ID_BRIGHTNESS,     {0,SANE_NAME_BRIGHTNESS,brightnessValues,NULL,NULL,NULL}},
    {ID_ANALOGGAMMA,    {0,NULL,                            NULL,getGammaDescr,getGamma,setGamma}},
    {ID_SCAN_SOURCE,    {0,SANE_NAME_SCAN_SOURCE,           NULL,NULL,NULL,NULL}},
    {ID_NONE,           {0,NULL,                            NULL,NULL,NULL,NULL}}
};

struct OptionInfo* getOption(SANE_Handle handle,OptionId id)
{
  int i = 0;

  while( (info[i].ac_id != ID_NONE) && (info[i].ac_id != id) )
    i++;
    
  if( info[i].ac_id == id )
    return &info[i].ac_info;
  else
    return NULL;
}

//
// Return gamma descriptor
//

static SANE_Range analog_gamma_range =
  { SANE_FIX(0.1), SANE_FIX(4.0), SANE_FIX(0) };

static SANE_Option_Descriptor gammaDescr =
{
  SANE_NAME_ANALOG_GAMMA,
  SANE_TITLE_ANALOG_GAMMA,
  NULL,
  SANE_TYPE_FIXED,
  SANE_UNIT_NONE,
  sizeof(SANE_Word),
  0,
  SANE_CONSTRAINT_RANGE,
  (SANE_String*)(&analog_gamma_range)
};

static SANE_Option_Descriptor* getGammaDescr(SANE_Handle handle,SANE_Int option)
{
  return &gammaDescr;
}

//
// Implement Gamma
//

static int grayGammaNum,redGammaNum,greenGammaNum,blueGammaNum;
static SANE_Fixed gammaVal= SANE_FIX(1.0);

SANE_Status saneOpen(SANE_String_Const devicename,SANE_Handle* handle)
{
  SANE_Status stat;

  stat = sane_open(devicename,handle);

  if( stat == SANE_STATUS_GOOD )
  {
    int customGammaNum;

    grayGammaNum = saneNum(*handle,SANE_NAME_GAMMA_VECTOR);
    redGammaNum = saneNum(*handle,SANE_NAME_GAMMA_VECTOR_R);
    greenGammaNum = saneNum(*handle,SANE_NAME_GAMMA_VECTOR_G);
    blueGammaNum = saneNum(*handle,SANE_NAME_GAMMA_VECTOR_B);

    // Enable custom gamma
    // if( customGammaNum = saneNum(*handle,SANE_NAME_CUSTOM_GAMMA) )
    // {
    //   SANE_Bool val = SANE_TRUE;
    //
    //   sane_control_option(*handle,customGammaNum,SANE_ACTION_SET_VALUE,&val,NULL);
    // }
  }

  return stat;
}

static SANE_Status getGamma(SANE_Handle handle,SANE_Int option,void* val,ULONG* flags)
{
  *(SANE_Fixed *)val = gammaVal;

  return SANE_STATUS_GOOD;
}

static SANE_Status setGamma(SANE_Handle handle,SANE_Int option,void* val,ULONG* flags)
{
  SANE_Word  table[256];
  SANE_Word* tab;
  double     gamma;

  gammaVal = *(SANE_Fixed *)val;
  gamma = SANE_UNFIX(gammaVal);

  tab = table;

  if( (gamma <= 0.0) || (gamma == 1.0) )
  {
    int i;

    for( i = 0 ; i < 256 ; i++ )
      *(tab++) = i;
  }
  else
  {
    int    i;
    double g = 1/gamma;

    for( i = 0 ; i < 256 ; i++ )
    {
      int b;
      
      b = (int)(256.0*pow(((double)i)/256.0,g));
      *(tab++) = b>255?255:(b<0?0:b);
    }
  }
  sane_control_option(handle,grayGammaNum,SANE_ACTION_SET_VALUE,table,NULL);
  sane_control_option(handle,redGammaNum,SANE_ACTION_SET_VALUE,table,NULL);
  sane_control_option(handle,greenGammaNum,SANE_ACTION_SET_VALUE,table,NULL);
  sane_control_option(handle,blueGammaNum,SANE_ACTION_SET_VALUE,table,NULL);

  return SANE_STATUS_GOOD;
}

// Get SANE option number
//
//   input:  str = SANE option identifier
//
//   return: the SANE option number (0 if not supported)
//
static int saneNum(SANE_Handle handle,char* str)
{
  if( str == NULL )
    return -1;
  else
  {
    const SANE_Option_Descriptor *sod;
    int   i;

    i = 1;
    while( (sod = sane_get_option_descriptor(handle,i)) && ((sod->name==0) || strcmp(str,sod->name)) )
      i++;

    if( sod )
      return i;
    else
      return 0;
  }
}

