#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <math.h>
#include <string.h>

#include "random.h"
#include "world.h"

#define _MAKE_CRATERS
#define _USE_ROUGHNESS_MAP
//#define _BIZARRE_ROUGHNESS_MAP
//#define _BIZARRE_ALT_MAP
//#define DEBUG

//----------------------------------------------------------------------------
// The maps.  Note that, in this module, the color_map array is used during
// the generation of the altitude map, so in that case, it is misnamed.
//----------------------------------------------------------------------------

map_t    far          alt_map;
map_t    far          color_map;

//----------------------------------------------------------------------------
// An internal temporary map used in generating the other maps:
//----------------------------------------------------------------------------

static map_t  temp_map;

//----------------------------------------------------------------------------
// Assertion code for this module:
//----------------------------------------------------------------------------

#ifdef DEBUG

   static void     _Assert
   (
      const char * file,
      unsigned     line,
      const char * assertion
   )
   {
      fflush (stdout);
      fprintf ( stderr, "\nAssertion failed (%s, line %u): %s\n",
                file, line, assertion );
      fflush (stderr);
      abort ();
   }

   #define ASSERT(f) if (f) {} else _Assert ( __FILE__, __LINE__, #f )

#else

   #define ASSERT(f)

#endif

//----------------------------------------------------------------------------
// FUNCTION  rough_init
//----------------------------------------------------------------------------

#ifdef _USE_ROUGHNESS_MAP

static void        rough_init
(
   void
)
{
   int  x, y;

   for ( x = 0; x < map_size_x; x += 64 )
   {
      for ( y = 0; y < map_size_y; y += 64 )
      {
         color_map [x][y] = (unsigned char) (random ( 128 ) + 80);
      }
   }
}

#endif

//----------------------------------------------------------------------------
// FUNCTION  map_init
//----------------------------------------------------------------------------

static void        map_init
(
   void
)
{
   int  x, y;

   for ( x = 0; x < map_size_x; x += 64 )
   {
      for ( y = 0; y < map_size_y; y += 64 )
      {
         color_map [x][y] = (unsigned char) random ( 256 );
      }
   }
}

//----------------------------------------------------------------------------
// FUNCTION  flat_area
//----------------------------------------------------------------------------

static void        flat_area(
   int             map_x,
   int             map_y,
   int             size_x,
   int             size_y,
   int             height_offset/* = 0 */
)
{
   int             height;
   int             x, y;

   // First get the average height of the original rectangular region.

   height = 0;

   for ( x = 0; x < size_x; ++ x ){
      for ( y = 0; y < size_y; ++ y ){
         height +=
            (int) alt_map [(x+map_x) & clip_mask_x][(y+map_y) & clip_mask_y];
      }
   }

   height /= size_x * size_y;

   // Offset from the average height.

   height += height_offset;
   if      ( height < min_alt   )  height = min_alt;
   else if ( height > max_alt-1 )  height = max_alt - 1;

   // Put in the landing pad.

   for ( x = 0; x < size_x; ++ x ){
      for ( y = 0; y < size_y; ++ y ){
         alt_map [(x+map_x) & clip_mask_x][(y+map_y) & clip_mask_y] =
            (unsigned char) (height + random (2));
      }
   }
}

//----------------------------------------------------------------------------
// FUNCTION  quonset_hut
//----------------------------------------------------------------------------
// These quonset huts run parallel to the Y axis.
//----------------------------------------------------------------------------

static void        quonset_hut
(
   int             map_x,
   int             map_y,
   int             size_y
)
{
   int             average;
   int             x, y;
   int r_squared;
   const           radius = 4;

   // First get the average height of the original region.

   average = 0;

   for ( x = 0; x < radius * 2; ++ x ){
      for ( y = 0; y < size_y; ++ y ){
         average +=alt_map [(x+map_x) & clip_mask_x][(y+map_y) & clip_mask_y];
      }
   }

   average /= size_y * radius * 2;

   // Bury the quonset hut a bit.

   average -= scale_area / scale_height;

   // Build the hut.

   r_squared = radius * radius;

   for ( x = 0; x < radius * 2; ++ x ){
      int height =
         (int) sqrt ( (double) (r_squared - (x-radius)*(x-radius) + 1) );
      height *= (scale_area / scale_height);
      height += average;
      if ( height > max_alt ) height = max_alt;

      for ( y = 0; y < size_y; ++ y ){
         alt_map [(x+map_x) & clip_mask_x][(y+map_y) & clip_mask_y] =(unsigned char) height;
      }
   }
}

//----------------------------------------------------------------------------
// FUNCTION  generate_roughness_map
//----------------------------------------------------------------------------

#ifdef _USE_ROUGHNESS_MAP

static void generate_roughness_map(void)
{
   int counter = 0;
   int which   = 0;
   int square_size,x1,y1;
   printf ( "  Roughness map:    0 %%" );

   rough_init ();

   for ( square_size = 64; square_size > 1; square_size /= 2 ){
      for (  x1 = 0; x1 < map_size_x; x1 += square_size ) {
         for ( y1 = 0; y1 < map_size_y; y1 += square_size ) {
            // Get the four influential points.

            int x2 = (x1 + square_size) & clip_mask_x;
            int y2 = (y1 + square_size) & clip_mask_y;

            int i1, i2, i3, i4;
            int p1,p2,p3,p4;
            int random_center,random_range;

            if ( which == 0 )
            {
               i1 = color_map [x1][y1];
               i2 = color_map [x2][y1];
               i3 = color_map [x1][y2];
               i4 = color_map [x2][y2];
            }else{
               i1 = alt_map [x1][y1];
               i2 = alt_map [x2][y1];
               i3 = alt_map [x1][y2];
               i4 = alt_map [x2][y2];
            }

            // Obtain new points by averaging the influential points.

            p1 = ((i1 * 9) + (i2 * 3) + (i3 * 3) + (i4)) / 16;
            p2 = ((i1 * 3) + (i2 * 9) + (i3) + (i4 * 3)) / 16;
            p3 = ((i1 * 3) + (i2) + (i3 * 9) + (i4 * 3)) / 16;
            p4 = ((i1) + (i2 * 3) + (i3 * 3) + (i4 * 9)) / 16;

            // Add a random offset to each new point.

            random_center = square_size;
            random_range  = random_center * 2;

            #ifdef _BIZARRE_ROUGHNESS_MAP

               p1 += random (random_center) + random_range - (max_alt - p1)/6;
               p2 += random (random_center) + random_range - (max_alt - p2)/6;
               p3 += random (random_center) + random_range - (max_alt - p3)/6;
               p4 += random (random_center) + random_range - (max_alt - p4)/6;

            #else

               p1 += random (random_range) - random_center;
               p2 += random (random_range) - random_center;
               p3 += random (random_range) - random_center;
               p4 += random (random_range) - random_center;

            #endif

            // Boundary check the altitudes.  Under the normal condition,
            // altitudes that are out of range will be "reflected" back into
            // the allowable range.  Under the bizarre condition, we do
            // something bizarre!

            #ifdef _BIZARRE_ROUGHNESS_MAP

               p1 = (p1 < min_alt) ? max_alt + p1 + 1 : p1;
               p2 = (p2 < min_alt) ? max_alt + p2 + 1 : p2;
               p3 = (p3 < min_alt) ? max_alt + p3 + 1 : p3;
               p4 = (p4 < min_alt) ? max_alt + p4 + 1 : p4;

               p1 = (p1 > max_alt) ? (p1 % (max_alt+1)) : p1;
               p2 = (p2 > max_alt) ? (p2 % (max_alt+1)) : p2;
               p3 = (p3 > max_alt) ? (p3 % (max_alt+1)) : p3;
               p4 = (p4 > max_alt) ? (p4 % (max_alt+1)) : p4;

            #else

               p1 = (p1 < min_alt) ? (min_alt - p1) + min_alt : p1;
               p2 = (p2 < min_alt) ? (min_alt - p2) + min_alt : p2;
               p3 = (p3 < min_alt) ? (min_alt - p3) + min_alt : p3;
               p4 = (p4 < min_alt) ? (min_alt - p4) + min_alt : p4;

               p1 = (p1 > max_alt) ? (max_alt - p1) + max_alt : p1;
               p2 = (p2 > max_alt) ? (max_alt - p2) + max_alt : p2;
               p3 = (p3 > max_alt) ? (max_alt - p3) + max_alt : p3;
               p4 = (p4 > max_alt) ? (max_alt - p4) + max_alt : p4;

            #endif

            // Write out the generated points.

            x2 = (x1 + square_size/2) & clip_mask_x;
            y2 = (y1 + square_size/2) & clip_mask_y;

            if ( which == 0 ){
               alt_map   [x1][y1] = (unsigned char) p1;
               alt_map   [x2][y1] = (unsigned char) p2;
               alt_map   [x1][y2] = (unsigned char) p3;
               alt_map   [x2][y2] = (unsigned char) p4;
            }else {
               color_map [x1][y1] = (unsigned char) p1;
               color_map [x2][y1] = (unsigned char) p2;
               color_map [x1][y2] = (unsigned char) p3;
               color_map [x2][y2] = (unsigned char) p4;
            }

            counter += 100;
         }
         {
           int percent_done = (int) ( counter / 87360 );
           printf ( "\b\b\b\b\b%3d %%", percent_done );
         }
      }

      which = (which == 0) ? 1 : 0;
   }

   if ( which == 0 ){
      memcpy ( temp_map, color_map, sizeof (temp_map) );
   }else{
      memcpy ( temp_map, alt_map, sizeof (temp_map) );
   }

   printf ( "\b\b\b\b\bdone \n" );
}

#endif

//----------------------------------------------------------------------------
// FUNCTION  generate_alt_map
//----------------------------------------------------------------------------

static void  generate_alt_map(void)
{
   int which   = 0;
   int counter = 0;
   int square_size,i,x,y,x1,y1;

   #ifdef _USE_ROUGHNESS_MAP

      generate_roughness_map ();

   #endif

   printf ( "  Altitude map:     0 %%" );
   fflush ( stdout );

   map_init ();


   for ( square_size = 64; square_size > 1; square_size /= 2 ){
      #ifdef _MAKE_CRATERS

         if ( square_size == 8 || square_size == 4 ){
            for ( i = 0; i < map_size_x * 3; ++ i ){
               if ( which == 0 )
                  color_map [random (map_size_x)][random (map_size_y)]
                     = (unsigned char) random (32);
               else
                  alt_map [random (map_size_x)][random (map_size_y)]
                     = (unsigned char) random (32);
            }
         }

      #endif

      // Make pseudo-valley floors.

      if ( square_size == 2 ){
         for (  x = 0; x < map_size_x; ++ x ){
            for (  y = 0; y < map_size_y; ++ y ){
               const cutoff_point = (max_alt+1) / 4;

               if ( alt_map [x][y] < cutoff_point ){
                  alt_map [x][y] = (unsigned char) (cutoff_point - alt_map [x][y]/2);
               }
            }
         }
      }

      for (  x1 = 0; x1 < map_size_x; x1 += square_size ) {
         for (  y1 = 0; y1 < map_size_y; y1 += square_size ){
            // Get the four influential points.

            int x2 = (x1 + square_size) & clip_mask_x;
            int y2 = (y1 + square_size) & clip_mask_y;

            int i1, i2, i3, i4;
            int p1,p2,p3,p4;
            int random_center,random_range;

            if ( which == 0 ){
               i1 = color_map [x1][y1];
               i2 = color_map [x2][y1];
               i3 = color_map [x1][y2];
               i4 = color_map [x2][y2];
            } else {
               i1 = alt_map [x1][y1];
               i2 = alt_map [x2][y1];
               i3 = alt_map [x1][y2];
               i4 = alt_map [x2][y2];
            }

            // Obtain new points by averaging the influential points.

            p1 = ((i1 * 9) + (i2 * 3) + (i3 * 3) + (i4)) / 16;
            p2 = ((i1 * 3) + (i2 * 9) + (i3) + (i4 * 3)) / 16;
            p3 = ((i1 * 3) + (i2) + (i3 * 9) + (i4 * 3)) / 16;
            p4 = ((i1) + (i2 * 3) + (i3 * 3) + (i4 * 9)) / 16;

            // Add a random offset to each new point.

            #ifdef _USE_ROUGHNESS_MAP
               random_center = square_size * temp_map [x1][y1] / 84;
            #else
               random_center = square_size * 2;
            #endif

            random_range = random_center * 2;

            #ifdef _BIZARRE_ALT_MAP

               p1 += ( random ( random_range ) - random_center + p1/4 );
               p2 += ( random ( random_range ) - random_center + p2/4 );
               p3 += ( random ( random_range ) - random_center + p3/4 );
               p4 += ( random ( random_range ) - random_center + p4/4 );

            #else

               p1 += ( random ( random_range ) - random_center );
               p2 += ( random ( random_range ) - random_center );
               p3 += ( random ( random_range ) - random_center );
               p4 += ( random ( random_range ) - random_center );

            #endif

            // Boundary checking

            p1 = (p1 < min_alt) ? (min_alt - p1) + min_alt : p1;
            p2 = (p2 < min_alt) ? (min_alt - p2) + min_alt : p2;
            p3 = (p3 < min_alt) ? (min_alt - p3) + min_alt : p3;
            p4 = (p4 < min_alt) ? (min_alt - p4) + min_alt : p4;

            #ifdef _BIZARRE_ALT_MAP

               p1 = (p1 > max_alt) ? (p1 - (max_alt+1)) : p1;
               p2 = (p2 > max_alt) ? (p2 - (max_alt+1)) : p2;
               p3 = (p3 > max_alt) ? (p3 - (max_alt+1)) : p3;
               p4 = (p4 > max_alt) ? (p4 - (max_alt+1)) : p4;

            #else

               p1 = (p1 > max_alt) ? (max_alt - p1) + max_alt : p1;
               p2 = (p2 > max_alt) ? (max_alt - p2) + max_alt : p2;
               p3 = (p3 > max_alt) ? (max_alt - p3) + max_alt : p3;
               p4 = (p4 > max_alt) ? (max_alt - p4) + max_alt : p4;

            #endif

            // Write out the generated points.

            x2 = (x1 + square_size/2) & clip_mask_x;
            y2 = (y1 + square_size/2) & clip_mask_y;

            if ( which == 0 ){
               alt_map [x1][y1]   = (unsigned char) p1;
               alt_map [x2][y1]   = (unsigned char) p2;
               alt_map [x1][y2]   = (unsigned char) p3;
               alt_map [x2][y2]   = (unsigned char) p4;
            } else{
               color_map [x1][y1] = (unsigned char) p1;
               color_map [x2][y1] = (unsigned char) p2;
               color_map [x1][y2] = (unsigned char) p3;
               color_map [x2][y2] = (unsigned char) p4;
            }

            counter += 100;
         }
         {
           int percent_done = (int) ( counter / 87360 );
           printf ( "\b\b\b\b\b%3d %%", percent_done );
         }
      }

      which = (which == 0) ? 1 : 0;
   }

   if ( which == 0 ){
      memcpy ( alt_map, color_map, map_size_x * map_size_y );
   }

   // Create some man-made features in the landscape.

   flat_area   ( 0,  0,  20, 20,0 );
   quonset_hut ( 0,  20, 24 );

   // We're done!

   printf ( "\b\b\b\b\bdone \n" );
   fflush ( stdout );
}

//----------------------------------------------------------------------------
// FUNCTION  calc_color_map
//----------------------------------------------------------------------------

static void  calc_color_map( void)
{
   int x,  y;
   int color;
   int percent_done;

   int * shadow;

   shadow = (int *) calloc ( map_size_y, sizeof (int) );
   ASSERT ( shadow != NULL );

   printf ( "  Shading map:      0 %%" );
   fflush ( stdout );

   for ( x = 0; x < map_size_x; ++ x ) {
      for ( y = 0; y < map_size_y; ++ y ) {
         int slope = alt_map [x][y] - alt_map [(x-1)&clip_mask_x][y];

         slope *= 2;
         slope += 40;

         if ( slope < 0  ) slope = 0;
         if ( slope > 63 ) slope = 63;

         color = slope + 1;

         color_map [x][y] = (unsigned char) color;
      }

      percent_done = x * 24 / map_size_x;

      printf ( "\b\b\b\b\b%3d %%", percent_done );
   }

   // Cast shadows.

   for ( y = 0; y < map_size_y; ++ y ) {
      shadow [y] = (int) alt_map [0][y];
   }

   for ( x = 1; x < map_size_x; ++ x ) {
      for ( y = 0; y < map_size_y; ++ y )  {
         int height;
         shadow [y] -= 3;
         if ( shadow [y] < 0 )  shadow [y] = 0;

         height = (int) alt_map [x][y];

         if ( height >= shadow [y] ) {
            shadow [y] = height;

            temp_map [x][y] = 0;
         } else {
            temp_map [x][y] = 1;
         }
      }

      percent_done = x * 23 / map_size_x + 24;

      printf ( "\b\b\b\b\b%3d %%", percent_done );
   }

   for ( x = 0; x < 128; ++ x ) {
      for ( y = 0; y < map_size_y; ++ y ) {
         int height;
         shadow [y] -= 3;
         if ( shadow [y] < 0 )  shadow [y] = 0;

         height = (int) alt_map [x][y];

         if ( height >= shadow [y] ){
            shadow [y] = height;

            temp_map [x][y] = 0;
         } else {
            temp_map [x][y] = 1;
         }
      }

      percent_done = x * 6 / map_size_x + 47;

      printf ( "\b\b\b\b\b%3d %%", percent_done );
   }

   for ( x = 0; x < map_size_x; ++ x ){
      for ( y = 0; y < map_size_y; ++ y ){
         if ( temp_map [x][y] ){
            int color = (int) color_map [x][y];
            color -= 18;
            if ( color < 1 )
               color = 1;
            color_map [x][y] = (unsigned char) color;
         }
      }

      percent_done = x * 23 / map_size_x + 53;

      printf ( "\b\b\b\b\b%3d %%", percent_done );
   }

   // Average the map colors to make the map appear smoother.

   for ( x = 0; x < map_size_x; ++ x ){
      for ( y = 0; y < map_size_y; ++ y ){
         color  = 6 * color_map [x][y];
         color += 4 * color_map [(x+1) & clip_mask_x][y];
         color += 4 * color_map [x][(y+1) & clip_mask_y];
         color += 2 * color_map [(x+1) & clip_mask_x][(y+1) & clip_mask_y];

         color /= 16;

         color_map [x][y] = (unsigned char) color;
      }

      percent_done = x * 24 / map_size_x + 76;

      printf ( "\b\b\b\b\b%3d %%", percent_done );
   }

   free (shadow);

   printf ( "\b\b\b\b\bdone \n" );
   fflush ( stdout );
}

//----------------------------------------------------------------------------
// FUNCTION  WORLD_generate
//----------------------------------------------------------------------------

void WORLD_generate (void )
{
   printf ( "\nGenerating the moonscape...\n\n" );

   randomize        ();
   generate_alt_map ();
   calc_color_map   ();
}
