/*
 *    GeoTools - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2004-2006, Geotools Project Managment Committee (PMC)
 *    
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation; either
 *    version 2.1 of the License, or (at your option) any later version.
 *
 *    This library 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
 *    Lesser General Public License for more details.
 */
package org.geotools.data.wms;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.geotools.data.ows.Layer;
import org.geotools.data.ows.WMSCapabilities;
import org.geotools.referencing.CRS;
import org.opengis.metadata.Identifier;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;


/**
 * Provides miscellaneous utility methods for use with WMSs.
 *
 * @author Richard Gould
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/plugin/wms/src/main/java/org/geotools/data/wms/WMSUtils.java $
 */
public class WMSUtils {
    /**
     * Utility method to return each layer that has a name. This method maintains no hierarchy at all.
     * 
     * @return An array of Layers, each value has a it's name property set or an empty array if there are none. It will return null if there is no capabilities document
     * 
     */
    public static Layer[] getNamedLayers(WMSCapabilities capabilities) {
    	
    	if (capabilities == null) {
    		return null;
    	}
    	
        List namedLayersList = new ArrayList();
        
        Layer[] layers = (Layer[]) capabilities.getLayerList().toArray(new Layer[capabilities.getLayerList().size()]);
    
        for( int i = 0; i < layers.length; i++ ) {
            if ((layers[i].getName() != null) && (layers[i].getName().length() != 0)) {
                namedLayersList.add(layers[i]);
            }
        }
    
        Layer[] namedLayers = new Layer[namedLayersList.size()];
        for (int i = 0; i < namedLayersList.size(); i++) {
            namedLayers[i] = (Layer) namedLayersList.get(i);
        }
        
        return namedLayers;
    }

    public static Set getQueryableLayers(WMSCapabilities capabilities) {
        Set layers = new TreeSet();
    
        Layer[] namedLayers = getNamedLayers(capabilities);
    
        for( int i = 0; i < namedLayers.length; i++ ) {
            Layer layer = namedLayers[i];
    
            if (layer.isQueryable()) {
                layers.add(layer);
            }
        }
    
        return layers;
    }

    public static Set getSRSs(WMSCapabilities capabilities) {
        Set srss = new TreeSet();
    
        Layer[] layers = (Layer[]) capabilities.getLayerList().toArray(new Layer[capabilities.getLayerList().size()]);
    
        for( int i = 0; i < layers.length; i++ ) {
            if (layers[i].getSrs() != null) {
                srss.addAll(layers[i].getSrs());
            }
        }
    
        return srss;
    }
    
    /**
     * Given a list of type Layer, return all EPSG codes that is supported
     * by all of the layers. This is an intersection of each layer's SRS set.
     *  
     * @param layers A List of type Layer
     * @return a Set of type String, containin EPSG codes, or empty if none found
     */
    public static Set findCommonEPSGs(List layers) {
    	TreeSet set = new TreeSet();
    	
    	Layer first = (Layer) layers.get(0);
    	
    	set.addAll(first.getSrs());
    	
    	for (int i = 1; i < layers.size(); i++ ) {
    		Layer layer = (Layer) layers.get(i);
    		set.retainAll(layer.getSrs());
    	}
    	
    	return set;
    }
    
    //Map<CoordinateReferenceSystem, TreeSet<String>>
    private static Map crsCache;
    
    static {
    	crsCache = new HashMap();
    }
    
    /**
     * Given a CRS and a Set of type String consisting of EPSG CRS codes
     * (such as "EPSG:4326"), it will check the transform that exists between
     * each EPSG code's CRS and the given CRS. If this is the identity
     * transform, meaning the CRS is equivalent to the EPSG code, 
     * the used EPSG code will be returned. The first valid EPSG code found
     * is returned, so it is possibly that multiple valid codes exist.
     * 
     * If no such identity transform can be found, null will be returned.
     * 
     * If this method is succesful, the result is stored in a cache, which is
     * called in subsequent calls. 
     * 
     * @param crs a CRS that is to be compared to each EPSG code in codes
     * @param codes a Set of type String containing EPSG codes
     * @return an EPSG code that correspondes to crs, or null if one can't be found
     */
    public static String matchEPSG(CoordinateReferenceSystem crs, Set codes) {
    	
    	TreeSet previous = (TreeSet) crsCache.get(crs);
    	if (previous != null) {
    		
    		Iterator iter = previous.iterator();
    		while (iter.hasNext()) {
    			String code = (String) iter.next();
    			
    			if (codes.contains(code)) {
    				return code;
    			}
    		}
    	}
    	
    	Iterator iter = crs.getIdentifiers().iterator();
    	while (iter.hasNext()) {
    		Identifier ident = (Identifier) iter.next();
    		String epsgCode = ident.toString();
    		if (codes.contains(epsgCode)) {
    			if (crsCache.get(crs) == null) {
    				crsCache.put(crs, new TreeSet());
    			}
    			TreeSet set = (TreeSet) crsCache.get(crs);
    			set.add(epsgCode);
    			
    			return epsgCode;
    		}
    	}
    	
    	iter = null;
    	
    	iter = codes.iterator();
    	while (iter.hasNext()) {
    		String epsgCode = (String) iter.next();
    		
    		CoordinateReferenceSystem epsgCRS;
			try {
				epsgCRS = CRS.decode(epsgCode);
			} catch (Exception e) {
//				e.printStackTrace();
				continue;
			}
    		
    		MathTransform transform;
			try {
				transform = CRS.findMathTransform(crs, epsgCRS, true);
			} catch (FactoryException e) {
//				e.printStackTrace();
				continue;
			}
    		
    		if (transform.isIdentity()) {
    			if (crsCache.get(crs) == null) {
    				crsCache.put(crs, new TreeSet());
    			}
    			TreeSet set = (TreeSet) crsCache.get(crs);
    			set.add(epsgCode);
    			
    			return epsgCode;
    		}
    	}
    	return null;
    }
}
