#include <list>
#include <cmath>
#include <map>

#include "Scanner.h"
#include "Scsi.h"
#include "SaneInterface.h"

// list of available scanner devices
static SANE_Device **deviceList = 0;

static ListItem imTypeItems[] = {
  {"Color", IMTYPE_COLOR, 1},
  {"Grayscale", IMTYPE_GRAY, 1},
  {"Black & White", IMTYPE_BW}
};
static const int NUM_IMTYPE = sizeof(imTypeItems) / sizeof(ListItem);

static ListItem gammaCorrectItems[] = {
  {"CRT A", SCN_GAMMA_CRTA, 2},
  {"CRT B", SCN_GAMMA_CRTB, 2},
  {"Printer A", SCN_GAMMA_PRINTA, 2},
  {"Printer B", SCN_GAMMA_PRINTB, 2},
  {"Printer C", SCN_GAMMA_PRINTA, 2}
};
static const int NUM_GAMMA = sizeof(gammaCorrectItems) / sizeof(ListItem);

static ListItem halftonePatternItems[] = {
  {"Hard Tone", SCN_HTONE_HARD, 1},
  {"Soft Tone", SCN_HTONE_SOFT, 1},
  {"Net Screen", SCN_HTONE_NET, 1},
  {"4x4 Bayer", SCN_HTONE_DITHERA, 4},
  {"4x4 Spiral", SCN_HTONE_DITHERB, 4},
  {"4x4 Net Screen", SCN_HTONE_DITHERC, 4},
  {"8x4 Net Screen", SCN_HTONE_DITHERD, 4},
  {"Disable", SCN_HTONE_DISABLE, 1}
};
static const int NUM_HTONE = sizeof(halftonePatternItems) / sizeof(ListItem);

static ListItem colorCorrectItems[] = {
  {"Dot-matrix Printer", SCN_CLR_CORRECT_DOTMAT, 1},
  {"Impact Dot Printer", SCN_CLR_CORRECT_IMPACT, 4},
  {"Color Laser Printer", SCN_CLR_CORRECT_LASER, 1},
  {"Color Inkjet Printer", SCN_CLR_CORRECT_INKJET, 1},
  {"Color Monitor", SCN_CLR_CORRECT_CRT, 1}
};
static const int NUM_CCORRECT = sizeof(colorCorrectItems) / sizeof(ListItem);


MySANE_Device::MySANE_Device(const char *name, const char *model,
			     const char *vendor, const char *type)
{
  this->name = new SANE_Char[strlen(name) + 1];
  strcpy(const_cast<SANE_String>(this->name), name);
  
  this->model = new SANE_Char[strlen(model) + 1];
  strcpy(const_cast<SANE_String>(this->model), model);
  
  this->vendor = new SANE_Char[strlen(vendor) + 1];
  strcpy(const_cast<SANE_String>(this->vendor), vendor);
  
  this->type = new SANE_Char[strlen(type) + 1];
  strcpy(const_cast<SANE_String>(this->type), type);
}

MySANE_Device&
MySANE_Device::operator=(const MySANE_Device& d)
{
  name = new SANE_Char[strlen(d.name) + 1];
  strcpy(const_cast<SANE_String>(name), d.name);

  model = new SANE_Char[strlen(d.model) + 1];
  strcpy(const_cast<SANE_String>(model), d.model);

  vendor = new SANE_Char[strlen(d.vendor) + 1];
  strcpy(const_cast<SANE_String>(vendor), d.vendor);

  type = new SANE_Char[strlen(d.type) + 1];
  strcpy(const_cast<SANE_String>(type), d.type);

  return *this;
}

OpenDevice::~OpenDevice()
{
  for (int i = 0; i < NUM_OPTIONS; i++) {
    SANE_Option_Descriptor& opt = options[i];
    switch (opt.constraint_type) {
    case SANE_CONSTRAINT_WORD_LIST:
      delete[] opt.constraint.word_list;
      break;
    case SANE_CONSTRAINT_STRING_LIST:
      delete[] opt.constraint.string_list;
      break;
    case SANE_CONSTRAINT_RANGE:
      delete opt.constraint.range;
      break;
    default:
      break;
    }
  }
}

typedef map<SANE_Handle, smart_ptr<OpenDevice>,
  less<SANE_Handle> > OpenDeviceList;
static OpenDeviceList openDevices;

static SANE_Status
findDevices()
{
  smart_ptr<ScsiDevice> dev(new ScsiDevice);
  if (!dev.get())
    return SANE_STATUS_NO_MEM;
  
  char device[10];
  list<smart_ptr<MySANE_Device> > devices;
  const char idStr[] = "EPSON SCANNER";
  for (char c = 'a'; c <= 'h'; c++) {
    sprintf(device, "/dev/sg%c", c);
    if (dev->open(device) == 0)	{ // found a scsi device
      const char *inq = reinterpret_cast<const char*>(dev->inquiry());
      if (inq && !strncmp(inq + 8, idStr, strlen(idStr))) {
	smart_ptr<MySANE_Device> newdev
	  (new MySANE_Device(device, inq + 22, "EPSON", "flatbed scanner"));
	devices.push_back(newdev);
      }
    }
  }

  if (devices.empty())
    return SANE_STATUS_ACCESS_DENIED;
  else {
    if (deviceList) {
      for (int i = 0; deviceList[i]; i++) {
	delete static_cast<MySANE_Device*>(deviceList[i]);
	deviceList[i] = 0;
      }
      delete [] deviceList;
    }
    deviceList = new (SANE_Device*)[devices.size() + 1];
    SANE_Device **p = deviceList;
    list<smart_ptr<MySANE_Device> >::iterator i;
    for (i = devices.begin(); i != devices.end(); i++) 
       *(p++) = (*i).release();
    deviceList[devices.size()] = 0;
    return SANE_STATUS_GOOD;
  }
}

// authorize is a pointer to a function that the backend can use to
// get authorization (username, password) from a user for a particular
// resource.  I won't use it anywhere in this driver.
SANE_Status
sane_init (SANE_Int * version_code, SANE_Auth_Callback)
{
  if (version_code)
    *version_code = SANE_VERSION_CODE(SANE_CURRENT_MAJOR,
				      Scanner::getVersionMajor(),
				      Scanner::getVersionMinor());
  return SANE_STATUS_GOOD;
}

void
sane_exit (void)
{
  if (deviceList) {
    for (int i = 0; deviceList[i]; i++)
      delete static_cast<MySANE_Device*>(deviceList[i]);
    delete [] deviceList;
    deviceList = 0;
  }

  openDevices.erase(openDevices.begin(), openDevices.end());
}

SANE_Status
sane_get_devices (const SANE_Device *** device_list,
		  SANE_Bool)
{
  SANE_Status s = findDevices();
  if (s != SANE_STATUS_GOOD)
    return s;

  *device_list = const_cast<const SANE_Device **>(deviceList);
  return SANE_STATUS_GOOD;
}

static const double MM_PER_INCH = 25.4;
static const double INCHES_PER_MM = 1.0 / MM_PER_INCH;

static SANE_Fixed
in2mm(double in)
{
  return SANE_FIX(in * MM_PER_INCH);
}

static double
mm2in(SANE_Fixed mm)
{
  return SANE_UNFIX(mm) * INCHES_PER_MM;
}

// return the maximum scanning width, in mm, with the current settings.
static SANE_Fixed
maxWidth(const OpenDevice& d)
{
  float amax = 8 * floor(d.identity.max_horiz_area *
			 ((float)d.settings.main_scan_res *
			  (float)d.settings.main_scan_zoom / 100.0) / 8.0);
  return in2mm(amax / (float)d.settings.main_scan_res);
}

// return the maximum scanning height, in mm, with the current settings.
static SANE_Fixed
maxHeight(const OpenDevice& d)
{
  float amax = (d.identity.max_vert_area *
		((float)d.settings.sub_scan_res *
		 (float)d.settings.sub_scan_zoom / 100.0));
  return in2mm(amax / (float)d.settings.sub_scan_res);
}

SANE_Status
sane_open (SANE_String_Const devicename,
	   SANE_Handle * handle)
{
  if (!devicename || strlen(devicename) == 0) {
    // use the first available device
    if (!deviceList) {
      SANE_Status s = findDevices();
      if (s != SANE_STATUS_GOOD)
	return s;
    }
    devicename = deviceList[0]->name;
  }
      
  smart_ptr<Scanner> scan(new Scanner);
  if (scan->open(devicename) < 0)
    return SANE_STATUS_ACCESS_DENIED;

  smart_ptr<OpenDevice> dev(new OpenDevice);
  dev->scanner = scan;
  scan->getIdentity(dev->identity);
  scan->getSettings(dev->settings);

  // set up options
  SANE_Option_Descriptor *opt = dev->options;
  OptValue *vals = dev->values;

  // option count
  opt = dev->options + OPT_NUM_OPTS;
  memset(opt, 0, sizeof(SANE_Option_Descriptor));
  opt->type = SANE_TYPE_INT;
  vals[OPT_NUM_OPTS].wrd = NUM_OPTIONS;

  // mode group
  opt = dev->options + OPT_MODE_GROUP;
  memset(opt, 0, sizeof(SANE_Option_Descriptor));
  opt->title = "Mode";
  opt->type = SANE_TYPE_GROUP;

  // resolution
  opt = dev->options + OPT_RESOLUTION;
  opt->name = SANE_NAME_SCAN_RESOLUTION;
  opt->title = SANE_TITLE_SCAN_RESOLUTION;
  opt->desc = SANE_DESC_SCAN_RESOLUTION;
  opt->type = SANE_TYPE_INT;
  opt->unit = SANE_UNIT_DPI;
  opt->size = sizeof(SANE_Int);
  opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  opt->constraint_type = SANE_CONSTRAINT_WORD_LIST;
  {
    int nres = 0;
    while (dev->identity.resolutions[nres])
      nres++;
    SANE_Word *res = new SANE_Word[nres + 1];
    res[0] = nres;
    for (int i = 0; i < nres; i++)
      res[i+1] = dev->identity.resolutions[i];
    opt->constraint.word_list = res;
  }
  dev->values[OPT_RESOLUTION].wrd = dev->settings.main_scan_res;
  dev->settings.sub_scan_res = dev->settings.main_scan_res;
  
  // speed
  opt = dev->options + OPT_SPEED;
  opt->name = SANE_NAME_SCAN_SPEED;
  opt->title = SANE_TITLE_SCAN_SPEED;
  opt->desc = SANE_DESC_SCAN_SPEED;
  opt->type = SANE_TYPE_BOOL;
  opt->unit = SANE_UNIT_NONE;
  opt->size = sizeof(SANE_Word);
  opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  opt->constraint_type = SANE_CONSTRAINT_NONE;
  dev->values[OPT_SPEED].wrd = dev->settings.scanmode;

  // preview mode
  opt = dev->options + OPT_PREVIEW;
  opt->name = SANE_NAME_PREVIEW;
  opt->title = SANE_TITLE_PREVIEW;
  opt->desc = SANE_DESC_PREVIEW;
  opt->type = SANE_TYPE_BOOL;
  opt->unit = SANE_UNIT_NONE;
  opt->size = sizeof(SANE_Word);
  opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  opt->constraint_type = SANE_CONSTRAINT_NONE;
  dev->values[OPT_SPEED].wrd = SANE_FALSE;

  // image type
  opt = dev->options + OPT_IMAGE_TYPE;
  opt->name = SANE_NAME_SCAN_MODE;
  opt->title = SANE_TITLE_SCAN_MODE;
  opt->desc = SANE_DESC_SCAN_MODE;
  opt->type = SANE_TYPE_STRING;
  opt->unit = SANE_UNIT_NONE;
  opt->size = 40;
  opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  opt->constraint_type = SANE_CONSTRAINT_STRING_LIST;
  {
    SANE_String_Const *types = new SANE_String_Const[NUM_IMTYPES + 1];
    types[0] = imTypeItems[0].name; // IMTYPE_COLOR;
    types[1] = imTypeItems[1].name; // imtype_GRAY;
    types[2] = imTypeItems[2].name; // IMTYPE_BW;
    types[3] = NULL;
    opt->constraint.string_list = types;
  }
  if (dev->settings.color_mode == SCN_CLR_COLOR_PAGE ||
      dev->settings.color_mode == SCN_CLR_COLOR_LINE)
    dev->values[OPT_IMAGE_TYPE].str = imTypeItems[0].name;
  else if (dev->settings.bits_per_pixel == 1)
    dev->values[OPT_IMAGE_TYPE].str = imTypeItems[2].name;
  else
    dev->values[OPT_IMAGE_TYPE].str = imTypeItems[1].name;

  // geometry group
  opt = dev->options + OPT_GEOMETRY_GROUP;
  memset(opt, 0, sizeof(SANE_Option_Descriptor));
  opt->title = "Geometry";
  opt->type = SANE_TYPE_GROUP;

  SANE_Range *rng;

  // top left x
  opt = dev->options + OPT_TL_X;
  opt->name = SANE_NAME_SCAN_TL_X;
  opt->title = SANE_TITLE_SCAN_TL_X;
  opt->desc = SANE_DESC_SCAN_TL_X;
  opt->type = SANE_TYPE_FIXED;
  opt->unit = SANE_UNIT_MM;
  opt->size = sizeof(SANE_Fixed);
  opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  opt->constraint_type = SANE_CONSTRAINT_RANGE;
  rng = new SANE_Range;
  rng->min = SANE_FIX(0.0);
  rng->max = maxWidth(*dev);
  rng->quant = 0;
  opt->constraint.range = rng;
  dev->values[OPT_TL_X].wrd = in2mm(dev->settings.main_scan_offset);

  // top left y
  opt = dev->options + OPT_TL_Y;
  opt->name = SANE_NAME_SCAN_TL_Y;
  opt->title = SANE_TITLE_SCAN_TL_Y;
  opt->desc = SANE_DESC_SCAN_TL_Y;
  opt->type = SANE_TYPE_FIXED;
  opt->unit = SANE_UNIT_MM;
  opt->size = sizeof(SANE_Fixed);
  opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  opt->constraint_type = SANE_CONSTRAINT_RANGE;
  rng = new SANE_Range;
  rng->min = SANE_FIX(0.0);
  rng->max = maxHeight(*dev);
  rng->quant = 0;
  opt->constraint.range = rng;
  dev->values[OPT_TL_Y].wrd = in2mm(dev->settings.sub_scan_offset);

  // lower right x
  opt = dev->options + OPT_BR_X;
  opt->name = SANE_NAME_SCAN_BR_X;
  opt->title = SANE_TITLE_SCAN_BR_X;
  opt->desc = SANE_DESC_SCAN_BR_X;
  opt->type = SANE_TYPE_FIXED;
  opt->unit = SANE_UNIT_MM;
  opt->size = sizeof(SANE_Fixed);
  opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  opt->constraint_type = SANE_CONSTRAINT_RANGE;
  rng = new SANE_Range;
  rng->min = in2mm(dev->settings.main_scan_offset);
  rng->max = maxWidth(*dev);
  rng->quant = 0;
  opt->constraint.range = rng;
  dev->values[OPT_BR_X].wrd =
    in2mm(dev->settings.main_scan_offset + dev->settings.main_scan_area);

  // bottom right y
  opt = dev->options + OPT_BR_Y;
  opt->name = SANE_NAME_SCAN_BR_Y;
  opt->title = SANE_TITLE_SCAN_BR_Y;
  opt->desc = SANE_DESC_SCAN_BR_Y;
  opt->type = SANE_TYPE_FIXED;
  opt->unit = SANE_UNIT_MM;
  opt->size = sizeof(SANE_Fixed);
  opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  opt->constraint_type = SANE_CONSTRAINT_RANGE;
  rng = new SANE_Range;
  rng->min = in2mm(dev->settings.sub_scan_offset);
  rng->max = maxWidth(*dev);
  rng->quant = 0;
  opt->constraint.range = rng;
  dev->values[OPT_BR_Y].wrd =
    in2mm(dev->settings.sub_scan_offset + dev->settings.sub_scan_area);
  
  // zoom
  opt = dev->options + OPT_ZOOM;
  opt->name = "zoom";
  opt->title = "Zoom factor";
  opt->desc = "Determines zoom factor.";
  opt->type = SANE_TYPE_INT;
  opt->unit = SANE_UNIT_PERCENT;
  opt->size = sizeof(SANE_Int);
  opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  opt->constraint_type = SANE_CONSTRAINT_RANGE;
  rng = new SANE_Range;
  rng->min = 50;
  rng->max = 200;
  rng->quant = 0;
  opt->constraint.range = rng;
  dev->values[OPT_ZOOM].wrd = dev->settings.main_scan_zoom;
  dev->settings.sub_scan_zoom = dev->settings.main_scan_zoom;

  // image enhancements group
  opt = dev->options + OPT_ENHANCEMENT_GROUP;
  memset(opt, 0, sizeof(SANE_Option_Descriptor));
  opt->title = "Enhancements";
  opt->type = SANE_TYPE_GROUP;

  // brightness
  opt = dev->options + OPT_BRIGHTNESS;
  opt->name = SANE_NAME_BRIGHTNESS;
  opt->title = SANE_TITLE_BRIGHTNESS;
  opt->desc = SANE_DESC_BRIGHTNESS;
  opt->type = SANE_TYPE_INT;
  opt->unit = SANE_UNIT_NONE;
  opt->size = sizeof(SANE_Int);
  opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  opt->constraint_type = SANE_CONSTRAINT_RANGE;
  rng = new SANE_Range;
  rng->min = -3;
  rng->max = 3;
  rng->quant = 0;
  opt->constraint.range = rng;
  dev->values[OPT_ZOOM].wrd = dev->settings.main_scan_zoom;
  dev->settings.sub_scan_zoom = dev->settings.main_scan_zoom;

  // gamma correction
  opt = dev->options + OPT_GAMMA;
  opt->name = "gamma";
  opt->title = "Gamma Correction";
  opt->desc = "Builtin gamma gorrection options";
  opt->type = SANE_TYPE_STRING;
  opt->unit = SANE_UNIT_NONE;
  opt->size = 40;
  opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  opt->constraint_type = SANE_CONSTRAINT_STRING_LIST;
  {
    SANE_String_Const *gamma = new SANE_String_Const[NUM_GAMMA + 1];
    int j = 0;
    for (int i = 0; i < NUM_GAMMA; i++)
      if (gammaCorrectItems[i].level >= dev->identity.software_level)
	gamma[j++] = gammaCorrectItems[i].name;
    gamma[j] = 0;
    opt->constraint.string_list = gamma;
  }
  for (int i = 0; i < NUM_GAMMA; i++)
    if (dev->settings.gamma == gammaCorrectItems[i].val) {
      dev->values[OPT_GAMMA].str = gammaCorrectItems[i].name;
      break;
    }
  
  // halftone pattern
  opt = dev->options + OPT_HALFTONE;
  opt->name = SANE_NAME_HALFTONE;
  opt->title = SANE_TITLE_HALFTONE;
  opt->desc = SANE_DESC_HALFTONE;
  opt->type = SANE_TYPE_STRING;
  opt->unit = SANE_UNIT_NONE;
  opt->size = 40;
  opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  opt->constraint_type = SANE_CONSTRAINT_STRING_LIST;
  {
    SANE_String_Const *htone = new SANE_String_Const[NUM_HTONE + 1];
    int j = 0;
    for (int i = 0; i < NUM_HTONE; i++)
      if (halftonePatternItems[i].level >= dev->identity.software_level)
	htone[j++] = halftonePatternItems[i].name;
    htone[j] = 0;
    opt->constraint.string_list = htone;
  }
  for (int i = 0; i < NUM_HTONE; i++)
    if (dev->settings.halftone == halftonePatternItems[i].val) {
      dev->values[OPT_HALFTONE].str = halftonePatternItems[i].name;
      break;
    }

  // color correction
  opt = dev->options + OPT_COLOR_CORRECT;
  opt->name = "color-correction";
  opt->title = "Color correction";
  opt->desc = "Selects color correction for different output devices.";
  opt->type = SANE_TYPE_STRING;
  opt->unit = SANE_UNIT_NONE;
  opt->size = 40;
  opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  opt->constraint_type = SANE_CONSTRAINT_STRING_LIST;
  {
    SANE_String_Const *ccor = new SANE_String_Const[NUM_HTONE + 1];
    int j = 0;
    for (int i = 0; i < NUM_CCORRECT; i++)
      if (colorCorrectItems[i].level >= dev->identity.software_level)
	ccor[j++] = colorCorrectItems[i].name;
    ccor[j] = 0;
    opt->constraint.string_list = ccor;
  }
  for (int i = 0; i < NUM_CCORRECT; i++)
    if (dev->settings.halftone == colorCorrectItems[i].val) {
      dev->values[OPT_COLOR_CORRECT].str = colorCorrectItems[i].name;
      break;
    }

  // the handle is the device's index in openDevices
  *handle = static_cast<SANE_Handle>(dev.get());
  openDevices[*handle] = dev;
  
  return SANE_STATUS_GOOD;
}

void
sane_close (SANE_Handle handle)
{
  openDevices.erase(handle);
}

const SANE_Option_Descriptor *
sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
{
  if (option < 0 || option >= NUM_OPTIONS)
    return NULL;
  
  OpenDeviceList::iterator i = openDevices.find(handle);
  if (i != openDevices.end()) 	// found it
    return (*i).second->options + option;
  else
    return NULL;
}

SANE_Status
sane_control_option(SANE_Handle handle, SANE_Int option,
		    SANE_Action action, void *value,
		    SANE_Int * info)
{
  OpenDeviceList::iterator i = openDevices.find(handle);
  if (i == openDevices.end() || option < 0 || option >= NUM_OPTIONS)
    return SANE_STATUS_INVAL;

  OpenDevice& dev = *((*i).second);
  SANE_Option_Descriptor& opt = dev.options[option];
  
  switch (action) {
  case SANE_ACTION_GET_VALUE:
    if (opt.type == SANE_TYPE_STRING)
      strcpy(static_cast<SANE_String>(value), dev.values[option].str);
    else
      *static_cast<SANE_Word*>(value) = dev.values[option].wrd;
    break;
  case SANE_ACTION_SET_VALUE:
    break;
  case SANE_ACTION_SET_AUTO:
    break;
  }

  return SANE_STATUS_GOOD;
}

SANE_Status
sane_get_parameters (SANE_Handle handle,
		     SANE_Parameters * params)
{
}

SANE_Status
sane_start (SANE_Handle handle)
{
}

SANE_Status
sane_read (SANE_Handle handle, SANE_Byte * data,
	   SANE_Int max_length, SANE_Int * length)
{
}

void
sane_cancel (SANE_Handle handle)
{
}

SANE_Status
sane_set_io_mode (SANE_Handle handle,
		  SANE_Bool non_blocking)
{
}

SANE_Status
sane_get_select_fd (SANE_Handle handle,
		    SANE_Int * fd)
{
}

SANE_String_Const
sane_strstatus (SANE_Status status)
{
}
