package org.fleen.forsythia.app.strobe;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Path2D;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Random;

import org.fleen.forsythia.core.composition.FPolygon;
import org.fleen.forsythia.core.composition.FPolygonSignature;
import org.fleen.forsythia.core.composition.ForsythiaComposition;
import org.fleen.geom_2D.DPoint;
import org.fleen.geom_2D.DPolygon;
import org.fleen.geom_2D.GD;
import org.fleen.util.tree.TreeNode;

public class R0{

  /*
   * ################################
   * CONSTRUCTOR
   * ################################
   */
  
  R0(Strobe strobe){
    this.strobe=strobe;}
  
  /*
   * ################################
   * STROBE
   * ################################
   */
  
  Strobe strobe;
  
  /*
   * ################################
   * IMAGE
   * ################################
   */
  
  BufferedImage image;
  Graphics2D graphics;
  
  //RENDERING HINTS
  public static final HashMap<RenderingHints.Key,Object> RENDERING_HINTS=
    new HashMap<RenderingHints.Key,Object>();
  static{
    RENDERING_HINTS.put(
      RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
    RENDERING_HINTS.put(
      RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
    RENDERING_HINTS.put(
      RenderingHints.KEY_DITHERING,RenderingHints.VALUE_DITHER_DEFAULT);
    RENDERING_HINTS.put(
      RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BICUBIC);
    RENDERING_HINTS.put(
      RenderingHints.KEY_ALPHA_INTERPOLATION,RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
    RENDERING_HINTS.put(
      RenderingHints.KEY_COLOR_RENDERING,RenderingHints.VALUE_COLOR_RENDER_QUALITY); 
    RENDERING_HINTS.put(
      RenderingHints.KEY_STROKE_CONTROL,RenderingHints.VALUE_STROKE_NORMALIZE);}
  
  public BufferedImage getImage(){
    OrderedPolygons orderedvisiblepolygons=new OrderedPolygons(strobe.visiblepolygons);
    //render visible polygons
    double
      visiblepolygondetailsizefloor=strobe.getVisiblePolygonDetailSizeFloor(),
      alpha255detailfloor=visiblepolygondetailsizefloor*2;
    ViewportDef viewportdef=strobe.getViewportDef();
    initGraphics(viewportdef);
    fillVisiblePolygons(viewportdef,graphics,orderedvisiblepolygons,visiblepolygondetailsizefloor,alpha255detailfloor);
    strokeVisiblePolygons(viewportdef,graphics,orderedvisiblepolygons,visiblepolygondetailsizefloor,alpha255detailfloor);
    return image;}
  
  private void initGraphics(ViewportDef viewportdef){
    image=new BufferedImage(viewportdef.width,viewportdef.height,BufferedImage.TYPE_INT_ARGB);
    graphics=image.createGraphics();
    graphics.setRenderingHints(RENDERING_HINTS);
    graphics.setPaint(new Color(255,255,255));
    graphics.fillRect(0,0,viewportdef.width,viewportdef.height);
    graphics.setTransform(viewportdef.getTransform());}
  
  private void fillVisiblePolygons(ViewportDef viewportdef,Graphics2D graphics,OrderedPolygons visiblepolygons,double visibledetailfloor,double alpha255detailfloor){
    double detailsize,zz;
    int alpha;
    Path2D path;
    Color fillcolor;
    //fill polygons
    for(List<FPolygon> a:visiblepolygons.getPolygonLists()){
      for(FPolygon polygon:a){
        //set alpha according to detail size relative to visibledetailfloor and alpha255detailfloor
        detailsize=polygon.getDetailSize();
        if(detailsize>alpha255detailfloor){
          alpha=255;
        }else if(detailsize<visibledetailfloor){
          alpha=0;
        }else{
          zz=(detailsize-visibledetailfloor)/(alpha255detailfloor-visibledetailfloor);
          alpha=(int)(zz*255);}
        path=polygon.getDPolygon().getPath2D();
        //get basic color for polygon
        fillcolor=getColor(polygon,frameindex);
        //apply alpha
        fillcolor=new Color(fillcolor.getRed(),fillcolor.getGreen(),fillcolor.getBlue(),alpha);
        graphics.setPaint(fillcolor);
        graphics.fill(path);}}}
  
  private void strokeVisiblePolygons(ViewportDef viewportdef,Graphics2D graphics,OrderedPolygons visiblepolygons,double visibledetailfloor,double alpha255detailfloor){
    //stroke visible polygons
    double detailsize,zz;
    int alpha;
    Path2D path;
    BasicStroke stroke=createStroke(viewportdef,1.5);
    graphics.setStroke(stroke);
    for(List<FPolygon> a:visiblepolygons.getPolygonLists()){
      for(FPolygon polygon:a){
        //set alpha according to detail size relative to visibledetailfloor and alpha255detailfloor
        detailsize=polygon.getDetailSize();
        if(detailsize>alpha255detailfloor){
          alpha=255;
        }else if(detailsize<visibledetailfloor){
          alpha=0;
        }else{
          zz=(detailsize-visibledetailfloor)/(alpha255detailfloor-visibledetailfloor);
          alpha=(int)(zz*255);}
        path=polygon.getDPolygon().getPath2D();
        graphics.setPaint(new Color(0,0,0,alpha));//black
        graphics.draw(path);}}}
  
  private BasicStroke createStroke(ViewportDef viewportdef,double size){
    BasicStroke s=new BasicStroke((float)(size*viewportdef.scale),BasicStroke.CAP_SQUARE,BasicStroke.JOIN_ROUND,0,null,0);
    return s;}
  
  /*
   * ################################
   * FORSYTHIA COMPOSITION
   * ################################
   */
  
  ForsythiaComposition composition;
  
  /*
   * ################################
   * VIEWPORT POLYGON
   * ################################
   */
  
  private DPolygon getViewportPolygon(ViewportDef viewportdef){
    
    double 
      scaledwidth=viewportdef.width*viewportdef.scale,
      scaledheight=viewportdef.height*viewportdef.scale,
      vpforward=viewportdef.forward;
    double[] 
      pcenternorth=GD.getPoint_PointDirectionInterval(viewportdef.center.x,viewportdef.center.y,vpforward,scaledheight/2),
      pnw=GD.getPoint_PointDirectionInterval(
        pcenternorth[0],pcenternorth[1],
        GD.normalizeDirection(vpforward-GD.PI/2),
        scaledwidth/2),
      pne=GD.getPoint_PointDirectionInterval(
        pnw[0],pnw[1],
        GD.normalizeDirection(vpforward+GD.PI/2),
        scaledwidth),
      pse=GD.getPoint_PointDirectionInterval(
        pne[0],pne[1],
        GD.normalizeDirection(vpforward+GD.PI),
        scaledheight),
      psw=GD.getPoint_PointDirectionInterval(
        pse[0],pse[1],
        GD.normalizeDirection(vpforward-GD.PI/2),
        scaledwidth);
    DPolygon vp=new DPolygon(
      new DPoint(pnw),
      new DPoint(pne),
      new DPoint(pse),
      new DPoint(psw));
    return vp;}
  
  /*
   * ################################
   * POLYGON COLOR
   * ################################
   */
  
  Random rnd=new Random();
  
  //SPLIT PALETTE
  
  ////that good early palette, use black stroke
  //private Color[] 
  //  color0={new Color(236,208,120),new Color(217,91,67)},
  //  color1={new Color(192,41,66),new Color(83,119,122)};
  
  //red orange yellow white. use black stroke
  //private Color[] 
  //    color0={new Color(255,0,0),new Color(255,255,0)},
  //    color1={new Color(255,128,0),new Color(255,255,255)};
  
  //red blue, yellow white. use black stroke
  private Color[] 
    color0={new Color(255,128,128),new Color(128,128,255)},
    color1={new Color(255,255,128),new Color(255,255,255)};
  
  //straight black and white. use gray stroke
  //private Color[] 
  //    color0={new Color(0,0,0)},
  //    color1={new Color(255,255,255)};
  
  
  public Color getColor(FPolygon polygon,int frameindex){
    BlinkPattern bp=getBlinkPattern(polygon);
    Color c=bp.getColor(frameindex);
    return c;}
  
  Map<FPolygonSignature,BlinkPattern> blinkpatternbysig=new Hashtable<FPolygonSignature,BlinkPattern>();
  
  /*
   * chorus by sig
   * color pair by tag depth
   */
  private BlinkPattern getBlinkPattern(FPolygon polygon){
    BlinkPattern blinkpattern=blinkpatternbysig.get(polygon);
    if(blinkpattern==null){
      blinkpattern=createBlinkPattern(polygon);
      blinkpatternbysig.put(polygon.getSignature(),blinkpattern);}
    return blinkpattern;}
  
  private BlinkPattern createBlinkPattern(FPolygon polygon){
    int eggdepth=getTagDepth(polygon,"egg");
    Color[] colors;
  if(eggdepth%2==0)
    colors=color0;
  else
    colors=color1;
  BlinkPattern bp=new BlinkPattern(colors[0],colors[1]);
  return bp;}
  
  private int getTagDepth(TreeNode node,String tag){
    int c=0;
    TreeNode n=node;
    FPolygon p;
    while(n!=null){
      if(n instanceof FPolygon){
        p=(FPolygon)n;
        if(p.hasTag(tag))
          c++;}
      n=n.getParent();}
    return c;}
  
  
}