/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: GlobalRouterPathFinder.java
 * Written by: Alexander Herzog (Team 4)
 *
 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
 *
 * Electric(tm) 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.
 *
 * Electric(tm) 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/>.
 */
package com.sun.electric.tool.routing.experimentalLeeMoore2;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

public class GlobalRouterPathFinder {

	GlobalRouterV3 glr;
	int[] fields;
	
	public GlobalRouterPathFinder(GlobalRouterV3 router){
		glr = router;
		
		fields = new int[glr.regions_x * glr.regions_y];
		for(int i = 0; i < fields.length; ++i){
			fields[i] = Integer.MAX_VALUE;
		}
	}
	private int GetFieldIndex(int x, int y){
		return y * glr.regions_x + x;
	}
	private void SetField(int x, int y, int value){
		fields[GetFieldIndex(x, y)] = value;
	}
	private int GetField(int x, int y){
		return fields[GetFieldIndex(x, y)];
	}
	
	public ArrayList<Vector2i> ConvertToPath(ArrayList<RegionDirection> dirs, Vector2i start){
		ArrayList<Vector2i> ret = new ArrayList<Vector2i>();
		Vector2i pos = new Vector2i(start);
		
		//add start
		ret.add(pos);
		
		Iterator<RegionDirection> it = dirs.iterator();
		while(it.hasNext()){
			RegionDirection rd = it.next();
			Vector2i nb = glr.GetNeighborPos(pos, rd);
			assert(glr.IsCoordinateValid(nb.x, nb.y));
			ret.add(nb);
		}
		
		return ret;
	}
	
	private class LeeMooreJob{
		Vector2i pos;
		int length;
		
		private LeeMooreJob(Vector2i p, int l){
			pos = p;
			length = l;
		}
	}
	
	// XXX [fschmidt] could not find shortest path on small designs
	private ArrayList<RegionDirection> FindShortestPathLeeMoore(int seg_id){
		Vector2i start = glr.segments[seg_id].start;
		Vector2i end = glr.segments[seg_id].end;
		
		LinkedList<LeeMooreJob> jobs = new LinkedList<LeeMooreJob>();
		LeeMooreJob cj = new LeeMooreJob(new Vector2i(start), 0);
		SetField(start.x, start.y, 0);
		
		while(cj != null && !cj.pos.equals(end)){
			/** consider neighbors */
			for(RegionDirection dir : RegionDirection.values()){
				if(dir == RegionDirection.rd_undefined){
					continue;
				}
				if(!glr.IsCoordinateValid(glr.GetNeighborX(cj.pos.x, dir), 
						glr.GetNeighborY(cj.pos.y, dir))){
					continue;
				}

				// check if neighbor has shorter backpath
				Vector2i neighbor = glr.GetNeighborPos(cj.pos, dir);
				int n = GetField(neighbor.x, neighbor.y);
				if(n >= 0 && n < Integer.MAX_VALUE && n <= cj.length + 1){
					continue;
				}
				
				// check if border is blocked
				if(glr.RegionAt(cj.pos.x, cj.pos.y).GetRegionBorder(dir).IsBlocked()){
					continue;
				}

				// else pass border and offer job
				SetField(neighbor.x, neighbor.y, cj.length + 1);
				jobs.offer(new LeeMooreJob(neighbor, cj.length + 1));
			}
			

			/** poll next job */
			cj = jobs.poll();
		}
			
		if(cj == null || !cj.pos.equals(end)){
			//could not get to end
			return null;
		}
		int l = cj.length;
		/** backtrace */
		ArrayList<RegionDirection> ret_list = new ArrayList<RegionDirection>();
		
		Vector2i it = new Vector2i(end);
		while(!it.equals(start)){
			int min = Integer.MAX_VALUE;
			RegionDirection min_dir = RegionDirection.rd_undefined;
			
			RegionDirection[] directions = RegionDirection.values();
			List<RegionDirection> dir_list = Arrays.asList(directions);
			Collections.shuffle(dir_list);
			for(int i= 0; i < dir_list.size(); ++i){
				directions[i] = dir_list.get(i);
			}
			for(RegionDirection dir : directions){
				if(dir == RegionDirection.rd_undefined){
					continue;
				}
				if(!glr.IsCoordinateValid(glr.GetNeighborX(it.x, dir), 
						glr.GetNeighborY(it.y, dir))){
					continue;
				}
				
				int w = GetField(glr.GetNeighborX(it.x, dir), glr.GetNeighborY(it.y, dir));
				if(w >= 0 && w < min){
					min = w;
					min_dir = dir;
				}
			}
			if(min_dir == RegionDirection.rd_undefined){
				// should never happen
				assert(min_dir != RegionDirection.rd_undefined);
				return null;
			}
			ret_list.add(0, GlobalRouterV3.GetOppositeDir(min_dir));
			assert(ret_list.size() <= l);
			it = glr.GetNeighborPos(it, min_dir);
		}
		
		return ret_list;
	}
	
	public List<RegionDirection> FindShortestPath(int seg_id){
		return FindShortestPathLeeMoore(seg_id);
	}

}
