package lpv;

import java.util.Arrays;

import javax.vecmath.Vector4f;

public class LPV {
	
	private Volume[] levels;
	private int[][][] index;
	private int width;
	private int height;
	private int depth;
	private int level;

	public LPV(int w, int h, int d) {
		
		this.width = w;
		this.height = h;
		this.depth = d;
		
		int min = Math.min(w, Math.min(h, d));
		int n = Integer.numberOfTrailingZeros(Integer.highestOneBit(min)) + 1;
		
		this.index = new int[d][h][w];
		for(int i = 0; i < d; i++) {
			for(int j = 0; j < h; j++) {
				Arrays.fill(index[i][j], n-1);
			}
		}
		this.levels = new Volume[n];
		
		for(int i = 0; i < n; i++) {
			levels[i] = new Volume(w, h, d);
			
			w >>= 1;
			h >>= 1;
			d >>= 1;
		}
		
		level = 0;
	}
	
	public Vector4f get(int level, int x, int y, int z) {
		return levels[level].get(x, y, z);
	}
	
	public Vector4f get(int x, int y, int z) {
		return levels[level].get(x, y, z);
	}
	
	public void setLevel(int level) {
		this.level = level;
	}
	public int getLevel() {
		return level;
	}
	public int getLevels() {
		return levels.length;
	}
	
	public void downsample() {
		for(int l = 1; l < levels.length; l++) {
			Volume to = levels[l];
			for(int i = 0; i < to.getDepth(); i++) {
				for(int j = 0; j < to.getHeight(); j++) {
					for(int k = 0; k < to.getWidth(); k++) {
						to.get(k, j, i).set(downsample(l, i, j, k));
					}
				}
			}
		}
	}

	private Vector4f downsample(int l, int i, int j, int k) {
		Volume from = levels[l-1];
		Vector4f v = new Vector4f();
		for(int m = 0; m < 2; m++) {
			for(int n = 0; n < 2; n++) {
				for(int o = 0; o < 2; o++) {
					v.add(from.get(k*2 + o, j*2 + n, i*2 + m));
				}
			}
		}
		v.scale(1.0f/8.0f);
		return v;
	}
	
	public void clear() {
		for(int i = 0; i < depth; i++) {
			for(int j = 0; j < height; j++) {
				Arrays.fill(index[i][j], levels.length-1);
			}
		}
		for(Volume v : levels) {
			v.clear();
		}
	}

	public int getWidth(int level) {
		return levels[level].getWidth();
	}

	public int getHeight(int level) {
		return levels[level].getHeight();
	}

	public int getDepth(int level) {
		return levels[level].getDepth();
	}
	
	public int getWidth() {
		return levels[level].getWidth();
	}

	public int getHeight() {
		return levels[level].getHeight();
	}

	public int getDepth() {
		return levels[level].getDepth();
	}
	
	public Vector4f getMax() {
		Vector4f max = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f);
		float maxLenSq = 0.0f;
		for(int i = 0; i < depth; i++) {
			for(int j = 0; j < height; j++) {
				for(int k = 0; k < width; k++) {
					Vector4f v = get(0, k, j, i);
					float tmpLenSq = v.x * v.x + v.y * v.y;
					if(tmpLenSq > maxLenSq) {
						maxLenSq = tmpLenSq;
						max.set(v);
					}
				}
			}
		}
		return max;
	}
	
	public ErrorMetric getError() {
		ErrorMetric error = new ErrorMetric();
		
		int n = 0;
		for(int l = 1; l < levels.length; l++) {
			Volume to = levels[l];
			for(int i = 0; i < to.getDepth(); i++) {
				for(int j = 0; j < to.getHeight(); j++) {
					for(int k = 0; k < to.getWidth(); k++) {
						Vector4f approx = to.get(k, j, i);
						Vector4f real = downsample(l, i, j, k);
						float abs = SH.absoluteError(approx, real);
						float rel = SH.relativeError(approx, real);
						
						error.averageAbsoluteError += abs;
						error.averageRelativeError += rel;
						
						error.maxAbsoluteError = Math.max(error.maxAbsoluteError, abs);
						error.maxRelativeError = Math.max(error.maxRelativeError, rel);
						
						error.cumulativeAbsoluteError += abs;
						
						n++;
					}
				}
			}
		}
		
		error.averageAbsoluteError /= n;
		error.averageRelativeError /= n;
		
		return error;
	}
	
}
