// vi:ft=cpp
/*
 * \brief   Sky texture interface
 * \date    2005-10-24
 * \author  Norman Feske <norman.feske@genode-labs.com>
 */

/*
 * Copyright (C) 2005-2009
 * Genode Labs, Feske & Helmuth Systementwicklung GbR
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */

#pragma once

#include <l4/scout-gfx/sky_texture>
#include <l4/mag-gfx/canvas>

#include <cstring>

namespace Scout_gfx { namespace Pt {

using Mag_gfx::Canvas;
using Mag_gfx::Point;

/**
 * \param PT   pixel type (compatible to Pixel_rgba)
 * \param TW   tile width
 * \param TH   tile height
 */
template <typename PT>
class Sky_texture : public Scout_gfx::Sky_texture
{
private:
  typedef typename PT::Pixel Pixel;

  Mag_gfx::Area _s;

  short *_bufs[3]; // [TH][TW];
  Pixel _coltab[16*16*16];
  Pixel *_fallback; //[TH][TW];  /* fallback texture */

public:
  Sky_texture();
  virtual ~Sky_texture();

  void draw(Canvas *c, Point const &p);
  void generate(Mag_gfx::Area const &size, bool detailed = true);


  Area preferred_size() const { return Area(1, 1); }
  Area min_size() const { return Area(1, 1); }
  Area max_size() const { return Area(Area::Max_w, Area::Max_h); }

  Orientations expanding() const { return (Orientations)Horizontal | (Orientations)Vertical; }
  bool empty() const { return false; }

  void set_geometry(Rect const &r) { _pos = r.p1(); _size = r.area(); }
  Rect geometry() const { return Rect(_pos, _size); }
};

namespace _Local_murx {


/***********************
 ** Texture generator **
 ***********************/

/**
 * Calculate fractional part of texture position for a given coordinate
 */
static inline int calc_u(int x, int w, int texture_w)
{
  return ((texture_w*x<<8)/w) & 0xff;
}


/**
 * Kubic interpolation
 *
 * \param u   relative position between x1 and x2 (0..255)
 */
static inline int filter(int x0, int x1, int x2, int x3, int u)
{
  static int cached_u = -1;
  static int k0, k1, k2, k3;

  /*
   * Do not recompute coefficients when called
   * with the same subsequencing u values.
   */
  if (u != cached_u)
    {
      int v   = 255 - u;
      int uuu = (u*u*u)>>16;
      int vvv = (v*v*v)>>16;
      int uu  = (u*u)>>8;
      int vv  = (v*v)>>8;

      k0 = vvv/6;
      k3 = uuu/6;
      k1 = k3*3 - uu + (4<<8)/6;
      k2 = k0*3 - vv + (4<<8)/6;

      cached_u = u;
    }

  return (x0*k0 + x1*k1 + x2*k2 + x3*k3)>>8;
}


/**
 * Determine texture position by given position in image
 */
static inline int get_idx(int x, int w, int texture_w, int offset)
{
  return (offset + texture_w + (texture_w*x)/w) % texture_w;
}


/**
 * Generate sky texture based on bicubic interpolation of some noise
 */
static void gen_buf(short tmp[], int noise_w, int noise_h,
    short dst[], int dst_w,   int dst_h)
{
  /* generate noise */
  for (int i = 0; i < noise_h; i++) for (int j = 0; j < noise_w; j++)
    dst[i*dst_w + j] = random()%256 - 128;

  /* interpolate horizontally */
  for (int j = dst_w - 1; j >= 0; j--)
    {
      int x0_idx = get_idx(j, dst_w, noise_w, -1);
      int x1_idx = get_idx(j, dst_w, noise_w,  0);
      int x2_idx = get_idx(j, dst_w, noise_w,  1);
      int x3_idx = get_idx(j, dst_w, noise_w,  2);
      int u      =  calc_u(j, dst_w, noise_w);

      for (int i = 0; i < noise_h; i++)
	{
	  int x0 = dst[i*dst_w + x0_idx];
	  int x1 = dst[i*dst_w + x1_idx];
	  int x2 = dst[i*dst_w + x2_idx];
	  int x3 = dst[i*dst_w + x3_idx];

	  tmp[i*dst_w + j] = filter(x0, x1, x2, x3, u);
	}
    }

  /* vertical interpolation */
  for (int i = dst_h - 1; i >= 0; i--)
    {
      int y0_idx = get_idx(i, dst_h, noise_h, -1)*dst_w;
      int y1_idx = get_idx(i, dst_h, noise_h,  0)*dst_w;
      int y2_idx = get_idx(i, dst_h, noise_h,  1)*dst_w;
      int y3_idx = get_idx(i, dst_h, noise_h,  2)*dst_w;
      int u      =  calc_u(i, dst_h, noise_h);

      for (int j = 0; j < dst_w; j++) {

	  int y0 = tmp[y0_idx + j];
	  int y1 = tmp[y1_idx + j];
	  int y2 = tmp[y2_idx + j];
	  int y3 = tmp[y3_idx + j];

	  dst[i*dst_w + j] = filter(y0, y1, y2, y3, u);
      }
    }
}


/**
 * Normalize buffer values to specified maximum
 */
static void normalize_buf(short dst[], int len, int amp)
{
  int min = 0x7ffffff, max = 0;

  for (int i = 0; i < len; i++)
    {
      if (dst[i] < min) min = dst[i];
      if (dst[i] > max) max = dst[i];
    }

  if (max == min) return;

  for (int i = 0; i < len; i++)
    dst[i] = (amp*(dst[i] - min))/(max - min);
}


/**
 * Multiply buffer values with 24:8 fixpoint value
 */
static void multiply_buf(short dst[], int len, int factor)
{
  for (int i = 0; i < len; i++)
    dst[i] = (dst[i]*factor)>>8;
}


/**
 * Add each pair of values of two buffers
 */
static void add_bufs(short src1[], short src2[], short dst[], int len)
{
  for (int i = 0; i < len; i++)
    dst[i] = src1[i] + src2[i];
}


/**
 * We combine (add) multiple low-frequency textures with one high-frequency
 * texture to get nice shapes.
 */
static void brew_texture(short tmp[], short tmp2[], short dst[], int w, int h,
    int lf_start, int lf_end, int lf_incr, int lf_mul,
    int hf_val, int hf_mul)
{
  for (int i = lf_start; i < lf_end; i += lf_incr)
    {
      gen_buf(tmp, i, i, tmp2, w, h);
      multiply_buf(tmp2, w*h, (lf_mul - i)*32);
      add_bufs(tmp2, dst, dst, w*h);
    }
  if (hf_val)
    {
      gen_buf(tmp, hf_val, hf_val, tmp2, w, h);
      multiply_buf(tmp2, w*h, hf_mul*32);
      add_bufs(tmp2, dst, dst, w*h);
    }

  /* normalize texture to use four bits */
  normalize_buf(dst, w*h, 15);
}


/***************************
 ** Color table generator **
 ***************************/

static inline int mix_channel(int value1, int value2, int alpha)
{
  return (value1*(255 - alpha) + value2*alpha)>>8;
}


/**
 * Create 3D color table
 */
template <typename PT>
static void create_coltab(PT *dst, Mag_gfx::Rgba32::Color c0, Mag_gfx::Rgba32::Color c1, Mag_gfx::Rgba32::Color c2, Mag_gfx::Rgba32::Color bg)
{
  using Mag_gfx::color_conv;
  using Mag_gfx::Rgb32;
  for (int i = 0; i < 16; i++)
    for (int j = 0; j < 16; j++)
      for (int k = 0; k < 16; k++)
	{
	  int r = bg.r();
	  int g = bg.g();
	  int b = bg.b();

	  r = mix_channel(r, c2.r(), k*16);
	  g = mix_channel(g, c2.g(), k*16);
	  b = mix_channel(b, c2.b(), k*16);

	  r = mix_channel(r, c1.r(), j*16);
	  g = mix_channel(g, c1.g(), j*16);
	  b = mix_channel(b, c1.b(), j*16);

	  r = mix_channel(r, c0.r(), i*8);
	  g = mix_channel(g, c0.g(), i*8);
	  b = mix_channel(b, c0.b(), i*8);

	  int v = (((i ^ j ^ k)<<1) & 0xff) + 128 + 64;

	  r = (r + v)>>1;
	  g = (g + v)>>1;
	  b = (b + v)>>1;

	  //				r = g = b = min(255, 50 + ((i*j*128 + j*k*128 + k*i*128)>>8));

	  v = 180;
	  r = (v*r + (255 - v)*255)>>8;
	  g = (v*g + (255 - v)*255)>>8;
	  b = (v*b + (255 - v)*255)>>8;

	  dst[(k<<8) + (j<<4) + i] = color_conv<typename PT::Traits::Color>(Rgb32::Color(r, g, b));
	}
}


template <typename PT>
static void compose(char *dst, int dst_w, int dst_h, int x_start, int x_end,
    short src1[], int src1_y,
    short src2[], int src2_y,
    short src3[], int src3_y, int src_w, int src_h,
    PT    const coltab[])
{
  for (int k = 0; k <= x_end; k += src_w)
    {
      int x_offset = std::max(0, x_start - k);
      int x_max    = std::min(x_end - k, src_w - 1);

      for (int j = 0; j < dst_h; j++)
	{
	  short *s1 = src1 + x_offset + ((src1_y + j)%src_h)*src_w;
	  short *s2 = src2 + x_offset + ((src2_y + j)%src_h)*src_w;
	  short *s3 = src3 + x_offset + ((src3_y + j)%src_h)*src_w;
	  PT    *d  = reinterpret_cast<PT*>(dst  + j*dst_w) + x_offset + k;

	  for (int i = x_offset; i <= x_max; i++)
	    *d++ = coltab[*s1++ + *s2++ + *s3++];
	}
    }
}



template <typename PT>
static void copy(char *dst, int dst_w, int dst_h, int x_start, int x_end,
    PT const *src, int src_y, int src_w, int src_h)
{
  for (int k = 0; k <= x_end; k += src_w)
    {

      int x_offset = std::max(0, x_start - k);
      int x_max    = std::min(x_end - k, src_w - 1);

      for (int j = 0; j < dst_h; j++)
	{

	  PT const *s  = src  + x_offset + ((src_y + j)%src_h)*src_w;
	  PT *d  = reinterpret_cast<PT*>(dst + j*dst_w) + x_offset + k;

	  if (x_max - x_offset >= 0)
	    memcpy(d, s, (x_max - x_offset + 1)*sizeof(PT));
	}
    }
}

} // end ns: _Local_murx

/*****************
 ** Constructor **
 *****************/

template <typename PT>
void
Sky_texture<PT>::generate(Mag_gfx::Area const &size, bool detailed)
{
  _s = size;

  for (int i = 0; i < 3; ++i)
    {
      if (_bufs[i])
	free(_bufs[i]);

      _bufs[i] = (short*)malloc(_s.pixels() * sizeof(short));
      memset(_bufs[i], 0, _s.pixels() * sizeof(short));
    }

  short *_buf = (short *)malloc(_s.pixels() * sizeof(short));
  short *_tmp = (short *)malloc(_s.pixels() * sizeof(short));

  using namespace _Local_murx;
  /* create nice-looking textures */
  brew_texture(_tmp, _buf, _bufs[0], _s.w(), _s.h(), 3,  7,  1, 30, 30, 10);
  brew_texture(_tmp, _buf, _bufs[1], _s.w(), _s.h(), 3, 16,  3, 50, 40, 30);
  brew_texture(_tmp, _buf, _bufs[2], _s.w(), _s.h(), 5, 40, 11, 70,  0,  0);

  free(_tmp);
  free(_buf);

  /* shift texture 1 to bits 4 to 7 */
  multiply_buf(_bufs[1], _s.pixels(), 16*256);

  /* shift texture 2 to bits 8 to 11 */
  multiply_buf(_bufs[2], _s.pixels(), 16*16*256);

  /* create color table */
  create_coltab(_coltab, Mag_gfx::Rgba32::Color(255, 255, 255),
      Mag_gfx::Rgba32::Color(  0,   0,   0),
      Mag_gfx::Rgba32::Color(255, 255, 255),
      Mag_gfx::Rgba32::Color( 80,  88, 112));

  if (!detailed)
    {
      if (_fallback)
	free(_fallback);

      _fallback = (Pixel*)malloc(_s.pixels() * sizeof(Pixel));
      /* create fallback texture */
      compose(reinterpret_cast<char*>(_fallback), _s.w()*sizeof(Pixel), _s.h(),
	    0, _s.w() - 1, _bufs[0], 0, _bufs[1], 0, _bufs[2], 0,
	    _s.w(), _s.h(), _coltab);

      for (int i = 0; i < 3; ++i)
	{
	  free(_bufs[i]);
	  _bufs[i] = 0;
	}
    }
}

template <typename PT>
Sky_texture<PT>::Sky_texture()
: _s(Area(0,0)), _fallback(0)
{
  for (int i = 0; i < 3; ++i)
    _bufs[i] = 0;
}

template <typename PT>
Sky_texture<PT>::~Sky_texture()
{
  for (int i = 0; i < 3; i++)
    if (_bufs[i])
      free(_bufs[i]);

  if (_fallback)
    free(_fallback);
}


/*****************************************
 ** Implementation of Element interface **
 *****************************************/

template <typename PT>
void Sky_texture<PT>::draw(Canvas *c, Point const &p)
{
  using Mag_gfx::Rect;
  using namespace _Local_murx;

  char *addr = (char*)c->buffer();

  if (!addr) return;

  int bpl = c->bytes_per_line();
  Rect cl = c->clip() & (Rect(geometry().area()) + p);

  int v  = -p.y();
  int y0 = cl.y1() + v;
  int y1 = cl.y1() + (( (5*v)/16)%_s.h());
  int y2 = cl.y1() + (((11*v)/16)%_s.w());

  addr  += cl.y1()*bpl;

  if (_fallback)
    {
      copy(addr, bpl, cl.h(), cl.x1(), cl.x2(),
	  _fallback, cl.y1() - p.y(), _s.w(), _s.h());
      return;
    }

  compose(addr, bpl, cl.h(), cl.x1(), cl.x2(),
      _bufs[0], y0, _bufs[1], y1, _bufs[2], y2,
      _s.w(), _s.h(), _coltab);
}

}}
