/*
 * Copyright 2010 JBoss Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.guvnor.tools.utils.webdav;

import java.io.InputStream;
import java.net.URL;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.webdav.IContext;
import org.eclipse.webdav.ILocator;
import org.eclipse.webdav.IResponse;
import org.eclipse.webdav.client.RemoteDAVClient;
import org.eclipse.webdav.client.WebDAVFactory;
import org.eclipse.webdav.http.client.HttpClient;
import org.eclipse.webdav.http.client.IAuthenticator;
import org.guvnor.tools.Activator;

/**
 * WebDav wrapper client.
 */
public class WebDavClient implements IWebDavClient {

    private RemoteDAVClient client;

    private WebDavAuthenticator platformAuthenticator;

    /**
     * Ctor for this wrapper WebDav client.
     * @param serverUrl The WebDav repository location (server)
     */
    /** package */ WebDavClient(URL serverUrl) {
        HttpClient httpClient = null;
        if (Activator.getDefault().useDebugHttpClient()) {
            httpClient = new DebugHttpClient();
        } else {
            httpClient = new HttpClient();
        }
        platformAuthenticator =  new WebDavAuthenticator(serverUrl);
        httpClient.setAuthenticator(platformAuthenticator);
        client = new RemoteDAVClient(new WebDAVFactory(), httpClient);
    }

    /*
     * (non-Javadoc)
     * @see org.guvnor.tools.utils.webdav.IWebDavClient#setSessionAuthenticator(org.eclipse.webdav.http.client.IAuthenticator)
     */
    public void setSessionAuthenticator(IAuthenticator sessionAuthen) {
        if (sessionAuthen != null) {
            client.getHttpClient().setAuthenticator(sessionAuthen);
        } else {
            client.getHttpClient().setAuthenticator(platformAuthenticator);
        }
    }

    /*
     * (non-Javadoc)
     * @see org.guvnor.tools.utils.webdav.IWebDavClient#getClient()
     */
    public RemoteDAVClient getClient() {
        return client;
    }

    /*
     * (non-Javadoc)
     * @see org.guvnor.tools.utils.webdav.IWebDavClient#createContext()
     */
    public IContext createContext() {
        IContext context = WebDAVFactory.contextFactory.newContext();
        // Need to make sure the USER-AGENT header is present for Guvnor
        context.put("USER-AGENT", "guvnor"); //$NON-NLS-1$ //$NON-NLS-2$
        return context;
    }

    /*
     * (non-Javadoc)
     * @see org.guvnor.tools.utils.webdav.IWebDavClient#listDirectory(java.lang.String)
     */
    public Map<String, ResourceProperties> listDirectory(String path) throws Exception {
        IResponse response = null;
        try {
            IContext context = createContext();
            context.put("Depth", "1"); //$NON-NLS-1$ //$NON-NLS-2$
            ILocator locator = WebDAVFactory.locatorFactory.newLocator(path);
            response = client.propfind(locator, context, null);
            if (response.getStatusCode() != IResponse.SC_MULTI_STATUS) {
                throw new WebDavException(response);
            }
            Map<String, ResourceProperties> res =
                StreamProcessingUtils.parseListing(path, response.getInputStream());
            addGuvnorResourceProperties(res, path);
            return res;
        } finally {
            if (response != null) {
                response.close();
            }
        }
    }

    private void addGuvnorResourceProperties(Map<String,
                                            ResourceProperties> props,
                                            String path) throws Exception {
        IResponse response = null;
        try {
            String apiVer = changeToAPICall(path);
            Properties guvProps = new Properties();
            response = getResourceInputStream(apiVer);
            guvProps.load(response.getInputStream());
            response.getInputStream();
            for (Iterator<String> it = props.keySet().iterator(); it.hasNext();) {
                String oneKey = it.next();
                String val = guvProps.getProperty(oneKey);
                if (val != null) {
                    ResourceProperties resProps = props.get(oneKey);
                    StringTokenizer tokens = new StringTokenizer(val, ","); //$NON-NLS-1$
//                    String dateStamp = tokens.nextToken();
//                    String revision = tokens.nextToken();
                    if(tokens.hasMoreElements()){
                        resProps.setLastModifiedDate(tokens.nextToken());
                    }
                    if(tokens.hasMoreElements()){
                        resProps.setRevision(tokens.nextToken());
                    }
                }
            }
        } finally {
            if (response != null) {
                response.close();
            }
        }
    }

    /*
     * (non-Javadoc)
     * @see org.guvnor.tools.utils.webdav.IWebDavClient#queryProperties(java.lang.String)
     */
    public ResourceProperties queryProperties(String resource) throws Exception {
        IResponse response = null;
        try {
            IContext context = createContext();
            context.put("Depth", "1"); //$NON-NLS-1$ //$NON-NLS-2$
            ILocator locator = WebDAVFactory.locatorFactory.newLocator(resource);
            response = client.propfind(locator, context, null);
            if (response.getStatusCode() != IResponse.SC_MULTI_STATUS
               && response.getStatusCode() != IResponse.SC_OK) {
                throw new WebDavException(response);
            }
            Map<String, ResourceProperties> props =
                StreamProcessingUtils.parseListing("", response.getInputStream()); //$NON-NLS-1$
            if (props.keySet().size() != 1) {
                throw new Exception(props.keySet().size() + " entries found for " + resource); //$NON-NLS-1$
            }
            String fullpath = props.keySet().iterator().next();
            ResourceProperties res = props.get(fullpath);
            String filename = new Path(fullpath).lastSegment();
            addGuvnorResourceProperties(res, filename, resource);
            return res;
        } finally {
            if (response != null) {
                response.close();
            }
        }
    }

    /**
     * Adds Guvnor-specific resource properties to the collection.
     * @param props The resource properties colleciton
     * @param filename The name of the resource
     * @param resource The full path to the resource in Guvnor
     * @throws Exception
     */
    private void addGuvnorResourceProperties(ResourceProperties props,
                                            String filename, String resource) throws Exception {
        if (props == null) {
            return;
        }
        IResponse response = null;
        try {
            String path = resource.substring(0, resource.lastIndexOf('/'));
            String apiVer = changeToAPICall(path);
            Properties guvProps = new Properties();
            response = getResourceInputStream(apiVer);
            guvProps.load(response.getInputStream());
            String val = guvProps.getProperty(filename);
            if (val != null) {
                StringTokenizer tokens = new StringTokenizer(val, ","); //$NON-NLS-1$
//                String dateStamp = tokens.nextToken();
//                String revision = tokens.nextToken();
                if(tokens.hasMoreElements()){
                    props.setLastModifiedDate(tokens.nextToken());
                }
                if(tokens.hasMoreElements()){
                    props.setRevision(tokens.nextToken());
                }
            } else {
                Exception nfe = new Exception("Failed to get Guvnor properties for " + filename); //$NON-NLS-1$
                Activator.getDefault().writeLog(IStatus.WARNING, nfe.getMessage(), nfe);
            }
        } finally {
            if (response != null) {
                response.close();
            }
        }
    }

    /*
     * (non-Javadoc)
     * @see org.guvnor.tools.utils.webdav.IWebDavClient#getResourceContents(java.lang.String)
     */
    public String getResourceContents(String resource) throws Exception {
        IResponse response = null;
        try {
            response = getResourceInputStream(resource);
            return StreamProcessingUtils.getStreamContents(response.getInputStream());
        } finally {
            if (response != null) {
                response.close();
            }
        }
    }

    /*
     * (non-Javadoc)
     * @see org.guvnor.tools.utils.webdav.IWebDavClient#getResourceVersionContents(java.lang.String, java.lang.String)
     */
    public String getResourceVersionContents(String resource, String version) throws Exception {
        String apiVer = changeToAPICall(resource) + "?version=" + version; //$NON-NLS-1$
        return getResourceContents(apiVer);
    }

    /*
     * (non-Javadoc)
     * @see org.guvnor.tools.utils.webdav.IWebDavClient#getInputStream(java.lang.String)
     */
    public IResponse getResourceInputStream(String resource) throws Exception {
        ILocator locator = WebDAVFactory.locatorFactory.newLocator(resource);
        IResponse response = client.get(locator, createContext());
        if (response.getStatusCode() != IResponse.SC_OK) {
            throw new WebDavException(response);
        }
        return response;
    }

    /*
     * (non-Javadoc)
     * @see org.guvnor.tools.utils.webdav.IWebDavClient#getResourceVersionInputStream(java.lang.String, java.lang.String)
     */
    public IResponse getResourceVersionInputStream(String resource, String version) throws Exception {
        String apiVer = changeToAPICall(resource) + "?version=" + version; //$NON-NLS-1$
        return getResourceInputStream(apiVer);
    }

    /*
     * (non-Javadoc)
     * @see org.guvnor.tools.utils.webdav.IWebDavClient#createResource(java.lang.String, java.io.InputStream)
     */
    public boolean createResource(String resource, InputStream is) throws Exception {
        return createResource(resource, is, true);
    }

    /*
     * (non-Javadoc)
     * @see org.guvnor.tools.utils.webdav.IWebDavClient#createResource(java.lang.String, java.io.InputStream, boolean)
     */
    public boolean createResource(String resource, InputStream is, boolean overwrite) throws Exception {
        boolean res = true;
        if (!overwrite) {
            try {
                if (queryProperties(resource) != null) {
                    res = false;
                }
            } catch (WebDavException e) {
                if (e.getErrorCode() != IResponse.SC_NOT_FOUND) {
                    throw e;
                }
            }
        }
        IResponse response = null;
        try {
            if (res) {
                ILocator locator = WebDAVFactory.locatorFactory.newLocator(resource);
                response = client.put(locator, createContext(), is);
                if (response.getStatusCode() != IResponse.SC_OK
                   && response.getStatusCode() != IResponse.SC_CREATED) {
                    throw new WebDavException(response);
                }
            }
        } finally {
            if (response != null) {
                response.close();
            }
        }
        return res;
    }

    /*
     * (non-Javadoc)
     * @see org.guvnor.tools.utils.webdav.IWebDavClient#putResource(java.lang.String, java.io.InputStream)
     */
    public void putResource(String resource, InputStream is) throws Exception {
        IResponse response = null;
        try {
            ILocator locator = WebDAVFactory.locatorFactory.newLocator(resource);
            response = client.put(locator, createContext(), is);
            if (response.getStatusCode() != IResponse.SC_OK
               && response.getStatusCode() != IResponse.SC_NO_CONTENT
               && response.getStatusCode() != IResponse.SC_CREATED) {
                throw new WebDavException(response);
            }
        } finally {
            if (response != null) {
                response.close();
            }
        }
    }

    /*
     * (non-Javadoc)
     * @see org.guvnor.tools.utils.webdav.IWebDavClient#getResourceVersions(java.lang.String)
     */
    public IResponse getResourceVersions(String resource) throws Exception {
        String apiVer = changeToAPICall(resource) + "?version=all"; //$NON-NLS-1$
        return getResourceInputStream(apiVer);
    }

    /*
     * (non-Javadoc)
     * @see org.guvnor.tools.utils.webdav.IWebDavClient#deleteResource(java.lang.String)
     */
    public void deleteResource(String resource) throws Exception {
        IResponse response = null;
        try {
            ILocator locator = WebDAVFactory.locatorFactory.newLocator(resource);
            response = client.delete(locator, createContext());
            if (response.getStatusCode() != IResponse.SC_NO_CONTENT
               && response.getStatusCode() != IResponse.SC_OK) {
                throw new WebDavException(response);
            }
        } finally {
            if (response != null) {
                response.close();
            }
        }
    }

    /**
     * Converts the WedDav resource path to a Guvnor "API path."
     */
    private String changeToAPICall(String path) {
        return path.replaceFirst("/webdav/", "/api/"); //$NON-NLS-1$ //$NON-NLS-2$
    }
}
