/**
 * ASCII spinning glboe
 */

#include "colors.h"
#include <curses.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <math.h>
#include <poll.h>

static const double GOLDEN_RATIO = 1.6180339887498948482045868343656381;
static const int NUM_WAVES = 7;
static const double OFFSETS[2*NUM_WAVES] = {
  0.5459919526, 0.6072439135, 0.6217563193, 0.5444045324, 0.8923452588,
  0.4626828607, 0.9422234679,

  0.7870886548, 0.7425605127, 0.1510923405, 0.3889282293, 0.3730024274,
  0.1450487012, 0.9051931355,
};
static const double AMPLITUDES[NUM_WAVES] = {
  //0.9, 0.81, 0.729, 0.6561, 0.59049, 0.531441, 0.4782969
  0.83, 0.6889, 0.571787, 0.47458321, 0.3939040643, 0.326940373369, 0.27136050989627
};

static double hillnoise(double x, double y)
{
  double noise = 0;
  double sigma = 0;
  for (int i = 0; i < NUM_WAVES; i++) {
      // Rotate coordinates
      double rotation = fmod(((float)i)*GOLDEN_RATIO, 1.0)*2.0*M_PI;
      double u = x*cos(rotation) - y*sin(rotation);
      double v = -x*sin(rotation) - y*cos(rotation);
      double size = AMPLITUDES[i];
      double offsetx = OFFSETS[2*i];
      double offsety = OFFSETS[2*i+1];
      noise += (size/2.)*(sin(u/size + offsetx) + sin(v/size + offsety));
      sigma += size*size;
  }
  sigma = sqrt(sigma)/2.;
  noise /= 2.*sigma;
  return (0.5*(noise < 0 ? -1. : 1.)*sqrt(1 - exp(-2./M_PI * noise*noise)) + 0.5);
}

static double mix(double a, double b, double amount)
{
  return (1.-amount)*a + amount*b;
}

void draw_stars(double t)
{
  int rows,cols;
  getmaxyx(stdscr,rows,cols);
  attron(COLOR_PAIR(WHITE));
  for (int r = 0; r < rows; r++) {
    for (int c = 0; c < cols; c++) {
      double n = hillnoise(r*1,c*2+(t*.04));
      if (n > .8) {
        mvaddch(r,c,n > .9 ? '*' : '.');
      }
    }
  }
  attroff(COLOR_PAIR(WHITE));
}

static double get_rotation(double t)
{
  return t * (2*M_PI)/60. + M_PI;
}

void draw_globe(double t, double zoom)
{
  int rows,cols;
  getmaxyx(stdscr,rows,cols);
  double rotation = get_rotation(t);
  const double rho = rows/2.;
  for (int r = 0; r < rows; r++) {
    for (int c = 0; c < cols; c++) {
      double y = (c - cols/2) * 0.5 / zoom;
      double z = (r - rows/2) * 1.0 / zoom;
      double x = sqrt(rho*rho - z*z - y*y);

      if (z*z + y*y > rho*rho) {
        continue;
      }

      double theta = atan2(z, sqrt(x*x + y*y));
      double phi = fmod(atan2(y, x) + rotation, 2.*M_PI);
      double elevation = hillnoise(theta*4., phi*4.);
      double clouds = hillnoise(theta*12., phi*6. - 1.*rotation - 140.);
      int color;
      int ch;
      if ((fabs(z)/rho) > .9) {
        elevation = elevation + mix(.0, 1., 10.*(fabs(z)/rho - .9)) > .6 ? 1. : 0.;
      }
      if (clouds < .4) {
        continue;
      } else if (elevation < .55) {
        // Water
        ch = '~';
        color = COLOR_PAIR(BLUE);
      } else if (elevation < .65) {
        // Sand
        ch = ':';
        color = COLOR_PAIR(YELLOW);
      } else if (elevation < .75) {
        // Grass
        ch = ',' | A_BOLD;
        color = COLOR_PAIR(GREEN);
      } else if (elevation < .85) {
        // Forest
        ch = '&';
        color = COLOR_PAIR(GREEN);
      } else {
        // Mountain
        ch = '#';
        color = COLOR_PAIR(WHITE);
      }
      attron(color);
      mvaddch(r,c,ch);
      attroff(color);
    }
  }
}

void draw_clouds(double t, double zoom)
{
  int rows,cols;
  getmaxyx(stdscr,rows,cols);
  double rotation = get_rotation(t);
  const double rho = rows/2.;
  for (int r = 0; r < rows; r++) {
    for (int c = 0; c < cols; c++) {
      double y = (c - cols/2) * 0.5 / zoom;
      double z = (r - rows/2) * 1.0 / zoom;
      double x = sqrt(rho*rho - z*z - y*y);

      if (z*z + y*y > rho*rho) {
        continue;
      }

      double theta = atan2(z, sqrt(x*x + y*y));
      double phi = fmod(atan2(y, x) + rotation, 2.*M_PI);
      double clouds = hillnoise(theta*12., phi*6. - 1.*rotation - 140.);
      int color;
      int ch;
      if (clouds < .4) {
        ch = (clouds < .3 ? '0' : '%') | A_BOLD;
        color = COLOR_PAIR(WHITE);
        attron(color);
        mvaddch(r,c,ch);
        attroff(color);
      }
    }
  }
}

int latlon_to_rc(double t, double zoom, double lat, double lon, int *r, int *c)
{
  int rows,cols;
  getmaxyx(stdscr,rows,cols);
  const double rho = rows/2.;
  double theta = lat;
  double phi = lon + get_rotation(t);
  double x = rho*sin(theta)*cos(phi);
  double y = rho*sin(theta)*sin(phi);
  double z = rho*cos(theta);
  *r = z / (1.0/zoom) + rows/2;
  *c = y / (.5/zoom) + cols/2;
  return x < 0;
}

const double target_lat = M_PI/2*1.01;
const double target_lon = M_PI + 2.6;
// return 1 if visible, else 0
int get_target_pos(double t, double zoom, int *targetr, int *targetc)
{
  return latlon_to_rc(t, zoom, target_lat, target_lon, targetr, targetc);
}