/*
 * Copyright (c) 2010 Jice
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * The name of Jice may not be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Jice ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Jice BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdlib.h>
#include <string.h>

// windows specific inclusion of alloca
// all other platforms have alloca in stdlib.h
#ifndef alloca
#include <malloc.h>
#endif

#include "main.hpp"

// dummy height indicating that a cell is not water
#define NO_WATER -1000.0f
// how much wave energy is lost per second
#define DAMPING_COEF 1.3f
// wave height below which the water is considered still
#define ACTIVE_THRESHOLD 0.02f
// height on the triggering ripple
#define RIPPLE_TRIGGER 3.0f
// how many times ripples are updated per second
#define RIPPLE_FPS 10

RippleManager::RippleManager(TCODMap* waterMap) { init(waterMap); }

void RippleManager::init(TCODMap* waterMap) {
  this->width = waterMap->getWidth();
  this->height = waterMap->getHeight();
  bool* visited = (bool*)alloca(sizeof(bool) * width * height);
  memset(visited, 0, sizeof(bool) * width * height);
  // first time : compute water zones
  zone.cumulatedElapsed = 0.0f;
  zone.isActive = false;
  zone.data = new float[width * height];
  zone.oldData = new float[width * height];
  for (int dx = 0; dx < width; dx++) {
    for (int dy = 0; dy < height; dy++) {
      float value = waterMap->isWalkable(dx, dy) ? 0.0f : NO_WATER;
      // set water height to 0.0, not water cells to -1000
      zone.data[dx + dy * width] = value;
    }
  }
  memcpy(zone.oldData, zone.data, sizeof(float) * width * height);
}

void RippleManager::startRipple(int x, int y) {
  // look for the water zone
  int off = x + y * width;
  if (zone.data[off] != NO_WATER) {
    zone.data[off] = -RIPPLE_TRIGGER;
    zone.isActive = true;
  }
}

bool RippleManager::updateRipples(float elapsed) {
  bool updated = false;
  zone.cumulatedElapsed += elapsed;
  if (zone.isActive) {
    // update the ripples
    if (zone.cumulatedElapsed > 1.0f / RIPPLE_FPS) {
      zone.cumulatedElapsed = 0.0f;
      // swap grids
      float* tmp = zone.data;
      zone.data = zone.oldData;
      zone.oldData = tmp;
      zone.isActive = false;
      for (int zx2 = 1; zx2 < width - 1; zx2++) {
        for (int zy2 = 1; zy2 < height - 1; zy2++) {
          int off = zx2 + zy2 * width;
          if (zone.data[off] != NO_WATER) {
            float sum = 0.0f;
            int count = 0;
            // wave smoothing + spreading
            if (zone.oldData[off - 1] != NO_WATER) {
              sum += zone.oldData[off - 1];
              count++;
            }
            if (zone.oldData[off + 1] != NO_WATER) {
              sum += zone.oldData[off + 1];
              count++;
            }
            if (zone.oldData[off - width] != NO_WATER) {
              sum += zone.oldData[off - width];
              count++;
            }
            if (zone.oldData[off + width] != NO_WATER) {
              sum += zone.oldData[off + width];
              count++;
            }
            sum = sum * 2 / count;
            zone.data[off] = sum - zone.data[off];
            // damping
            zone.data[off] *= 1.0f - DAMPING_COEF / RIPPLE_FPS;
            if (ABS(zone.data[off]) > ACTIVE_THRESHOLD) {
              zone.isActive = true;
              updated = true;
            }
          }
        }
      }
    }
  }

  return updated;
}

void RippleManager::renderRipples(const TCODImage* ground, TCODImage* groundWithRipples) {
  float elCoef = TCODSystem::getElapsedSeconds() * 2.0f;
  for (int x = 1; x < width - 1; x++) {
    for (int y = 1; y < height - 1; y++) {
      if (getData(x, y) != NO_WATER) {
        float xOffset = (getData(x - 1, y) - getData(x + 1, y));
        float yOffset = (getData(x, y - 1) - getData(x, y + 1));
        float f[3] = {float(x), float(y), elCoef};
        xOffset += noise3d.get(f, TCOD_NOISE_SIMPLEX) * 0.3f;
        if (ABS(xOffset) < 250 && ABS(yOffset) < 250) {
          TCODColor col = ground->getPixel(x + (int)(xOffset), y + (int)(yOffset));
          col = col + TCODColor::white * xOffset * 0.1f;
          groundWithRipples->putPixel(x, y, col);
        }
      }
    }
  }
}
