#pragma once

namespace nall {

//scan all four sides of the image for fully transparent pixels, and then crop them
//imagine an icon centered on a transparent background: this function removes the bordering
//this certainly won't win any speed awards, but nall::image is meant to be correct and simple, not fast
inline auto image::shrink(uint64_t transparentColor) -> void {
  //top
  { uint padding = 0;
    for(uint y : range(_height)) {
      const uint8_t* sp = _data + pitch() * y;
      bool found = false;
      for(uint x : range(_width)) {
        if(read(sp) != transparentColor) { found = true; break; }
        sp += stride();
      }
      if(found) break;
      padding++;
    }
    crop(0, padding, _width, _height - padding);
  }

  //bottom
  { uint padding = 0;
    for(uint y : reverse(range(_height))) {
      const uint8_t* sp = _data + pitch() * y;
      bool found = false;
      for(uint x : range(_width)) {
        if(read(sp) != transparentColor) { found = true; break; }
        sp += stride();
      }
      if(found) break;
      padding++;
    }
    crop(0, 0, _width, _height - padding);
  }

  //left
  { uint padding = 0;
    for(uint x : range(_width)) {
      const uint8_t* sp = _data + stride() * x;
      bool found = false;
      for(uint y : range(_height)) {
        if(read(sp) != transparentColor) { found = true; break; }
        sp += pitch();
      }
      if(found) break;
      padding++;
    }
    crop(padding, 0, _width - padding, _height);
  }

  //right
  { uint padding = 0;
    for(uint x : reverse(range(_width))) {
      const uint8_t* sp = _data + stride() * x;
      bool found = false;
      for(uint y : range(_height)) {
        if(read(sp) != transparentColor) { found = true; break; }
        sp += pitch();
      }
      if(found) break;
      padding++;
    }
    crop(0, 0, _width - padding, _height);
  }
}

inline auto image::crop(uint outputX, uint outputY, uint outputWidth, uint outputHeight) -> bool {
  if(outputX + outputWidth > _width) return false;
  if(outputY + outputHeight > _height) return false;

  uint8_t* outputData = allocate(outputWidth, outputHeight, stride());
  uint outputPitch = outputWidth * stride();

  for(uint y = 0; y < outputHeight; y++) {
    const uint8_t* sp = _data + pitch() * (outputY + y) + stride() * outputX;
    uint8_t* dp = outputData + outputPitch * y;
    for(uint x = 0; x < outputWidth; x++) {
      write(dp, read(sp));
      sp += stride();
      dp += stride();
    }
  }

  delete[] _data;
  _data = outputData;
  _width = outputWidth;
  _height = outputHeight;
  return true;
}

inline auto image::alphaBlend(uint64_t alphaColor) -> void {
  uint64_t alphaR = (alphaColor & _red.mask()  ) >> _red.shift();
  uint64_t alphaG = (alphaColor & _green.mask()) >> _green.shift();
  uint64_t alphaB = (alphaColor & _blue.mask() ) >> _blue.shift();

  for(uint y = 0; y < _height; y++) {
    uint8_t* dp = _data + pitch() * y;
    for(uint x = 0; x < _width; x++) {
      uint64_t color = read(dp);

      uint64_t colorA = (color & _alpha.mask()) >> _alpha.shift();
      uint64_t colorR = (color & _red.mask()  ) >> _red.shift();
      uint64_t colorG = (color & _green.mask()) >> _green.shift();
      uint64_t colorB = (color & _blue.mask() ) >> _blue.shift();
      double alphaScale = (double)colorA / (double)((1 << _alpha.depth()) - 1);

      colorA = (1 << _alpha.depth()) - 1;
      colorR = (colorR * alphaScale) + (alphaR * (1.0 - alphaScale));
      colorG = (colorG * alphaScale) + (alphaG * (1.0 - alphaScale));
      colorB = (colorB * alphaScale) + (alphaB * (1.0 - alphaScale));

      write(dp, (colorA << _alpha.shift()) | (colorR << _red.shift()) | (colorG << _green.shift()) | (colorB << _blue.shift()));
      dp += stride();
    }
  }
}

inline auto image::alphaMultiply() -> void {
  uint divisor = (1 << _alpha.depth()) - 1;

  for(uint y = 0; y < _height; y++) {
    uint8_t* dp = _data + pitch() * y;
    for(uint x = 0; x < _width; x++) {
      uint64_t color = read(dp);

      uint64_t colorA = (color & _alpha.mask()) >> _alpha.shift();
      uint64_t colorR = (color & _red.mask()  ) >> _red.shift();
      uint64_t colorG = (color & _green.mask()) >> _green.shift();
      uint64_t colorB = (color & _blue.mask() ) >> _blue.shift();

      colorR = (colorR * colorA) / divisor;
      colorG = (colorG * colorA) / divisor;
      colorB = (colorB * colorA) / divisor;

      write(dp, (colorA << _alpha.shift()) | (colorR << _red.shift()) | (colorG << _green.shift()) | (colorB << _blue.shift()));
      dp += stride();
    }
  }
}

inline auto image::transform(const image& source) -> void {
  return transform(source._endian, source._depth, source._alpha.mask(), source._red.mask(), source._green.mask(), source._blue.mask());
}

inline auto image::transform(bool outputEndian, uint outputDepth, uint64_t outputAlphaMask, uint64_t outputRedMask, uint64_t outputGreenMask, uint64_t outputBlueMask) -> void {
  if(_endian == outputEndian && _depth == outputDepth && _alpha.mask() == outputAlphaMask && _red.mask() == outputRedMask && _green.mask() == outputGreenMask && _blue.mask() == outputBlueMask) return;

  image output(outputEndian, outputDepth, outputAlphaMask, outputRedMask, outputGreenMask, outputBlueMask);
  output.allocate(_width, _height);

  for(uint y = 0; y < _height; y++) {
    const uint8_t* sp = _data + pitch() * y;
    uint8_t* dp = output._data + output.pitch() * y;
    for(uint x = 0; x < _width; x++) {
      uint64_t color = read(sp);
      sp += stride();

      uint64_t a = (color & _alpha.mask()) >> _alpha.shift();
      uint64_t r = (color & _red.mask()  ) >> _red.shift();
      uint64_t g = (color & _green.mask()) >> _green.shift();
      uint64_t b = (color & _blue.mask() ) >> _blue.shift();

      a = normalize(a, _alpha.depth(), output._alpha.depth());
      r = normalize(r, _red.depth(),   output._red.depth());
      g = normalize(g, _green.depth(), output._green.depth());
      b = normalize(b, _blue.depth(),  output._blue.depth());

      output.write(dp, (a << output._alpha.shift()) | (r << output._red.shift()) | (g << output._green.shift()) | (b << output._blue.shift()));
      dp += output.stride();
    }
  }

  operator=(move(output));
}

}
