/*
 * Copyright 2010 Red Hat, Inc. and/or its affiliates.
 *
 * 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;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.text.MessageFormat;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.StringTokenizer;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;
import org.eclipse.webdav.IResponse;
import org.guvnor.tools.Activator;
import org.guvnor.tools.Messages;
import org.guvnor.tools.utils.webdav.IWebDavClient;
import org.guvnor.tools.utils.webdav.ResourceProperties;
import org.guvnor.tools.utils.webdav.WebDavClientFactory;
import org.guvnor.tools.utils.webdav.WebDavException;
import org.guvnor.tools.utils.webdav.WebDavServerCache;
import org.guvnor.tools.views.model.ResourceHistoryEntry;

/**
 * A set of utilities for dealing with (local) Guvnor metadata.
 */
public class GuvnorMetadataUtils {
    
    private static final QualifiedName CONTROLLED_PROJECT_PROPERTY = new QualifiedName(Activator.PLUGIN_ID,"guvnorControlled");
	/**
     * Finds the local Guvnor metadata file associated with a given resource.
     * @param resource The resource to locate metadata for.
     * @return The metadata file for the given resource, null if metadata is not found.
     */
    public static IFile findGuvnorMetadata(IResource resource) {
        IFile res = null;
        IPath dir = resource.getFullPath().removeLastSegments(1);
        IPath mdpath = dir.append(".guvnorinfo").append("." + resource.getName()); //$NON-NLS-1$ //$NON-NLS-2$
        IResource mdResource = resource.getWorkspace().getRoot().findMember(mdpath);
        if (mdResource != null
           && mdResource.exists()
           && mdResource instanceof IFile) {
            res = (IFile)mdResource;
        }
        return res;
    }

    /**
     * Adds a resource to Guvnor.
     * @param repLoc The location of the Guvnor repository
     * @param targetLoc The location in Guvnor to add the resource
     * @param selectedFile The resource to add
     * @return true if the resource is added, false if there is already a resource
     *         with the same name in the given location.
     */
    public static boolean addResourceToGuvnor(String repLoc,
                                             String targetLoc,
                                             IFile selectedFile) {
        boolean res = false;
        try {
            String fullPath = targetLoc + selectedFile.getName();
            IWebDavClient client = WebDavServerCache.getWebDavClient(repLoc);
            if (client == null) {
                client = WebDavClientFactory.createClient(new URL(repLoc));
                WebDavServerCache.cacheWebDavClient(repLoc, client);
            }
            try {
//                res = client.createResource(fullPath, selectedFile.getContents(), false);
                // Hack: When creating a file, if the actual contents are passed first,
                // the client hangs for about 20 seconds when closing the InputStream.
                // Don't know why...
                // But, if the file is created with empty contents, and then the contents
                // set, the operation is fast (less than a couple of seconds)
                res = client.createResource(fullPath, new ByteArrayInputStream(new byte[0]), false);
                if (res) {
                    client.putResource(fullPath, selectedFile.getContents());
                }
            } catch (WebDavException wde) {
                if (wde.getErrorCode() != IResponse.SC_UNAUTHORIZED) {
                    // If not an authentication failure, we don't know what to do with it
                    throw wde;
                }
                boolean retry = PlatformUtils.getInstance().
                                    authenticateForServer(repLoc, client);
                if (retry) {
//                    res = client.createResource(fullPath, selectedFile.getContents(), false);
                    // See Hack note immediately above...
                    res = client.createResource(fullPath, new ByteArrayInputStream(new byte[0]), false);
                    if (res) {
                        client.putResource(fullPath, selectedFile.getContents());
                    }
                }
            }
            if (res) {
                GuvnorMetadataUtils.markCurrentGuvnorResource(selectedFile);
                ResourceProperties resProps = client.queryProperties(fullPath);
                GuvnorMetadataProps mdProps =
                        new GuvnorMetadataProps(selectedFile.getName(),
                                               repLoc,
                                               fullPath, resProps.getLastModifiedDate(),
                                               resProps.getRevision());
                GuvnorMetadataUtils.setGuvnorMetadataProps(selectedFile.getFullPath(), mdProps);
            }
        } catch (Exception e) {
            Activator.getDefault().displayError(IStatus.ERROR, e.getMessage(), e, true);
        }
        return res;
    }

    private static void setProjectPropertyForControlledResource(IFile selectedFile) throws Exception{
        IProject project = selectedFile.getProject();
        String value = project.getPersistentProperty(CONTROLLED_PROJECT_PROPERTY);
        if(value == null){
            project.setPersistentProperty(CONTROLLED_PROJECT_PROPERTY, "true");
        }
    }
    /**
     * Commits changes to Guvnor.
     * @param selectedFile The Guvnor controlled file with pending changes
     */
    public static void commitFileChanges(IFile selectedFile) {
        try {
            GuvnorMetadataProps props = GuvnorMetadataUtils.getGuvnorMetadata(selectedFile);
            IWebDavClient client = WebDavServerCache.getWebDavClient(props.getRepository());
            if (client == null) {
                client = WebDavClientFactory.createClient(new URL(props.getRepository()));
                WebDavServerCache.cacheWebDavClient(props.getRepository(), client);
            }
            ResourceProperties remoteProps = null;
            try {
                remoteProps = client.queryProperties(props.getFullpath());
            } catch (WebDavException wde) {
                if (wde.getErrorCode() != IResponse.SC_UNAUTHORIZED) {
                    // If not an authentication failure, we don't know what to do with it
                    throw wde;
                }
                boolean retry = PlatformUtils.getInstance().
                                    authenticateForServer(props.getRepository(), client);
                if (retry) {
                    remoteProps = client.queryProperties(props.getFullpath());
                }
            }
            if (remoteProps == null) {
                throw new Exception("Could not retrieve server version of " + props.getFullpath()); //$NON-NLS-1$
            }
            // Check to make sure that the version in the repository is the same as the base
            // version for the local copy
            boolean proceed = true;
            if (!props.getRevision().equals(remoteProps.getRevision())) {
                String msg = MessageFormat.format(Messages.getString("overwrite.confirmation"), //$NON-NLS-1$
                                                 new Object[] { selectedFile.getName(),
                                                                remoteProps.getRevision(),
                                                                props.getRevision() });
                Display display = PlatformUI.getWorkbench().getDisplay();
                proceed = MessageDialog.openQuestion(display.getActiveShell(),
                                                    Messages.getString("overwrite.confirmation.caption"), msg); //$NON-NLS-1$

            }
            if (proceed) {
                client.putResource(props.getFullpath(), selectedFile.getContents());
                GuvnorMetadataUtils.markCurrentGuvnorResource(selectedFile);
                ResourceProperties resProps = client.queryProperties(props.getFullpath());
                GuvnorMetadataProps mdProps = GuvnorMetadataUtils.getGuvnorMetadata(selectedFile);
                mdProps.setVersion(resProps.getLastModifiedDate());
                mdProps.setRevision(resProps.getRevision());
                GuvnorMetadataUtils.setGuvnorMetadataProps(selectedFile.getFullPath(), mdProps);
            }
        } catch (Exception e) {
            Activator.getDefault().displayError(IStatus.ERROR, e.getMessage(), e, true);
        }
    }

    /**
     * Finds the local Guvnor metadata file associated with a given resource.
     * @param resource The resource path to locate metadata for.
     * @return The metadata file for the given resource, null if metadata is not found.
     */
    public static IFile findGuvnorMetadata(IPath resource) {
        IFile res = null;
        IPath dir = resource.removeLastSegments(1);
        IPath mdpath = dir.append(".guvnorinfo").append("." + resource.lastSegment()); //$NON-NLS-1$ //$NON-NLS-2$
        IResource mdResource = Activator.getDefault().getWorkspace().
                                            getRoot().findMember(mdpath);
        if (mdResource != null
           && mdResource.exists()
           && mdResource instanceof IFile) {
            res = (IFile)mdResource;
        }
        return res;
    }

    public static boolean isGuvnorControlledResource(IResource resource) {
        return findGuvnorMetadata(resource) != null;
    }

    public static GuvnorMetadataProps loadGuvnorMetadata(IFile mdFile) throws Exception {
    	
        setProjectPropertyForControlledResource(mdFile);
    	
        if(!mdFile.isTeamPrivateMember()){
            mdFile.setTeamPrivateMember(true);
            mdFile.setDerived(true);
            if(mdFile.getParent().getName().endsWith(".guvnorinfo")){
                mdFile.getParent().setTeamPrivateMember(true);
                mdFile.getParent().setDerived(true);
            }
        }

        Properties props = new Properties();
        InputStream contents = mdFile.getContents();

        try{
            props.load(contents);
            return new GuvnorMetadataProps(props.getProperty("filename"), //$NON-NLS-1$
                                       props.getProperty("repository"), //$NON-NLS-1$
                                       props.getProperty("fullpath"), //$NON-NLS-1$
                                       props.getProperty("lastmodified"), //$NON-NLS-1$
                                       props.getProperty("revision")); //$NON-NLS-1$
        }
        finally{
            contents.close();
        }
    }

    public static GuvnorMetadataProps getGuvnorMetadata(IResource resource) throws Exception {
        IFile mdFile = findGuvnorMetadata(resource);
        if (mdFile == null) {
            return null;
        }
        return loadGuvnorMetadata(mdFile);
    }

    public static void writeGuvnorMetadataProps(File mdFile,
                                             GuvnorMetadataProps mdProps) throws Exception {
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(mdFile);
            Properties props = new Properties();
            props.put("repository",     mdProps.getRepository()); //$NON-NLS-1$
            props.put("fullpath",         mdProps.getFullpath()); //$NON-NLS-1$
            props.put("filename",         mdProps.getFilename()); //$NON-NLS-1$
            props.put("lastmodified",     mdProps.getVersion()); //$NON-NLS-1$
            props.put("revision",        mdProps.getRevision()); //$NON-NLS-1$
            props.store(fos, null);
            fos.flush();
        } finally {
            if (fos != null) {
                fos.close();
            }
        }
    }

    public static void setGuvnorMetadataProps(IPath controlledFile,
                                             GuvnorMetadataProps mdProps) throws Exception {
        IWorkspaceRoot root = Activator.getDefault().getWorkspace().getRoot();
        IFolder mdFolder = root.getFolder(
                            controlledFile.removeLastSegments(1).append(".guvnorinfo")); //$NON-NLS-1$
        if (!mdFolder.exists()) {
            mdFolder.create(true, true, null);
            mdFolder.setTeamPrivateMember(true);
            mdFolder.setDerived(true);
        }

        IFile mdFile = root.getFile(
                        mdFolder.getFullPath().append("." + controlledFile.lastSegment())); //$NON-NLS-1$

        Properties props = new Properties();
        if (!mdFile.exists()) {
            mdFile.create(new ByteArrayInputStream(new byte[] {}), true, null);
            mdFile.setTeamPrivateMember(true);
            mdFile.setDerived(true);
        } else {
            props.load(mdFile.getContents());
        }


        if (mdProps.getRepository() != null) {
            props.put("repository", mdProps.getRepository()); //$NON-NLS-1$
        }
        if (mdProps.getFullpath() != null) {
            props.put("fullpath", mdProps.getFullpath()); //$NON-NLS-1$
        }
        if (mdProps.getFilename() != null) {
            props.put("filename", mdProps.getFilename()); //$NON-NLS-1$
        }
        if (mdProps.getVersion() != null) {
            props.put("lastmodified", mdProps.getVersion()); //$NON-NLS-1$
        }
        if (mdProps.getRevision() != null) {
            props.put("revision", mdProps.getRevision()); //$NON-NLS-1$
        }
        OutputStream os = null;
        try {
            os = new FileOutputStream(new File(mdFile.getLocation().toOSString()));
            props.store(os, null);
            os.flush();
        } finally {
            if (os != null) {
                os.close();
            }
        }
        mdFolder.refreshLocal(IResource.DEPTH_INFINITE, null);
    }

    public static IPath createGuvnorMetadataLocation(String rootPath) throws Exception {
        IPath path = new Path(rootPath + File.separator + ".guvnorinfo"); //$NON-NLS-1$
        if (!path.toFile().exists()) {
            if (!path.toFile().mkdir()) {
                throw new Exception("Could not create directory " + path.toOSString()); //$NON-NLS-1$
            }
        }
        return path;
    }

    public static File getGuvnorMetadataFile(String path, String fname) {
        return new File(path + File.separator + "." + fname); //$NON-NLS-1$
    }

    public static IFile getGuvnorControlledResource(IResource resource) throws Exception {
        IFile res = null;
        if (resource instanceof IFile) {
            Properties props = new Properties();
            props.load(((IFile)resource).getContents());
            if (props.getProperty("filename") != null) { //$NON-NLS-1$
                res = (IFile)Activator.getDefault().getWorkspace().
                                getRoot().findMember(resource.getFullPath().
                                        removeLastSegments(2).append(props.getProperty("filename"))); //$NON-NLS-1$
            }
        }
        return res;
    }

    public static boolean isGuvnorMetadata(IResource resource) {
        return resource.getFullPath().removeLastSegments(1).
                            toOSString().endsWith(".guvnorinfo"); //$NON-NLS-1$
    }

    public static void markCurrentGuvnorResource(IResource resource) throws CoreException {
        resource.setPersistentProperty(generateQualifiedName("version"), "current"); //$NON-NLS-1$ //$NON-NLS-2$
    }

    public static void markExpiredGuvnorResource(IResource resource) throws CoreException {
        resource.setPersistentProperty(generateQualifiedName("version"), "expired"); //$NON-NLS-1$ //$NON-NLS-2$
    }

    public static String getGuvnorResourceProperty(IResource resource) throws CoreException {
        return resource.getPersistentProperty(generateQualifiedName("version")); //$NON-NLS-1$
    }

    public static boolean isGuvnorResourceCurrent(IResource resource) throws CoreException {
        String ver = resource.getPersistentProperty(generateQualifiedName("version")); //$NON-NLS-1$
        if (ver == null) {
            return false;
        }
        return ver.equals("current"); //$NON-NLS-1$
    }

    private static QualifiedName generateQualifiedName(String attr) {
        return new QualifiedName("org.guvnor.tools", attr);  //$NON-NLS-1$
    }

    public static ResourceHistoryEntry[] parseHistoryProperties(Properties entryProps) {
        ResourceHistoryEntry[] entries = new ResourceHistoryEntry[entryProps.size()];
        Enumeration<Object> en = entryProps.keys();
        for (int i = 0; i < entryProps.size(); i++) {
            String oneRevision = (String)en.nextElement();
            String val = entryProps.getProperty(oneRevision);
            StringTokenizer tokens = new StringTokenizer(val, ","); //$NON-NLS-1$
            String verDate = null;
            String author = null;
            String comment = null;
            try {
                verDate = tokens.nextToken();
                author = tokens.nextToken();
                comment = tokens.nextToken();
            } catch (NoSuchElementException e) {
                // Don't care if some fields are missing
            }
            entries[i] = new ResourceHistoryEntry(oneRevision, verDate, author, comment);
        }
        return entries;
    }
}
