package com.elphel.imagej.tileprocessor;
/**
 ** TileCluster - clusters from disparity map to generate textured mesh
 **
 ** Copyright (C) 2017 Elphel, Inc.
 **
 ** -----------------------------------------------------------------------------**
 **
 **  TileCluster.java is free software: you can redistribute it and/or modify
 **  it under the terms of the GNU General Public License as published by
 **  the Free Software Foundation, either version 3 of the License, or
 **  (at your option) any later version.
 **
 **  This program is distributed in the hope that it will be useful,
 **  but WITHOUT ANY WARRANTY; without even the implied warranty of
 **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 **  GNU General Public License for more details.
 **
 **  You should have received a copy of the GNU General Public License
 **  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ** -----------------------------------------------------------------------------**
 **
 */

import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Arrays;

class TileCluster{
	Rectangle  bounds;
	boolean [] border;
	// <0 - outside, 0 - inner /true disparity, border_int_max - outer border layer, ... 
	int []     border_int;           // will replace border? Provide on-the-fly? 
	int        border_int_max;       // outer border value
	int []     stitch_stitched;      // +1 - stitch, +2 - stitched
	int []     no_connect;           // bit mask of prohibited directions
	int []     class_conn;            // 1 - disconnected from, 2 - disconnected (not only for stitch)

	
	double []  disparity;            // all and only unused - NaN 
	int []     cluster_index = null; // for debug purposes, index of the source cluster
	int        index = -1;
	boolean    is_sky = false;
	ArrayList<IndexedRectanle> clust_list;
	class IndexedRectanle{
		int index;
		Rectangle bounds;
		boolean is_sky;
		IndexedRectanle (
				int index,
				Rectangle bounds,
				boolean is_sky){
			this.index = index;
			this.bounds = bounds;
			this.is_sky = is_sky;
		}
	}
	// to use cluster_index - set index >= 0, <0 - do not use.
	public TileCluster (
			Rectangle  bounds,
			int index, // <0 to skip
			int []     border_int,           // will replace border? Provide on-the-fly? 
			int        border_int_max,       // outer border value
			int []     stitch_stitched,      // +1 - stitch, +2 - stitched
			int []     no_connect,           // bit mask of prohibited directions
			double []  disparity,
			int []     class_conn,           // 1 - disconnected from, 2 - disconnected (not only for stitch)
			boolean is_sky){
		this.bounds =    bounds;
		this.index = index;
		this.is_sky = is_sky;
		if (disparity == null) {
			disparity = new double[bounds.width * bounds.height];
			Arrays.fill(disparity, Double.NaN);
		}
		this.disparity = disparity;

//		boolean [] 
		border = new boolean[bounds.width * bounds.height];
		if (border_int != null) {
			for (int i = 0; i < border_int.length; i++) {
				border[i] = border_int[i] == border_int_max;
			}
		}
		// this.border =    border;
		// for back compatibility
		if (border_int == null) {
			border_int = new int [bounds.width * bounds.height];
			border_int_max = 1; 

			for (int i = 0; i < border_int.length; i++) {
				if (Double.isNaN(disparity[i])) {
					border_int[i] = -1;
				} else {
					border_int[i] = border[i] ? border_int_max : 0; 
				}
			}
		}
		
		this.border_int = border_int;
		this.border_int_max = border_int_max;
		if (stitch_stitched == null) {
			stitch_stitched = new int [bounds.width * bounds.height];
		}
		this.stitch_stitched = stitch_stitched;
		
		if (no_connect == null) {
			no_connect =  new int [bounds.width * bounds.height];
		}
		this.no_connect = no_connect;

		if (class_conn == null) {
			class_conn =  new int [bounds.width * bounds.height];
		}
		this.class_conn = class_conn;
	}
	
	public boolean isSky() {
		return is_sky;
	}
	public boolean isSky(int sub) {
		if (clust_list == null) {
			return is_sky;
		}
		return clust_list.get(sub).is_sky;
	}

	public int getSkyClusterIndex() { // currently (need to fix) there can be multiple sky clusters !
		if (clust_list == null) {
			return -2;
		}
		for (int i = 0; i < clust_list.size(); i++) {
			if (clust_list.get(i).is_sky) {
				return i;
			}
		}
		return -1;
	}
	
	public Rectangle  getBounds() {
		return bounds;
	}
	public Rectangle  getBounds(int gap) {
		return new Rectangle (bounds.x - gap, bounds.y - gap, bounds.width + 2* gap,  bounds.height + 2* gap);
	}
	public boolean [] getBorder() {return border;} // Modify to use border_int (==border_int_max)?
	public int []     getBorderInt() {return border_int;}
	public int []     getStitchStitched() {return stitch_stitched;}
	public int []     getNoConnect()      {return no_connect;}
	public int []     getClassConn()      {return class_conn;}
	public int        getBorderIntMax() {return border_int_max;}
	public double []  getDisparity() {return disparity;}
	public void       setDisparity(double [] disparity) {this.disparity = disparity;}
	public double []  getSubDisparity(int indx) { // disparity should be NaN for unused !
		if (clust_list == null) {
			return null;
		}
		Rectangle sub_bounds = clust_list.get(indx).bounds;
		double [] sub_disparity = new double [sub_bounds.width * sub_bounds.height];
		int src_x = sub_bounds.x - bounds.x;
		for (int dst_y = 0; dst_y < sub_bounds.height; dst_y++) {
			int src_y = dst_y + sub_bounds.y - bounds.y;
			System.arraycopy(
					disparity,
					src_y * bounds.width + src_x,
					sub_disparity,
					dst_y * sub_bounds.width,
					sub_bounds.width);
		}		
		return sub_disparity;
	}

	public void setSubDisparity(int indx, double [] sub_disparity) { // disparity should be NaN for unused !
		if (clust_list == null) {
			return;
		}
		Rectangle sub_bounds = clust_list.get(indx).bounds;
		int src_x = sub_bounds.x - bounds.x;
		for (int dst_y = 0; dst_y < sub_bounds.height; dst_y++) {
			int src_y = dst_y + sub_bounds.y - bounds.y;
			System.arraycopy(
					sub_disparity,
					dst_y * sub_bounds.width,
					disparity,
					src_y * bounds.width + src_x,
					sub_bounds.width);
		}		
	}
	
	
	
	public boolean isSubSky(int indx) {
		if (clust_list == null) {
			return false;
		}
		return clust_list.get(indx).is_sky;
	}
	
	public boolean []  getSubBorder(int indx) {
		if (clust_list == null) {
			return null;
		}
		Rectangle sub_bounds = clust_list.get(indx).bounds;
		boolean [] sub_border = new boolean [sub_bounds.width * sub_bounds.height];
		int src_x = sub_bounds.x - bounds.x;
		for (int dst_y = 0; dst_y < sub_bounds.height; dst_y++) {
			int src_y = dst_y + sub_bounds.y - bounds.y;
			System.arraycopy(
					border,
					src_y * bounds.width + src_x,
					sub_border,
					dst_y * sub_bounds.width,
					sub_bounds.width);
		}		
		return sub_border;
	}

	public int []  getSubBorderInt(int indx) {
		if (clust_list == null) {
			return null;
		}
		Rectangle sub_bounds = clust_list.get(indx).bounds;
		int [] sub_border_int = new int [sub_bounds.width * sub_bounds.height];
		int src_x = sub_bounds.x - bounds.x;
		for (int dst_y = 0; dst_y < sub_bounds.height; dst_y++) {
			int src_y = dst_y + sub_bounds.y - bounds.y;
			System.arraycopy(
					border_int,
					src_y * bounds.width + src_x,
					sub_border_int,
					dst_y * sub_bounds.width,
					sub_bounds.width);
		}		
		return sub_border_int;
	}

	public int []  getSubStitchStitched(int indx) {
		if (clust_list == null) {
			return null;
		}
		Rectangle sub_bounds = clust_list.get(indx).bounds;
		int [] sub_stitch_stitched = new int [sub_bounds.width * sub_bounds.height];
		int src_x = sub_bounds.x - bounds.x;
		for (int dst_y = 0; dst_y < sub_bounds.height; dst_y++) {
			int src_y = dst_y + sub_bounds.y - bounds.y;
			System.arraycopy(
					stitch_stitched,
					src_y * bounds.width + src_x,
					sub_stitch_stitched,
					dst_y * sub_bounds.width,
					sub_bounds.width);
		}		
		return sub_stitch_stitched;
	}

	public int []  getSubNoConnect(int indx) {
		if (clust_list == null) {
			return null;
		}
		Rectangle sub_bounds = clust_list.get(indx).bounds;
		int [] sub_no_connect = new int [sub_bounds.width * sub_bounds.height];
		int src_x = sub_bounds.x - bounds.x;
		for (int dst_y = 0; dst_y < sub_bounds.height; dst_y++) {
			int src_y = dst_y + sub_bounds.y - bounds.y;
			System.arraycopy(
					no_connect,
					src_y * bounds.width + src_x,
					sub_no_connect,
					dst_y * sub_bounds.width,
					sub_bounds.width);
		}		
		return sub_no_connect;
	}
	
	public int []  getSubClassConn(int indx) {
		if (clust_list == null) {
			return null;
		}
		Rectangle sub_bounds = clust_list.get(indx).bounds;
		int [] sub_class_conn = new int [sub_bounds.width * sub_bounds.height];
		int src_x = sub_bounds.x - bounds.x;
		for (int dst_y = 0; dst_y < sub_bounds.height; dst_y++) {
			int src_y = dst_y + sub_bounds.y - bounds.y;
			System.arraycopy(
					class_conn,
					src_y * bounds.width + src_x,
					sub_class_conn,
					dst_y * sub_bounds.width,
					sub_bounds.width);
		}		
		return sub_class_conn;
	}
	
	
	
	// returns selected for all non-NAN, so it is possible to use NEGATIVE_INFINITY for non-NaN
	public boolean []  getSubSelected(int indx) { // disparity should be NaN for unused !
		if (clust_list == null) {
			return null;
		}
		double [] sub_disparity = getSubDisparity(indx);
		boolean [] sub_selection = new boolean [sub_disparity.length];
		for (int i = 0; i < sub_disparity.length; i++) {
			sub_selection[i] = !Double.isNaN(sub_disparity[i]);
		}
		return sub_selection;
	}
	
	public boolean [] getSelected() {
		if (disparity == null) {
			return null;
		}
		boolean [] selected = new boolean [disparity.length];
		for (int i = 0; i < selected.length; i++) {
			selected[i] = !Double.isNaN(disparity[i]);
		}
		return selected;
	}
	
	public Rectangle getSubBounds (int indx) {
		if (clust_list == null) {
			return null;
		} else {
			Rectangle sub_bounds = clust_list.get(indx).bounds;
			return sub_bounds;
		}
	}
	
	public Rectangle [] getSubBounds() {
		if (clust_list == null) {
			return null;
		} else {
			Rectangle [] sub_bounds = new Rectangle[clust_list.size()];
			for (int i = 0; i <clust_list.size(); i++) {
				sub_bounds[i] = clust_list.get(i).bounds;
			}
			return sub_bounds;
		}
	}
	public int [] getSubIndices() {
		if (clust_list == null) {
			return null;
		} else {
			int [] sub_indices = new int[clust_list.size()];
			for (int i = 0; i <clust_list.size(); i++) {
				sub_indices[i] = clust_list.get(i).index;
			}
			return sub_indices;
		}
	}
	public void resetClusterIndex() { // to rebuild cluster index from disparity  NOT USED
		this.cluster_index = null; 
	}
	public int [] getClusterIndex() { // (Now not) just a debug feature, no need to optimize?
		if (clust_list == null) {
			return null;
		} else if (cluster_index == null){
			cluster_index = new int [bounds.width * bounds.height];
			Arrays.fill(cluster_index, -1);
			for (IndexedRectanle ir : clust_list) {
				for (int y_src = 0; y_src < ir.bounds.height; y_src++) {
					int y = y_src + ir.bounds.y - bounds.y;
					int offs = y * bounds.width + ir.bounds.x - bounds.x;
					for (int x_src = 0; x_src < ir.bounds.width; x_src++) {
						if (!Double.isNaN(disparity[offs])) {
							cluster_index[offs] = ir.index;
						}
						offs++;
					}
				}
			}
		}
		return cluster_index;
	}
	
	public void increaseBounds() {
		int num_subs = getSubBounds().length;
		for (int indx = 0; indx < num_subs; indx++) {
			increaseBounds(indx);
		}
	}

	public void increaseBounds(int sub_index) {
		Rectangle bounds = getSubBounds(sub_index);
		Rectangle ext_bounds = new Rectangle (bounds.x - 1, bounds.y - 1, bounds.width + 2, bounds.height + 2);
		if (ext_bounds.x < 0) {
			ext_bounds.width += ext_bounds.x;
			ext_bounds.x = 0;
		}
		if ((ext_bounds.x + ext_bounds.width) > this.bounds.width) {
			ext_bounds.width = this.bounds.width - ext_bounds.x;
		}
		if (ext_bounds.y < 0) {
			ext_bounds.height += ext_bounds.y;
			ext_bounds.y = 0;
		}
		if ((ext_bounds.y + ext_bounds.height) > this.bounds.height) {
			ext_bounds.height = this.bounds.height - ext_bounds.y;
		}
		bounds.x =      ext_bounds.x;
		bounds.y =      ext_bounds.y;
		bounds.width =  ext_bounds.width;
		bounds.height = ext_bounds.height;

	}

	public void add(TileCluster tileCluster) {
		if (!bounds.contains(tileCluster.bounds)) {
			throw new IllegalArgumentException ("TileCluster.add(): Added cluster should fit into this ");
		}
		cluster_index =   null;
		if (clust_list == null) {
			clust_list =  new ArrayList<IndexedRectanle>();
		}
		clust_list.add(new IndexedRectanle(tileCluster.index, tileCluster.bounds, tileCluster.isSky()));
		border_int_max = tileCluster.border_int_max; // all clusters should have the same border_int_max
		int dst_x = tileCluster.bounds.x - bounds.x;
		for (int src_y = 0; src_y < tileCluster.bounds.height; src_y++) {
			int dst_y = src_y + tileCluster.bounds.y - bounds.y;
			System.arraycopy(
					tileCluster.border,
					src_y * tileCluster.bounds.width,
					border,
					dst_y * bounds.width + dst_x,
					tileCluster.bounds.width);
			System.arraycopy(
					tileCluster.border_int,
					src_y * tileCluster.bounds.width,
					border_int,
					dst_y * bounds.width + dst_x,
					tileCluster.bounds.width);
			System.arraycopy(
					tileCluster.disparity,
					src_y * tileCluster.bounds.width,
					disparity,
					dst_y * bounds.width + dst_x,
					tileCluster.bounds.width);
			System.arraycopy(
					tileCluster.stitch_stitched,
					src_y * tileCluster.bounds.width,
					stitch_stitched,
					dst_y * bounds.width + dst_x,
					tileCluster.bounds.width);
			System.arraycopy(
					tileCluster.no_connect,
					src_y * tileCluster.bounds.width,
					no_connect,
					dst_y * bounds.width + dst_x,
					tileCluster.bounds.width);
			System.arraycopy(
					tileCluster.class_conn,
					src_y * tileCluster.bounds.width,
					class_conn,
					dst_y * bounds.width + dst_x,
					tileCluster.bounds.width);
		}
		return;
	}
	
	
	
}