// see http://www.openprocessing.org/sketch/30809
// http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation/
// https://github.com/amitp/mapgen2

package csa.tiletools.worldgen.mapgen;

import csa.tiletools.worldgen.mapgen.miny.render.Line;
import csa.tiletools.worldgen.mapgen.miny.render.MultiPoints;
import csa.tiletools.worldgen.mapgen.miny.render.PolyLine;
import csa.tiletools.worldgen.mapgen.miny.render.Polygon;
import csa.tiletools.worldgen.mapgen.miny.render.Renderer;
import csa.tiletools.worldgen.mapgen.processing.CSAProcessing;
import csa.tiletools.worldgen.mapgen.processing.CSAVector;
import java.awt.image.BufferedImage;
import java.util.ArrayList;

public class MapGen
{
    public static MapGen MG = new MapGen();
    public static void reset()
    {
        MG = new MapGen();
    }

    private MapGen() { }

    public Map map;
    public Renderer renderer;

    boolean showSteps = false;
    public boolean createNoisyEdges = true;
    boolean customRender = true;//true;
    int showData = 0; // 0 : default, 1 : elevation, 2 : moisture
    public long seed = 26004;


    public int width=600;
    public int height=600;
    public int pointsToGenerate=2000;
    public boolean coastline = true;
    public boolean smoothColors = true;

    public float landMassFractionMin = (float) 0.3;  // [0;1] fraction of points required to be land
    public float landMassFractionMax = (float) 0.7;
    public float riversFraction = (float) 0.15;  // [0;1] influence the number of rivers
    public float lakeThreshold = (float) 0.3; // [0;1] fraction of water points required for a face to become a lake

    public boolean doGenerate()
    {
      map = new Map(width, height);
      map.nbPoints = pointsToGenerate;

      map.createNoisyEdges = createNoisyEdges;
      map.landMassFractionMin = landMassFractionMin;  // [0;1] fraction of points required to be land
      map.landMassFractionMax = landMassFractionMax;
      map.riversFraction = riversFraction;  // [0;1] influence the number of rivers
      map.lakeThreshold = lakeThreshold; // [0;1] fraction of water points required for a face to become a lake

      map.createMap(seed);
      map.fullCreation();
      return map.ok;
        // BUG with 17504 (for example)
    }
    public long getSeed()
    {
        return map.mapSeed;
    }


    void drawPathForwards(ArrayList<CSAVector> path, MultiPoints shape)
    {
      for(int i=0; i<path.size(); i++) {
            shape.addPoint(path.get(i).x, path.get(i).y);
        }
    }

    void drawPathBackwards(ArrayList<CSAVector> path, MultiPoints shape)
    {
      for(int i=path.size()-1; i>+0; i--) {
            shape.addPoint(path.get(i).x, path.get(i).y);
        }
    }

    void drawEdge(Map.Edge e, Map.Face f, MultiPoints shape)
    {
      shape.addPoint(f.pos.x, f.pos.y);
      if(e.f1 == f)
      {
        shape.addPoint(e.p1.pos.x, e.p1.pos.y);
        if(e.noisy1 != null && e.noisy2 != null) {
          drawPathForwards(e.noisy1, shape);
          drawPathBackwards(e.noisy2, shape);
        }
        shape.addPoint(e.p2.pos.x, e.p2.pos.y);
      } else { // reverse order
        shape.addPoint(e.p2.pos.x, e.p2.pos.y);
        if(e.noisy1 != null && e.noisy2 != null) {
          drawPathForwards(e.noisy2, shape);
          drawPathBackwards(e.noisy1, shape);
        }
        shape.addPoint(e.p1.pos.x, e.p1.pos.y);
      }
    }

    void renderMap()
    {
        if (map.facesList == null) return;
        renderer = new Renderer(width, height);
        renderer.setBackground(map.biomesColor[1]);
        for(Map.Face f : map.facesList) {
          for(Map.Edge e : f.edges) {
            if(smoothColors && !f.water && !e.coast && !e.shore) {
              BiomeTriangle tri = new BiomeTriangle(1, f.pos, e.p1.pos, e.p2.pos,
                f.elevation, e.p1.elevation, e.p2.elevation,
                f.moisture, e.p1.moisture, e.p2.moisture,
                f.water, f.ocean, f.coast, e.p1.coast, e.p2.coast,
                (float)0.3, (float)0.1);  // noiseDist, noiseScale
              renderer.add(tri);
            } else {
              Polygon poly = new Polygon(1, map.biomesColor[f.biome]);
              drawEdge(e, f, poly);

              renderer.add(poly);
            }
          }
        }

        if (coastline)
        {
            // first only the coasts
            for(Map.Edge e : map.edgesList) {
              if(e.f2 == null || !e.coast)
                continue;

              if(e.noisy1 != null && e.noisy2 != null) {
                PolyLine line = new PolyLine(2, 1, map.colorCoast, false);
                drawPathForwards(e.noisy1, line);
                drawPathBackwards(e.noisy2, line);
                renderer.add(line);
              } else
                renderer.add(new Line(e.p1.pos.x, e.p1.pos.y, e.p2.pos.x, e.p2.pos.y, 2, 1, map.colorCoast));
            }
        }


      // then all the rest
      for(Map.Edge e : map.edgesList) {
        if(e.f2 == null) // border
          continue;
        PolyLine line;
        if(e.coast)
          continue;
        if((e.shore)&&(!coastline)) // csa
           continue;
        else if(e.shore && e.f1.biome != 3 && e.f2.biome != 3)
          line = new PolyLine(1, 1, map.colorRiver, false);
        else if(e.f1.water || e.f2.water)  // inside a lake
          continue;
        else if(e.river > 0)
          line = new PolyLine(CSAProcessing.sqrt(e.river), 1, map.colorRiver, false);
        else
         continue;

        line.addPoint(e.p1.pos.x, e.p1.pos.y);
        if(e.noisy1 != null && e.noisy2 != null) {
          drawPathForwards(e.noisy1, line);
          drawPathBackwards(e.noisy2, line);
        }
        line.addPoint(e.p2.pos.x, e.p2.pos.y);
        renderer.add(line);
      }

      NoiseImage ni = new NoiseImage(CSAProcessing.floor(CSAProcessing.random(1000)), 128-5, 128+5);
      ni.blendMode = CSAProcessing.HARD_LIGHT;
      renderer.add(ni);


    }

    // only custom renderer
    public BufferedImage getMapImage()
    {
      if(map.created)
      {
        if(renderer == null)
          renderMap();
        renderer.renderAll();

        return (BufferedImage)renderer.getImage();
      }
      return null;
    }

    public static int getBiome(float elevation, float moisture, boolean water, boolean ocean, boolean coast)
    {
      if(ocean) {
        return 1;  // Ocean
      } else if(water) {
        if (elevation < 0.1) return 2;  // Marsh
        if (elevation > 0.8) return 3;  // Ice
        return 4;  // Lake
      } else if(coast) {
        return 5;  // Beach
      } else if(elevation > 0.8) {
        if(moisture > 0.50) return 6;  // Snow
        else if(moisture > 0.33) return 7;  // Tundra
        else if(moisture > 0.16) return 8;  // Bare
        else return 9;  // Scorched
      } else if(elevation > 0.6) {
        if(moisture > 0.66) return 10;  // Taiga
        else if(moisture > 0.33) return 11;  // Shrubland
        else return 12;  // Temperate desert
      } else if(elevation > 0.3) {
        if(moisture > 0.83) return 13;  // Temperate rain forest
        else if(moisture > 0.50) return 14;  // Temperate deciduous forest
        else if(moisture > 0.16) return 15;  // Grassland
        else return 12;  // Temperate desert
      } else
      {
        if(moisture > 0.66) return 16;  // Tropical rain forest
        else if(moisture > 0.33) return 17;  // Tropical seasonal forest
        else if(moisture > 0.16) return 15;  // Grassland
        else return 18;  // Subtropical desert
      }
    }

}
