package org.exist.xmldb;

import java.net.URI;
import java.net.URISyntaxException;

public class FullXmldbURI extends XmldbURI {

	//this will never have xmldb:
	private URI wrappedURI;	
	private String context;      
	private String apiName;
	
	/**
	 * Contructs an XmldbURI from given URI.
	 * The provided URI must have the XMLDB_SCHEME ("xmldb")
	 * @param xmldbURI A string 
	 * @throws URISyntaxException If the given string is not a valid xmldb URI.
	 */
	protected FullXmldbURI(URI xmldbURI) throws URISyntaxException {
    	super(xmldbURI);
	}
    /** Feeds private members
     * @throws URISyntaxException
     */
	protected void parseURI(URI xmldbURI, boolean hadXmldbPrefix) throws URISyntaxException {
		wrappedURI = xmldbURI;
		if(hadXmldbPrefix) {
			if (wrappedURI.getScheme() == null)   
				//Put the "right" URI in the message ;-)
				throw new URISyntaxException(XMLDB_URI_PREFIX+wrappedURI.toString(), "xmldb URI scheme has no instance name");			
			String userInfo = wrappedURI.getUserInfo();
			//Very tricky :
			if (wrappedURI.getHost() == null && wrappedURI.getAuthority() != null) {
				userInfo = wrappedURI.getAuthority();
				if (userInfo.endsWith("@"))
					userInfo = userInfo.substring(0, userInfo.length() - 1);
			}
			//Eventually rewrite wrappedURI *without* user info
			if (userInfo != null) {
				StringBuffer recomputed = new StringBuffer();//XMLDB_URI_PREFIX);                
				recomputed.append(wrappedURI.getScheme());
				recomputed.append("://");
				recomputed.append(wrappedURI.getHost());
				if (wrappedURI.getPort() != -1)
					recomputed.append(":").append(wrappedURI.getPort());                
				recomputed.append(wrappedURI.getRawPath());
				wrappedURI = new URI(recomputed.toString());                
			}
		}
		super.parseURI(xmldbURI,hadXmldbPrefix);
	}
	
	protected void splitPath(String path) throws URISyntaxException {
		int index = -1;
		int lastIndex = -1;	
		//Reinitialise members
		this.context = null;
		String pathForSuper = null;
		if (path != null) {
			String host = getHost();
			if(host==null || EMBEDDED_SERVER_AUTHORITY.equals(host)) {
	        	if (getPort() != NO_PORT)
	        		//Put the "right" URI in the message ;-)
	        		throw new URISyntaxException(XMLDB_URI_PREFIX+wrappedURI.toString(), "Local xmldb URI should not provide a port");
	        	apiName = API_LOCAL;  
	        	context = null;
	        	pathForSuper = path; 	 

			} else {  
                //Try to extract the protocol from the provided URI. 
                //TODO : get rid of this and use a more robust approach (dedicated constructor ?) -pb
	    		//TODO : use named constants  
	        	index = path.lastIndexOf("/xmlrpc");        	         	
	        	if (index > lastIndex) {
	        		apiName = API_XMLRPC;        		
	        		pathForSuper = path.substring(index + "/xmlrpc".length());
	        		context = path.substring(0, index) + "/xmlrpc";
	        		lastIndex = index;
	        	}         	
	        	//TODO : use named constants  
	        	index = path.lastIndexOf("/webdav");        	         	
	        	if (index > lastIndex) {
	        		apiName = API_WEBDAV;        		
	        		pathForSuper = path.substring(index + "/webdav".length());
	        		context = path.substring(0, index) + "/webdav";
	        		lastIndex = index;
	        	}    		
	        	//Default : REST-style...
	        	if (apiName == null) {	    			
	        		apiName = API_REST;  
	        		pathForSuper =  path; 	
	    			//TODO : determine the context out of a clean root collection policy.
	    			context = null;	        		        		
	        	}
    		}    	
		}		
    	super.splitPath(pathForSuper);							
	}

	/** To be called each time a private member that interacts with the wrapped URI is modified.
	 * @throws URISyntaxException
	 */
	protected void recomputeURI() throws URISyntaxException {
		URI oldWrappedURI = wrappedURI;
		StringBuffer buf = new StringBuffer();
		if (getInstanceName() != null)	
			buf.append(getInstanceName()).append("://");
        //No userInfo
		if (getHost() != null)	
			buf.append(getHost());				
		if (getPort() != NO_PORT)
			buf.append(":" + getPort());		
		if (context != null)
			buf.append(context);
		//TODO : eventually use a prepend.root.collection system property 		
		if (getRawCollectionPath() != null)
			buf.append(getRawCollectionPath());
		try {
			wrappedURI = new URI(buf.toString());			
    	} catch (URISyntaxException e) {
    		wrappedURI = oldWrappedURI;        	
        	throw e; 
    	}			
	}
		
	private void setContext(String context) {
		String oldContext = this.context;
		try {
			//trims any trailing slash 
	    	if (context != null && context.endsWith("/")) {   		
	    		//include root slash if we have a host
	    		if (this.getHost() != null)
	    			context = context.substring(0, context.length() - 1); 
	    	}
			this.context = "".equals(context) ? null : context;
			recomputeURI();
		} catch (URISyntaxException e) {
			this.context = oldContext;
			throw new IllegalArgumentException("Invalid URI: "+e.getMessage());
		}
	}
	
	public URI getURI() { 			
		return wrappedURI; 
	}
	
	public URI getXmldbURI() { 			
		return URI.create(XMLDB_URI_PREFIX+wrappedURI.toString()); 
	}
	
	public String getInstanceName() {		
		return wrappedURI.getScheme(); 
	}
    

	public String getApiName() {		
		return apiName; 
	}
	
	public String getContext() {		
		return context; 
	}
	
	public boolean isAbsolute() {	
		return wrappedURI.isAbsolute();
	}
	
	public boolean isContextAbsolute() {
		String context = this.getContext();
		if (context == null)
			return true;
		return context.startsWith("/");
	}
	
	public XmldbURI normalizeContext() {			
		String context = this.getContext();
		if (context == null)
			return this;
		URI uri = URI.create(context);		
		try {
			FullXmldbURI xmldbURI = new FullXmldbURI(getXmldbURI());
			xmldbURI.setContext(uri.normalize().getRawPath());
			return xmldbURI;
		} catch (URISyntaxException e) {
			throw new IllegalArgumentException("Invalid URI: "+e.getMessage());
		}
	}	
	
	public URI relativizeContext(URI uri) {
		if (uri == null)
			throw new NullPointerException("The provided URI is null");			
		String context = this.getContext();
		if (context == null)
			throw new NullPointerException("The current context is null");		
		URI contextURI;
		//Adds a final slash if necessary
		if (!context.endsWith("/")) {
            LOG.info("Added a final '/' to '" + context + "'"); 
			contextURI = URI.create(context + "/");
        } else
			contextURI = URI.create(context);		
		return contextURI.relativize(uri);	
	}
	
	public URI resolveContext(String str) throws NullPointerException, IllegalArgumentException {	
		if (str == null)
			throw new NullPointerException("The provided URI is null");		
		String context = this.getContext();
		if (context == null)
			throw new NullPointerException("The current context is null");
		URI contextURI;
		//Adds a final slash if necessary
		if (!context.endsWith("/")) {
            LOG.info("Added a final '/' to '" + context + "'");  
			contextURI = URI.create(context + "/");
        } else
			contextURI = URI.create(context);
		
		return contextURI.resolve(str);	
	}
	
	public URI resolveContext(URI uri) throws NullPointerException {	
		if (uri == null)
			throw new NullPointerException("The provided URI is null");		
		String context = this.getContext();
		if (context == null)
			throw new NullPointerException("The current context is null");	
		URI contextURI;
		//Adds a final slash if necessary
		if (!context.endsWith("/")) {
            LOG.info("Added a final '/' to '" + context + "'"); 
			contextURI = URI.create(context + "/");
        } else
			contextURI = URI.create(context);		
		return contextURI.resolve(uri);	
	}
	
	/* (non-Javadoc)
	 * @see java.net.URI#getAuthority()
	 */
	public String getAuthority() {
		return wrappedURI.getAuthority();
	}

	/* (non-Javadoc)
	 * @see java.net.URI#getFragment()
	 */
	public String getFragment() {
		return wrappedURI.getFragment();
	}

	/* (non-Javadoc)
	 * @see java.net.URI#getPort()
	 */
	public int getPort() {
		return wrappedURI.getPort();
	}

	/* (non-Javadoc)
	 * @see java.net.URI#getQuery()
	 */
	public String getQuery() {
		return wrappedURI.getQuery();
	}

	/* (non-Javadoc)
	 * @see java.net.URI#getRawAuthority()
	 */
	public String getRawAuthority() {
		return wrappedURI.getRawAuthority();
	}

	/* (non-Javadoc)
	 * @see java.net.URI#getHost()
	 */
	public String getHost() {
		return wrappedURI.getHost();
	}

	/* (non-Javadoc)
	 * @see java.net.URI#getUserInfo()
	 */
	public String getUserInfo() {
		return wrappedURI.getUserInfo();
	}

	/* (non-Javadoc)
	 * @see java.net.URI#getRawFragment()
	 */
	public String getRawFragment() {
		return wrappedURI.getRawFragment();
	}

	/* (non-Javadoc)
	 * @see java.net.URI#getRawQuery()
	 */
	public String getRawQuery() {
		return wrappedURI.getRawQuery();
	}

	/* (non-Javadoc)
	 * @see java.net.URI#getRawUserInfo()
	 */
	public String getRawUserInfo() {
		return wrappedURI.getRawUserInfo();
	}
}
