/*
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
 * or http://www.netbeans.org/cddl.txt.
 * 
 * When distributing Covered Code, include this CDDL Header Notice in each file
 * and include the License file at http://www.netbeans.org/cddl.txt.
 * If applicable, add the following below the CDDL Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * The Original Software is NetBeans. The Initial Developer of the Original
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.netbeans.modules.masterfs;

import java.io.IOException;
import java.io.File;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.WeakHashMap;
import org.netbeans.modules.masterfs.filebasedfs.fileobjects.WriteLock;
import org.netbeans.modules.masterfs.filebasedfs.fileobjects.WriteLockFactory;
import org.openide.ErrorManager;
import org.openide.filesystems.FileAlreadyLockedException;
import org.openide.filesystems.FileChangeListener;
import org.openide.filesystems.FileLock;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.netbeans.modules.masterfs.filebasedfs.fileobjects.WriteLockUtils;

/**
 * @author Radek Matous
 */
final class Delegate {
    private FileObject delegate;
    private FileObject secondDelegate;
    private FileChangeListener weakListener;
    final FileChangeListener fListener;
    private Reference lock = null;

    private int attribs = 0;

    static final int SECOND_DELEGATE = 1;
    static final int BEST_DELEGATE = 0;
    private final MasterFileObject mfo;

    Delegate(final FileObject fo, final FileChangeListener fListener, MasterFileObject mfo) {
        this.fListener = fListener;
        this.mfo = mfo;
        set(fo);        
    }

    FileObject get() {
        ResourcePath resPath = mfo.getResource();
        FileObject retVal = delegate;
        if (delegate instanceof InvalidDelegate) {
            retVal = null;       
        } else {
            if (retVal == null) {
                retVal = resolve(resPath, true);
                set(retVal);
            }
        }
        return retVal;
    }

    FileObject get(boolean maybeInvalid) {
        FileObject retVal = get ();
        if (maybeInvalid && retVal == null) {
            retVal = delegate;       
        } 
        return retVal;
    }
    
    static FileObject resolve(final ResourcePath resPath) {
        return resolve(resPath, true);
    }
    
    private static FileObject resolve(final ResourcePath resPath, final  boolean bestDelegate) {
        FileObject retVal;
        String normalizedPath = resPath.getNormalizedPath();
        if (WriteLockUtils.hasActiveLockFileSigns(normalizedPath)) {
            File f = new ResourcePath(normalizedPath).getFile();
            if (WriteLockUtils.hasActiveLockFileSigns(f.getName())) {
                return null;
            }
        }
        
        if (bestDelegate) {
            retVal = MountTable.getDefault().resolveBestDelegate(normalizedPath);
        } else {
            retVal = MountTable.getDefault().resolveSecondDelegate(normalizedPath);
        }

        if (retVal == null) {
            retVal = SpecialDelegates.get(resPath);
        } else {
            // reloads table from file .nbattrs. Changed values
            // may be cached, but previous delegate may change some values
            retVal.getAttribute("--hack--");//NOI18N
        }
        return retVal;
    }

    static FileLock getLockForDelegate(final FileLock testedLock, final FileObject delegate) throws IOException {
        if (testedLock == null)
            throw new IOException();

        if (!(testedLock instanceof FileLockImpl))
            throw new IOException();

        FileLockImpl fLockImpl = (FileLockImpl) testedLock;
        Delegate del = fLockImpl.getDelegate();

        if (del.getFileLockImpl() != fLockImpl) {
            throw new IOException();
        }

        FileLock retVal = fLockImpl.get(delegate);
        if (retVal == null)
            throw new IOException();

        return retVal;
    }
    
    FileObject getPrefered() {
        final FileObject retVal = get();
        FileObject secRetVal = null;
        if (retVal != null) {
            if (retVal.isRoot()) {
                secRetVal = getSecond();
            }
        }
        return (secRetVal != null) ? secRetVal : retVal;
    }

    void reset(final ResourcePath resPath) {
        FileObject lastDelegate = delegate;
        set(null);
        setSecond(null);
        if (get() == null) {
            boolean folder = (lastDelegate != null) ? lastDelegate.isFolder() : false;
            delegate = new InvalidDelegate(resPath, folder);
        } else {
            getSecond();
            attribs = 0;
            try {
                reLock();
            } catch (IOException e) {
                ErrorManager.getDefault().notify(e);
            }
        }
    }

    boolean isValid() {
        return (delegate != null) && (delegate.isValid());
    }

    FileLock lock() throws IOException {
        FileLockImpl lck;
        String path = mfo.getPath ();

        synchronized (Delegate.class) {
            lck = getFileLockImpl();
            if (lck != null) throw new FileAlreadyLockedException(path);
            lock = new WeakReference(lck = new FileLockImpl());
        }

        lck.initLock(mfo.getResource().getFile(), get(), getPrefered());
        return lck;
    }

    boolean hasMountAbleFlag() {
        //TODO: evaluate commented code for Automount mode 
/*
        String path = mfo.getPath ();
        if ((attribs & MNTPOINTFLAG_SUPPRESS) == MNTPOINTFLAG_SUPPRESS) return false;
        if ((attribs & MNTPOINTFLAG) == MNTPOINTFLAG) return true;
        if ((attribs & MNTPOINTFLAG_RESOLVED) == MNTPOINTFLAG_RESOLVED) return false;

        attribs |= MNTPOINTFLAG_RESOLVED;


        if (ProviderCall.isMountAble(path)) {
            attribs |= MNTPOINTFLAG;
            return true;
        }
*/

        return false;
    }

    private void set(final FileObject fo) {
        synchronized (this) {
            FileChangeListener oldListener = weakListener;
            FileObject oldDelegate = delegate;
            if (oldDelegate == fo) return;

            if (oldListener != null && oldDelegate != null)
                oldDelegate.removeFileChangeListener(oldListener);

            delegate = fo;
            if (fo != null) {
                weakListener = FileUtil.weakFileChangeListener(fListener, fo);
                fo.addFileChangeListener(weakListener);
            }
        }
    }

    private void reLock() throws IOException {
        final FileLockImpl lck = getFileLockImpl();
        if (lck != null) {
            lck.lock(delegate);
            lck.lock(secondDelegate);
        }
    }

    private FileLockImpl getFileLockImpl() {
        FileLockImpl lck;
        synchronized (Delegate.class) {
            lck = (FileLockImpl) ((lock == null) ? null : lock.get());
        }
        return lck;
    }

    private void setSecond(final FileObject fo) {
        synchronized (this) {
            secondDelegate = fo;
        }
    }

    private FileObject getSecond() {
        ResourcePath resPath = mfo.getResource();                
        FileObject retVal = secondDelegate;
        if (retVal == null) {
            retVal = resolve(resPath, false);
            setSecond(retVal);
        }
        return retVal;
    }

    private final class FileLockImpl extends FileLock {
        private final Map lockList = new WeakHashMap();
/*
        there is some ugly piece of code around mandatoryLock that ensures that also
        VCS support will use the same locking mechanism as FileBasedFileSystem does
*/
        private FileLock/**ever must be WriteLock*/ mandatoryLock;

        FileLockImpl() {
        }
        
        private void initLock(File file, final FileObject delegate, final FileObject prefered) throws IOException {
            try {
                lock(delegate);
                lock(prefered);
                if (mandatoryLock == null && file != null) {
                    if ((delegate != null && delegate.isData()) || (prefered != null && prefered.isData())) 
                    mandatoryLock = WriteLockFactory.tryLock(file, false);
                }
            } catch (IOException e) {
                releaseLock();
                throw e;                        
            } 
        }
        
        public void releaseLock() {
            if (this.isValid()) {
                super.releaseLock();                                      
                if (mandatoryLock != null) {
                    mandatoryLock.releaseLock(); 
                }
                final Collection locks = lockList.values();
                for (Iterator iterator = locks.iterator(); iterator.hasNext();) {
                    FileLock fLock = (FileLock) iterator.next();
                    fLock.releaseLock();
                }

                if (getCurrentLock () == this) {
                    synchronized (Delegate.class) {
                        lock = null;
                    }
                }
            }
        }

        private FileLock getCurrentLock () {
            return (lock != null) ? (FileLock)lock.get() : null;
        }
        
        private void lock(final FileObject delegate) throws IOException {
            if (delegate != null && this.get(delegate) == null) {
                FileLock fLock = delegate.lock();
                if (mandatoryLock == null && fLock instanceof WriteLock) {
                    mandatoryLock = fLock;
                }
                lockList.put(delegate, fLock);
            }
        }

        private FileLock get(final FileObject delegate) {
            return (FileLock) lockList.get(delegate);
        }

        private Delegate getDelegate() {
            return Delegate.this;
        }
    }
    private static class TestLockException extends IOException {
        Map m = new HashMap ();

        void put (FileLockImpl impl, String msg) {
           m.put(impl, new Exception(msg));
        }

        public void printStackTrace(FileLockImpl tested, FileLockImpl current) {
            Exception exc = (Exception)m.get(tested);
            if (exc != null) {
                System.err.println("-------- "+exc.getLocalizedMessage() + " ----------");
                exc.printStackTrace();
            }
            Exception exc2 = (Exception)m.get(current);
            if (exc2 != null) {
                System.err.println("-------- "+exc2.getLocalizedMessage() + " ----------");
                exc2.printStackTrace();
            }

        }
    }
    
    private static class InvalidDelegate extends InvalidDummy {
        private final boolean isFolder; 

        protected InvalidDelegate(ResourcePath resourcePath, boolean isFolder) {
            super(resourcePath);
            this.isFolder = isFolder;
        }

        public boolean isData() {
            return !isFolder;
        }

        public boolean isFolder() {
            return isFolder;
        }
    }
}
