/*******************************************************************************
 * Copyright (c) 2004, 2007 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * Contributors:
 * IBM Corporation - initial API and implementation
 * yyyymmdd bug      Email and other contact information
 * -------- -------- -----------------------------------------------------------
 *     IBM Corporation - Initial API and implementation
 *     Jens Lukowski/Innoopract - initial renaming/restructuring
 * 20071205   211262 ericdp@ca.ibm.com - Eric Peters, CopyWSDLTreeCommand fails to copy ?wsdl
 *******************************************************************************/
package org.eclipse.wst.common.uriresolver.internal.util;

import java.io.File;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URL;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;


public class URIHelper
{                       
  protected static final String FILE_PROTOCOL = "file:";
  protected static final String PLATFORM_RESOURCE_PROTOCOL = "platform:/resource/";
  protected static final String PROTOCOL_PATTERN = ":"; 
  
  
  public static String ensureURIProtocolFormat(String uri) {
	  String protocol = getProtocol(uri);
	  if (protocol != null) {
		  if (protocol.equals(FILE_PROTOCOL)) {
			  return ensureFileURIProtocolFormat(uri);
		  }
	  }
	 return uri;
  }
  
  
  /**
   * This method takes a file URI in String format and ensures the protocol is followed by three slashes.
   * For example, files "file:D:/XXX", "file:/D:/XXX" and "file://D:/XXX" are corrected to:
   * "file:///D:/XXX".
   * If the input is not a file URI (does not start with "file:"), the String is returned unmodified.
   */
  public static String ensureFileURIProtocolFormat(String uri) {
      if (uri.startsWith(FILE_PROTOCOL) && !uri.startsWith(FILE_PROTOCOL + "///")) //$NON-NLS-1$
      {
      	if (uri.startsWith(FILE_PROTOCOL + "//")) {
      		uri = FILE_PROTOCOL + "/" + uri.substring(FILE_PROTOCOL.length()); //$NON-NLS-1$
      	} else if (uri.startsWith(FILE_PROTOCOL + "/")) {
      		uri = FILE_PROTOCOL + "//" + uri.substring(FILE_PROTOCOL.length()); //$NON-NLS-1$
      	} else {
      		uri = FILE_PROTOCOL + "///" + uri.substring(FILE_PROTOCOL.length()); //$NON-NLS-1$
      	}
      }
     return uri;
  }
  
  public static String normalize(String uri)
  {                           
    if (uri != null)
    {                      
      String protocol = getProtocol(uri);
      String file = uri;
           
      if (protocol != null)
      {               
        try
        {   
          // 
          URL url = new URL(uri); 
          // we use a 'Path' on the 'file' part of the url in order to normalize the '.' and '..' segments
          IPath path = new Path(url.getFile()); 
          URL url2 = new URL(url.getProtocol(), url.getHost(), url.getPort(), path.toString());
          uri = url2.toString();                               
        }                        
        catch (Exception e)
        {  
        }
      }   
      else
      {      
        IPath path = new Path(file);
        uri = path.toString();
      }
    }
    return uri;
  }


  /**
   * a 'null' rootLocation argument will causes uri that begins with a '/' to be treated as a workspace relative resource
   * (i.e. the string "platform:/resource" is prepended and the uri is resolved via the Platform object)
   */
  public static String normalize(String uri, String resourceLocation, String rootLocation)
  {
    String result = null;

    if (uri != null)
    { 
      // is the uri a url
      if (hasProtocol(uri))
      {                  
        if (isPlatformResourceProtocol(uri))
        {
          result = resolvePlatformUrl(uri);
        }
        else
        {
          result = uri;
        }
      }
   
      // is uri absolute
      //
      if (result == null)
      {
        if (uri.indexOf(":") != -1 || uri.startsWith("/") || uri.startsWith("\\"))
        {                   
          result = uri;
        }
      }
  
      // if uri is relative to the resourceLocation
      //
      if (result == null && resourceLocation != null)
      {          
        if (resourceLocation.endsWith("/"))
        {
			    result = resourceLocation + uri;
        }
		    else
        {
			    result = resourceLocation + "/../" + uri;
        }
      }
      
      if (result == null)
      {
        result = uri;
      }
  
      result = normalize(result);
    }

    //System.out.println("normalize(" + uri + ", " + resourceLocation + ", " + rootLocation + ") = " + result);
    return result;
  }


  public static boolean isURL(String uri)
  {
    return uri.indexOf(":/") > 2; // test that the index is > 2 so that C:/ is not considered a protocol
  }


  public static String getLastSegment(String uri)
  {
    String result = uri;
    int index = Math.max(uri.lastIndexOf("/"), uri.lastIndexOf("\\"));
    if (index != -1)
    {
      result = uri.substring(index + 1);
    }
    return result;
  }


  public static String getFileExtension(String uri)
  {
    String result = null;
    int dotIndex = getExtensionDotIndex(uri);
               
    if (dotIndex != -1)
    {
      result = uri.substring(dotIndex + 1);
    }

    return result;
  }


  public static String removeFileExtension(String uri)
  {
    String result = null;
    int dotIndex = getExtensionDotIndex(uri);

    if (dotIndex != -1)
    {
      result = uri.substring(0, dotIndex);
    }

    return result;
  }   
             

  // here we use the Platform to resolve a workspace relative path to an actual url
  //
  protected static String resolvePlatformUrl(String urlspec)
  {
    String result = null;
    try
    {                        
      urlspec = urlspec.replace('\\', '/'); 
      URL url = new URL(urlspec);
      URL resolvedURL = FileLocator.resolve(url);
      result = resolvedURL.toString();
    }
    catch (Exception e)
    {
    }
    return result;
  }


  protected static int getExtensionDotIndex(String uri)
  {
    int result = -1;
    int dotIndex = uri.lastIndexOf(".");
    int slashIndex = Math.max(uri.lastIndexOf("/"), uri.lastIndexOf("\\"));

    if (dotIndex != -1 && dotIndex > slashIndex)
    {
      result = dotIndex;
    }

    return result;
  }
  

  public static boolean isPlatformResourceProtocol(String uri)
  {                                                     
    return uri != null && uri.startsWith(PLATFORM_RESOURCE_PROTOCOL);
  }                                                   

  public static String removePlatformResourceProtocol(String uri)
  {  
    if (uri != null && uri.startsWith(PLATFORM_RESOURCE_PROTOCOL))
    {
      uri = uri.substring(PLATFORM_RESOURCE_PROTOCOL.length());
    }                                                          
    return uri;
  }            


  public static String prependPlatformResourceProtocol(String uri)
  {  
    if (uri != null && !uri.startsWith(PLATFORM_RESOURCE_PROTOCOL))
    {
      uri = PLATFORM_RESOURCE_PROTOCOL + uri;
    }                                                          
    return uri;
  } 
  

  public static String prependFileProtocol(String uri)
  {  
    if (uri != null && !uri.startsWith(FILE_PROTOCOL))
    {
      uri = FILE_PROTOCOL + uri;
    }                                                          
    return uri;
  } 
            
  public static boolean hasProtocol(String uri)
  {
    boolean result = false;     
    if (uri != null)
    {
      int index = uri.indexOf(PROTOCOL_PATTERN);
      if (index != -1 && index > 2) // assume protocol with be length 3 so that the'C' in 'C:/' is not interpreted as a protocol
      {
        result = true;
      }
    }
    return result;
  }     
                      

  public static boolean isAbsolute(String uri)
  {
    boolean result = false;     
    if (uri != null)
    {
      int index = uri.indexOf(PROTOCOL_PATTERN);
      if (index != -1 || uri.startsWith("/") || uri.startsWith("\\"))
      {
        result = true;
      }
    }
    return result;
  }


  public static String addImpliedFileProtocol(String uri)
  {  
    if (!hasProtocol(uri))
    {                           
      String prefix = FILE_PROTOCOL;
      prefix += uri.startsWith("/") ? "//" : "///";
      uri = prefix + uri;
    }
    return uri;
  }
             
  // todo... need to revisit this before we publicize it
  // 
  protected static String getProtocol(String uri)
  {  
    String result = null;     
    if (uri != null)
    {
      int index = uri.indexOf(PROTOCOL_PATTERN);
      if (index > 2) // assume protocol with be length 3 so that the'C' in 'C:/' is not interpreted as a protocol
      {
        result = uri.substring(0, index + PROTOCOL_PATTERN.length());
      }
    }
    return result;
  } 
 

  public static String removeProtocol(String uri)
  {
    String result = uri;     
    if (uri != null)
    {
      int index = uri.indexOf(PROTOCOL_PATTERN);
      if (index > 2)
      {
        result = result.substring(index + PROTOCOL_PATTERN.length());                 
      }
    }
    return result;
  } 


  protected static boolean isProtocolFileOrNull(String uri)
  {                                    
    String protocol = getProtocol(uri);   
    return protocol == null || protocol.equals(FILE_PROTOCOL);
  }  

  public static boolean isProtocolFile(String uri)
  {                                    
	  return uri != null && uri.startsWith(FILE_PROTOCOL);
  }  

                                           
  protected static boolean isMatchingProtocol(String uri1, String uri2)
  { 
    boolean result = false;  

    String protocol1 = getProtocol(uri1);
    String protocol2 = getProtocol(uri2);

    if (isProtocolFileOrNull(protocol1) && isProtocolFileOrNull(protocol2))
    {                                                                      
      result = true;
    } 
    else
    {
      result = protocol1 != null && protocol2 != null && protocol1.equals(protocol2);
    }             

    return result;
  }

  /**
   * warning... this method not fully tested yet
   */
  public static String getRelativeURI(String uri, String resourceLocation)
  {                                      
    String result = uri;  
    if (isMatchingProtocol(uri, resourceLocation)) 
    {
      result = getRelativeURI(new Path(removeProtocol(uri)),
                              new Path(removeProtocol(resourceLocation)));
    }            

    return result;
  }

  /**
   * warning... this method not fully tested yet
   */
  public static String getRelativeURI(IPath uri, IPath resourceLocation)
  {            
    String result = null;
    int nMatchingSegments = 0;       
    resourceLocation = resourceLocation.removeLastSegments(1);
    while (true)
    {                   
      String a = uri.segment(nMatchingSegments); 
      String b = resourceLocation.segment(nMatchingSegments); 
      if (a != null && b != null && a.equals(b))
      {
        nMatchingSegments++;
      }
      else
      {
        break;
      }
    }                 

    if (nMatchingSegments == 0)
    {
      result = uri.toOSString();
    }
    else
    {    
      result = "";   
      boolean isFirst = true;
      String[] segments = resourceLocation.segments();
      for (int i = nMatchingSegments; i < segments.length; i++)
      {  
        result += isFirst ? ".." : "/..";     
        if (isFirst)
        {
          isFirst = false;
        }        
      }
      // 
      segments = uri.segments();
      for (int i = nMatchingSegments; i < segments.length; i++)
      {                      
        result += isFirst ? segments[i] : ("/" + segments[i]);     
        if (isFirst)
        {
          isFirst = false;
        } 
      }
    }   
    return result;
  }


  public static String getPlatformURI(IResource resource)
  {                            
    String fullPath = resource.getFullPath().toString();
    if (fullPath.startsWith("/"))
    {
      fullPath = fullPath.substring(1);
    }
    return PLATFORM_RESOURCE_PROTOCOL + fullPath;
  }
  

  /**
   * This methods is used as a quick test to see if a uri can be resolved to an existing resource.   
   */
  public static boolean isReadableURI(String uri, boolean testRemoteURI)
  {  
    boolean result = true;  
    if (uri != null)
    {   
      try
      {                               
        uri = normalize(uri, null, null);
        if (isProtocolFileOrNull(uri))
        {
          uri = removeProtocol(uri);                            
          File file = new File(org.eclipse.wst.common.uriresolver.internal.URI.decode(uri));
          result = file.exists() && file.isFile();
        }
        else if (isPlatformResourceProtocol(uri))
        {
          // Note - If we are here, uri has been failed to resolve
          // relative to the Platform. See normalize() to find why.
          result = false;
        }
        else if (testRemoteURI)
        {
          URL url = new URL(uri);
          InputStream is = url.openConnection().getInputStream();
          is.close();
          // the uri is readable if we reach here.
          result = true;
        }
      }
      catch (Exception e)
      {
        result = false;
      }
    }
    else // uri is null
      result = false;

    return result;
  }  

  /**
   * return true if this is a valid uri
   */
  public static boolean isValidURI(String uri)
  {                       
    boolean result = false;
    try                                                              
    {
      new URI(uri);
      result = true;
    }
    catch (Exception e)
    {
    }               
    return result;
  }

  /**
   * returns an acceptable URI for a file path
   */
  public static String getURIForFilePath(String filePath)
  {
    String result = addImpliedFileProtocol(filePath);
    if (!isValidURI(result))
    {
    	try
    	{
        result = URIEncoder.encode(result, "UTF8");
    	}
    	catch(UnsupportedEncodingException e)
    	{
    		// Do nothing as long as UTF8 is used. This is supported.
    	}
    }
    return result;
  }
}
