#include "image.hpp"
#include "common/figure.hpp"
#include "a83/mat.hpp"
#include "a83/log.hpp"
#include "a83/random.hpp"
#include <omp.h>
void Image::print    (int posx, int posy, Image& dst) const { make_print<blend_past>(posx, posy, dst); }
void Image::print_min(int posx, int posy, Image& dst) const { make_print<blend_min>(posx, posy, dst); }
void Image::print_max(int posx, int posy, Image& dst) const { make_print<blend_max>(posx, posy, dst); }
void Image::print_avr(int posx, int posy, Image& dst) const { make_print<blend_avr>(posx, posy, dst); }
void Image::print_add(int posx, int posy, Image& dst) const { make_print<blend_add>(posx, posy, dst); }
void Image::print_add_safe(int posx, int posy, Image& dst) const { make_print<blend_add_safe>(posx, posy, dst); }
void Image::print_or (int posx, int posy, Image& dst) const { make_print<blend_or>(posx, posy, dst); }
void Image::print_diff(int posx, int posy, Image& dst) const { make_print<blend_diff>(posx, posy, dst); }
void Image::print_and(int posx, int posy, Image& dst) const { make_print<blend_and>(posx, posy, dst); }
void Image::print_blend_158(int posx, int posy, Image& dst) const { make_print<blend_158>(posx, posy, dst); }
void Image::noise(bool USE_RED) {
  if ( !check())
    return;
  for (int i = 0; i < size(); ++i) {
    Color val;
    if (USE_RED)
      val = a83::lcg_parkmiller();
    else
      val = a83::lcg_parkmiller() % Color::RED_START;
    fast_set(i, val);
  }
}
void Image::fast_print(Image& dst) const
  { memcpy(dst.get_data(), this->get_data(), size() * sizeof(Color)); }
void Image::fast_print_mask(Image& dst, CN(Image) mask,
CN(Image) mask_light, bool use_mask_light) const{
  if ( !check() || !dst.check()) return;
  if ( !mask.check()) {
    fast_print(dst);
    if (use_mask_light)
      fast_print_max(dst);
    a83::log("empty mask");
    return;
  }
  if (use_mask_light and mask_light.check()) { // с осветляюшей маской
    FOR (i, size()) {
      cnauto SRC = fast_get(i).val;
      cnauto MASK = mask.fast_get(i).val;
      auto &DST = *dst.fast_get_ptr(i);
      blend_avr(mask_light.fast_get(i), DST);
      const uint8_t SRC_DST[2] = {SRC, DST.val};
      DST.val = SRC_DST[MASK & 1]; // оптимизация без использования ветвления
    }
  } else { // без осветляюшей маски
    FOR (i, size()) {
      auto &DST = dst.fast_get_ptr(i)->val;
      cnauto SRC = fast_get(i).val;
      cnauto MASK = mask.fast_get(i).val;
      const uint8_t SRC_DST[2] = {SRC, DST};
      DST = SRC_DST[MASK & 1]; // оптимизация без использования ветвления
    }
  } // if mask_light
}
void Image::print_mask(int posx, int posy, Image& dst, CN(Image) mask) const {
  if ( !check() || !dst.check()) return;
  if ( !mask.check()) {
    print(posx, posy, dst);
    a83::log("empty mask");
    return;
  }
  // вычисляются границы вставки:
  if (posx >= dst.getX()) return;
  if (posy >= dst.getY()) return;
  auto ey = posy + getY();
  auto ex = posx + getX();
  if (ex <= 0) return;
  if (ey <= 0) return;
  if (ey > dst.getY()) ey = dst.getY();
  if (ex > dst.getX()) ex = dst.getX();
  cauto old_posy = posy;
  cauto old_posx = posx;
  if (posx < 0) posx = 0;
  if (posy < 0) posy = 0;
  for (int y = posy; y < ey; ++y) 
  for (int x = posx; x < ex; ++x) {
    cnauto SRC = fast_get(x - old_posx, y - old_posy).val;
    cnauto MASK = mask.fast_get(x - old_posx, y - old_posy).val;
    auto &DST = dst.fast_get_ptr(x, y)->val;
    const uint8_t SRC_DST[2] = {SRC, DST};
    DST = SRC_DST[MASK & 1]; // оптимизация без использования ветвления
  }
}
void Image::draw_rect(int sx, int sy, int mx, int my, Color col) {
  if ( !check()) return;
  if (mx < 1 || my < 1) return;
  cauto ex = sx + mx;
  cauto ey = sy + my;
  // рисование линий по два раза:
  for (int x = sx; x < ex; ++x) {
    set(x, sy, col);
    set(x, ey, col);
  }
  for (int y = sy; y< ey; ++y) {
    set(sx, y, col);
    set(ex, y, col);
  }
  set(ex, ey, col); // последняя точка
}
void Image::circle_fill(int x0, int y0, CN(Color) col, int radius) {
  if ( !check()) return;
  int x = radius;
  int y = 0;
  int xChange = 1 - (radius << 1);
  int yChange = 0;
  int radiusError = 0;
  while (x >= y) {
    for (int i = x0 - x; i <= x0 + x; ++i) {
      set(i, y0 + y, col);
      set(i, y0 - y, col);
    }
    for (int i = x0 - y; i <= x0 + y; ++i) {
      set(i, y0 + x, col);
      set(i, y0 - x, col);
    }
    ++y;
    radiusError += yChange;
    yChange += 2;
    if (((radiusError << 1) + xChange) > 0) {
      --x;
      radiusError += xChange;
      xChange += 2;
    }
  } // while (x >= y)
}
void Image::draw_circle(Color col, int x0, int y0, int radius) {
  if ( !check() or radius < 1) return;
  int x = radius;
  int y = 0;
  int err = 0;
  while (x >= y) {
    set(x0 + x, y0 + y, col);
    set(x0 + y, y0 + x, col);
    set(x0 - y, y0 + x, col);
    set(x0 - x, y0 + y, col);
    set(x0 - x, y0 - y, col);
    set(x0 - y, y0 - x, col);
    set(x0 + y, y0 - x, col);
    set(x0 + x, y0 - y, col);
    if (err <= 0) {
      ++y;
      err += 2 * y + 1;
    }
    if (err > 0) {
      --x;
      err -= 2 * x + 1;
    }
  } // while
}
// EFLA Variation E (Addition Fixed Point PreCalc)
void Image::draw_line(int x, int y, int x2, int y2, Color color) {
  // выход, если линия за пределами видимости
  if (x < 0 and x2 < 0)
    return;
  if (x >= X and x2 >= X)
    return;
  if (y < 0 and y2 < 0)
    return;
  if (y >= Y and y2 >= Y)
    return; 
  bool yLonger = false;
	int shortLen = y2 - y;
	int longLen = x2 - x;
	if (std::abs(shortLen) > std::abs(longLen)) {
		int swap = shortLen;
		shortLen = longLen;
		longLen = swap;				
		yLonger = true;
	}
	int decInc;
	if (longLen == 0)
    decInc = 0;
	else
    decInc = (shortLen << 16) / longLen;
	if (yLonger) {
		if (longLen > 0) {
			longLen += y;
			for (int j = 0x8000 + (x << 16);y <= longLen; ++y) {
				set(j >> 16, y, color);
				j += decInc;
			}
			return;
		}
		longLen += y;
		for (int j = 0x8000 + (x << 16);y >= longLen; --y) {
			set(j >> 16, y, color);	
			j -= decInc;
		}
		return;	
	}
	if (longLen > 0) {
		longLen += x;
		for (int j = 0x8000 + (y << 16); x <= longLen; ++x) {
			set(x, j >> 16, color);
			j += decInc;
		}
		return;
	}
	longLen += x;
	for (int j = 0x8000 + (y << 16); x >= longLen; --x) {
		set(x, j >> 16, color);
		j -= decInc;
	}
} // draw_line
void Image::print_rotated(Image& dst, float pos_x, float pos_y,
float center_x, float center_y, float deg) const {
  using namespace a83;
  if ( !dst.check() || !check())
    return;
  if (deg == 0.0f) {
    print(pos_x, pos_y, dst);
    return;
  }
  center_x -= 1.0f;
  center_y -= 1.0f;
  auto rad = deg2rad(deg);
  auto rotX = a83::cos(-rad);
	auto rotY = a83::sin(-rad);
  #pragma omp parallel for simd
  FOR (y, dst.getY())
  FOR (x, dst.getX()) {
    cauto fx = x - pos_x;
    cauto fy = y - pos_y;
		cauto rotPosY = (rotY * fx) + (rotX * fy) + center_y;
    cauto rotPosX = (rotX * fx) - (rotY * fy) + center_x;
		if (cauto* col_ptr = get_ptr2(rotPosX, rotPosY); col_ptr)
      dst.fast_set(x, y, *col_ptr);
  }
} // print_rotated
void Image::fast_cut(int sx, int sy, int mx, int my, Image& dst) const {
  dst.init(mx, my);
  cauto ex = sx + mx;
  cauto ey = sy + my;
  for (auto y = sy; y < ey; ++y)
  for (auto x = sx; x < ex; ++x)
    dst.fast_set(x - sx, y - sy, fast_get(x, y));
}
void Image::cut2img(int sx, int sy, int mx, int my, Image& dst,
bound_e mode) const {
  dst.init(mx, my);
  cauto ex = sx + mx;
  cauto ey = sy + my;
  if (sx < 0 || sy < 0) { // safe ver
    for (auto y = sy; y < ey; ++y)
    for (auto x = sx; x < ex; ++x)
      dst.set(x - sx, y - sy, this->get(x, y, mode));
  } else { // fast ver
    for (auto y = sy; y < ey; ++y)
    for (auto x = sx; x < ex; ++x)
      dst.fast_set(x - sx, y - sy, this->get(x, y, mode));
  }
} // cut2img
void Image::draw_star(int posx, int posy, float size, float power) {
  if (size <= 0.0)
    return;
  const int i_size = std::ceil(size);
  cauto mul = 1.0f / size;
  cauto mul2 = 1.0f / (size * 0.375f);
  for (int i = 0; i < i_size; ++i) {
    cauto c1 = 255.0f * (1.0f - i * mul) * power;
    set_add(posx - i, posy, c1);
    set_add(posx + i, posy, c1);
    set_add(posx, posy - i, c1);
    set_add(posx, posy + i, c1);
    cauto c2 = 255.0f * (1.0f - i * mul2) * power;
    if (c2 <= 0.0f)
      continue;
    set_add(posx - i, posy - i, c2);
    set_add(posx + i, posy - i, c2);
    set_add(posx - i, posy + i, c2);
    set_add(posx + i, posy + i, c2);
  }
}
void Image::draw_star2(int posx, int posy, float size, float power) {
  if (size <= 0.0f)
    return;
  const int i_size = std::ceil(size);
  cauto mul = 1.0f / size;
  for (int i = 0; i < i_size; ++i) {
    cauto c1 = 255.0f * (1.0f - i * mul) * power;
    set_add(posx - i, posy, c1);
    set_add(posx + i, posy, c1);
  }
}
void Image::binary_safe() {
  for (int i = 0; i < size(); ++i) {
    nauto col = *fast_get_ptr(i);
    cauto is_red = col.is_red();
    if (is_red) {
      col.val = col.val < (Color::RED_START + (Color::RED_COUNT / 2))
        ? Color::RED_START : Color::RED;
    } else {
      col.val = col.val < Color::GRAY_COUNT / 2
        ? Color::BLACK : Color::WHITE;
    }
  }
}
void Image::pixel_expand(CN(Color) alpha, bool use_8way) {
  Image tmp(X, Y, alpha);
  if (use_8way) {
    FOR (y, Y)
    FOR (x, X) {
      cnauto c = fast_get(x, y);
      if (c != alpha) {
        tmp.set(x + 0, y + 0, c);
        tmp.set(x + 1, y + 0, c);
        tmp.set(x - 1, y + 0, c);
        tmp.set(x + 0, y - 1, c);
        tmp.set(x + 0, y + 1, c);
        tmp.set(x - 1, y - 1, c);
        tmp.set(x - 1, y + 1, c);
        tmp.set(x + 1, y - 1, c);
        tmp.set(x + 1, y + 1, c);
      } // if c != alpha
    } // FOR y,x
  } else { // 4 way
    FOR (y, Y)
    FOR (x, X) {
      cnauto c = fast_get(x, y);
      if (c != alpha) {
        tmp.set(x + 0, y + 0, c);
        tmp.set(x + 1, y + 0, c);
        tmp.set(x - 1, y + 0, c);
        tmp.set(x + 0, y - 1, c);
        tmp.set(x + 0, y + 1, c);
      } // if c != alpha
    } // FOR y,x
  } // if 4 way
  tmp.fast_print(*this);
} // pixel_expand
void Image::motion_lines(float len, float dir_x, float dir_y, float freg) {
  if (len <= 0)
    return;
  #ifdef DEBUG
  assert( !(dir_x == 0 and dir_y == 0)
    and (std::abs(dir_x) <= 1 and std::abs(dir_y) <= 1));
  assert(freg >= 0 and freg <= 1);
  #endif
  /* TODO не работает, как надо
  static Image tmp;
  tmp.init(*this);
  // линии размываются назад
  dir_x *= -1.0f;
  dir_y *= -1.0f;
  FOR (y, Y)
  FOR (x, X) {
    if (a83::frand() > freg)
      continue;
    auto col = tmp.fast_get(x, y).to_f();
    float fx = x;
    float fy = y;
    cauto max_len = int(std::ceil(len * a83::frand()));
    FOR (i, max_len) {
      // определить степень прозрачности пикселя
      // в линии (чем дальше тем прозрачнее)
      cauto power = 1.0f - (i / float(max_len));
      set(fx, fy, Color::from_f(std::lerp(tmp.get(fx, fy).to_f(), col, power)));
      fx += dir_x;
      fy += dir_y;
    }
  }*/
} // motion_lines