//==============
//ScanlineFilter
//==============

ScanlineFilter scanlineFilter;

void ScanlineFilter::size(unsigned &width, unsigned &height) {
  if(enabled && height <= 240) height *= 2;
}

void ScanlineFilter::render(
  const uint16_t *&input, unsigned &pitch,
  unsigned width, unsigned &height
) {
  if(enabled && height <= 240) {
    pitch >>= 1;

    const uint16_t *sp = input;
    uint16_t *dp = buffer;
    for(unsigned y = 0; y < height; y++) {
      for(unsigned x = 0; x < width; x++) {
        uint16_t color = *sp++;
        *(dp +   0) = color;
        *(dp + 512) = adjust[color];
        dp++;
      }

      sp += pitch - width;
      dp += 1024 - width;
    }

    input = buffer;
    pitch = 1024;
    height *= 2;
  }
}

void ScanlineFilter::setIntensity(unsigned intensity) {
  if(intensity >= 100) {
    enabled = false;
  } else {
    enabled = true;

    for(unsigned i = 0; i < 32768; i++) {
      unsigned r = (i >>  0) & 31;
      unsigned g = (i >>  5) & 31;
      unsigned b = (i >> 10) & 31;

      r = (double)r * (double)intensity / 100.0;
      g = (double)g * (double)intensity / 100.0;
      b = (double)b * (double)intensity / 100.0;

      adjust[i] = (r << 0) + (g << 5) + (b << 10);
    }
  }
}

ScanlineFilter::ScanlineFilter() {
  enabled = false;
  adjust = new uint16_t[32768];
  buffer = new uint16_t[512 * 480];
  setIntensity(50);
}

ScanlineFilter::~ScanlineFilter() {
  delete[] adjust;
  delete[] buffer;
}

//======
//Filter
//======

Filter filter;

const uint8_t Filter::gamma_ramp_table[32] = {
  0x00, 0x01, 0x03, 0x06, 0x0a, 0x0f, 0x15, 0x1c,
  0x24, 0x2d, 0x37, 0x42, 0x4e, 0x5b, 0x69, 0x78,
  0x88, 0x90, 0x98, 0xa0, 0xa8, 0xb0, 0xb8, 0xc0,
  0xc8, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0xff,
};

uint8_t Filter::contrast_adjust(uint8_t input) {
  signed result = input - contrast + (2 * contrast * input + 127) / 255;
  return max(0, min(255, result));
}

uint8_t Filter::brightness_adjust(uint8_t input) {
  signed result = input + brightness;
  return max(0, min(255, result));
}

uint8_t Filter::gamma_adjust(uint8_t input) {
  signed result = (signed)(pow(((double)input / 255.0), (double)gamma / 100.0) * 255.0 + 0.5);
  return max(0, min(255, result));
}

void Filter::colortable_update() {
  double kr = 0.2126, kb = 0.0722, kg = (1.0 - kr - kb);  //luminance weights

  for(unsigned i = 0; i < 32768; i++) {
    unsigned color  //rgb555->rgb888 conversion
    = ((i & 0x7c00) << 9) + ((i & 0x7000) << 4)
    + ((i & 0x03e0) << 6) + ((i & 0x0380) << 1)
    + ((i & 0x001f) << 3) + ((i & 0x001c) >> 2);

    signed l;
    signed r = (color >> 16) & 0xff;
    signed g = (color >>  8) & 0xff;
    signed b = (color      ) & 0xff;

    if(gamma_ramp == true) {
      r = gamma_ramp_table[r >> 3];
      g = gamma_ramp_table[g >> 3];
      b = gamma_ramp_table[b >> 3];
    }

    if(contrast != 0) {
      r = contrast_adjust(r);
      g = contrast_adjust(g);
      b = contrast_adjust(b);
    }

    if(brightness != 0) {
      r = brightness_adjust(r);
      g = brightness_adjust(g);
      b = brightness_adjust(b);
    }

    if(gamma != 100) {
      r = gamma_adjust(r);
      g = gamma_adjust(g);
      b = gamma_adjust(b);
    }

    if(sepia == true) {
      l = (signed)((double)r * kr + (double)g * kg + (double)b * kb);
      l = max(0, min(255, l));

      r = (signed)((double)l * (1.0 + 0.300));
      g = (signed)((double)l * (1.0 - 0.055));
      b = (signed)((double)l * (1.0 - 0.225));

      r = max(0, min(255, r));
      g = max(0, min(255, g));
      b = max(0, min(255, b));
    }

    if(grayscale == true) {
      l = (signed)((double)r * kr + (double)g * kg + (double)b * kb);
      l = max(0, min(255, l));
      r = g = b = l;
    }

    if(invert == true) {
      r ^= 0xff;
      g ^= 0xff;
      b ^= 0xff;
    }

    colortable[i] = (r << 16) | (g << 8) | (b);
  }
}

void Filter::size(unsigned &outwidth, unsigned &outheight, unsigned width, unsigned height) {
  scanlineFilter.size(width, height);

  if(opened() && renderer > 0) {
    return dl_size(renderer, outwidth, outheight, width, height);
  }

  outwidth  = width;
  outheight = height;
}

void Filter::render(
  uint32_t *output, unsigned outpitch,
  const uint16_t *input, unsigned pitch, unsigned width, unsigned height
) {
  scanlineFilter.render(input, pitch, width, height);

  if(opened() && renderer > 0) {
    return dl_render(renderer, output, outpitch, input, pitch, width, height);
  }

  pitch >>= 1;
  outpitch >>= 2;

  for(unsigned y = 0; y < height; y++) {
    const uint16_t *in = input + y * pitch;
    uint32_t *out = output + y * outpitch;
    for(unsigned x = 0; x < width; x++) {
      *out++ = colortable[*in++];
    }
  }
}

QWidget* Filter::settings() {
  if(opened() && renderer > 0) {
    return dl_settings(renderer);
  } else {
    return 0;
  }
}

Filter::Filter() {
  renderer = 0;

  contrast = 0;
  brightness = 0;
  gamma = 100;

  gamma_ramp = false;
  sepia = false;
  grayscale = false;
  invert = false;

  colortable = new uint32_t[32768];
  colortable_update();

  if(open("snesfilter")) {
    dl_supported = sym("snesfilter_supported");
    dl_colortable = sym("snesfilter_colortable");
    dl_configuration = sym("snesfilter_configuration");
    dl_size = sym("snesfilter_size");
    dl_render = sym("snesfilter_render");
    dl_settings = sym("snesfilter_settings");

    dl_colortable(colortable);
    dl_configuration(config());
  } else {
    config().video.windowed.swFilter   = 0;
    config().video.fullscreen.swFilter = 0;
  }
}

Filter::~Filter() {
  delete[] colortable;
}
