package page.image;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.Semaphore;

import org.wikiwebserver.handler.http.FormData;
import org.wikiwebserver.handler.http.HTTPException;
import org.wikiwebserver.handler.http.HTTPHandler;
import org.wikiwebserver.handler.http.interfaces.*;

public class Plasma implements HTTPResponder {

    private static final int CONCURRENCY_LEVEL = 4;
    private static final Semaphore cpuTime = new Semaphore(CONCURRENCY_LEVEL, true);    
    
    private static Random random = new Random();
    private int width = 500;
    private int height = 500;
    
    public Object respond(HTTPHandler conn) throws IOException {  
        return respond(conn.getRequest().getFormData());
    }
    
    public Object respond(FormData formData) throws IOException {
        
        if (formData != null) {
            width = (int) getDouble("w", width, formData); 
            height = (int) getDouble("h", height, formData);
            random.setSeed((long) getDouble("s", random.nextLong(), formData));
        }

        return plasma(width, height);
    }
    
    public static BufferedImage plasma(int width, int height) throws HTTPException {
        
        try {
            cpuTime.acquire();        
            if (width > 2000) width = 2000;
            if (height > 2000) height = 2000;           
            
            BufferedImage i = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            
            double c1, c2, c3, c4;
               
            c1 = random.nextDouble();
            c2 = random.nextDouble();
            c3 = random.nextDouble();
            c4 = random.nextDouble();
                    
            divideGrid(i, 0, 0, width, height, c1, c2, c3, c4, width, height);
            
            return i;
            
        } catch (InterruptedException ex) {
            throw new HTTPException(500, "Image generation interrupted.", ex);
        } finally {
            cpuTime.release();
        }            
    }
     
    private static void divideGrid(BufferedImage i, double x, double y, double width, double height, 
                                                    double c1, double c2, double c3, double c4,
                                                    int fullWidth, int fullHeight) {
        double edge1, edge2, edge3, edge4, middle;
        double newWidth = width / 2;
        double newHeight = height / 2;

        if (width > 1 || height > 1)  {   
            middle = (c1 + c2 + c3 + c4) / 4 
                   + displace(newWidth + newHeight, fullWidth, fullHeight);
            edge1 = (c1 + c2) / 2;
            edge2 = (c2 + c3) / 2;
            edge3 = (c3 + c4) / 2;
            edge4 = (c4 + c1) / 2;
            
            if (middle < 0) middle = 0;
            else if (middle > 1.0f) middle = 1.0f;
            
            divideGrid(i, x, y, newWidth, newHeight, c1, edge1, middle, edge4, fullWidth, fullHeight);
            divideGrid(i, x + newWidth, y, newWidth, newHeight, edge1, c2, edge2, middle, fullWidth, fullHeight);
            divideGrid(i, x + newWidth, y + newHeight, newWidth, newHeight, middle, edge2, c3, edge3, fullWidth, fullHeight);
            divideGrid(i, x, y + newHeight, newWidth, newHeight, edge4, middle, edge3, c4, fullWidth, fullHeight);
        }
        else  {
            double c = (c1 + c2 + c3 + c4) / 4;
            i.setRGB((int)x, (int)y, computeColor((float)c).getRGB());
        }
    }
    
    
    private static double displace(double num, int fullWidth, int fullHeight) {
        double max = num / (double)(fullWidth + fullHeight) * 3;
        return (random.nextDouble() - 0.5f) * max;
    }

    private static Color computeColor(float c) {       
        float r = 0, g = 0, b = 0;
        
        if (c < 0.5f) r = c * 2;
        else r = (1.0f - c) * 2;
        
        if (c >= 0.3f && c < 0.8f) g = (c - 0.3f) * 2;
        else if (c < 0.3f) g = (0.3f - c) * 2;
        else g = (1.3f - c) * 2;
        
        if (c >= 0.5f) b = (c - 0.5f) * 2;
        else b = (0.5f - c) * 2;
        
        return new Color(r, g, b);
    }    

    
    private static double getDouble(String name, double def, FormData formData) {
        try {
            return Double.parseDouble((String)formData.getFirst(name));
        } catch (Exception ex) {
            return def;
        }
    }    
}
