/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.commons.jci.monitor;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;

import junit.framework.TestCase;

import org.apache.commons.io.FileUtils;
import org.apache.commons.jci.listeners.AbstractFilesystemAlterationListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


/**
 * 
 * @author tcurdt
 */
public final class FilesystemAlterationMonitorTestCase extends TestCase {

    private final Log log = LogFactory.getLog(FilesystemAlterationMonitorTestCase.class);

    private FilesystemAlterationMonitor fam;
    private MyFilesystemAlterationListener listener;

    private File directory;

    
    @Override
    protected void setUp() throws Exception {
        directory = createTempDirectory();
        assertTrue(directory.exists());
        assertTrue(directory.isDirectory());
    }
    
    @Override
    protected void tearDown() throws Exception {
        FileUtils.deleteDirectory(directory);
    }
    
    
    protected File createDirectory( final String pName ) throws Exception {
        final File newDirectory = new File(directory, pName);
        assertTrue(newDirectory.mkdir());
        assertTrue(newDirectory.exists());
        assertTrue(newDirectory.isDirectory());
        return newDirectory;
    }
    
    protected File writeFile( final String pName, final byte[] pData ) throws Exception {
        final File file = new File(directory, pName);
        final File parent = file.getParentFile();
        if (!parent.mkdirs() && !parent.isDirectory()) {
            throw new IOException("could not create" + parent);
        }
        
        log.debug("writing file " + pName + " (" + pData.length + " bytes)");
        
        final FileOutputStream os = new FileOutputStream(file);
        os.write(pData);
        os.close();
        
        assertTrue(file.exists());
        assertTrue(file.isFile());
        
        return file;
    }

    protected File writeFile( final String pName, final String pText ) throws Exception {
        final File file = new File(directory, pName);
        final File parent = file.getParentFile();
        if (!parent.mkdirs() && !parent.isDirectory()) {
            throw new IOException("could not create" + parent);
        }
        log.debug("writing " + file);
        final FileWriter writer = new FileWriter(file);
        writer.write(pText);
        writer.close();
        
        assertTrue(file.exists());
        assertTrue(file.isFile());
        
        return file;
    }

    protected File createTempDirectory() throws IOException {
        final File tempFile = File.createTempFile("jci", null);
        
        if (!tempFile.delete()) {
            throw new IOException();
        }
        
        if (!tempFile.mkdir()) {
            throw new IOException();
        }
        
        return tempFile;         
    }


    protected void delay() {
        try {
            Thread.sleep(1500);
        } catch (final InterruptedException e) {
        }
    }


    
    private static class MyFilesystemAlterationListener extends AbstractFilesystemAlterationListener {
    }

    private void start() throws Exception {
        fam = new FilesystemAlterationMonitor();
        listener = new MyFilesystemAlterationListener();
        fam.addListener(directory, listener);
        fam.start();
        listener.waitForFirstCheck();
    }
    
    private void stop() {
        fam.stop();
    }
    
    public void testListenerDoublication() throws Exception {
        fam = new FilesystemAlterationMonitor();
        listener = new MyFilesystemAlterationListener();
        
        fam.addListener(directory, listener);
        assertEquals(1, fam.getListenersFor(directory).length);
        
        fam.addListener(directory, listener); 
        assertEquals(1, fam.getListenersFor(directory).length);
        
        fam.removeListener(listener);
        assertEquals(0, fam.getListenersFor(directory).length);
}

    public void testDirectoryDoublication() throws Exception {
        fam = new FilesystemAlterationMonitor();

        fam.addListener(directory, new MyFilesystemAlterationListener()); 
        assertEquals(1, fam.getListenersFor(directory).length);
        
        fam.addListener(directory, new MyFilesystemAlterationListener()); 
        assertEquals(2, fam.getListenersFor(directory).length);
    }

    public void testCreateFileDetection() throws Exception {
        start();
        
        writeFile("file", "file");
        
        listener.waitForCheck();
        
        assertEquals(1, listener.getCreatedFiles().size());
        
        stop();
    }

    public void testTimeout() throws Exception {
    	listener = new MyFilesystemAlterationListener();
    	
    	try {
        	listener.waitForFirstCheck();
        	fail("should be an timeout");
        } catch(Exception e) {
        	assertEquals("timeout", e.getMessage());
        }

        start();

        try {
        	listener.waitForEvent();
        	fail("should be an timeout");
        } catch(Exception e) {
        	assertEquals("timeout", e.getMessage());
        }
        
        stop();

        try {
        	listener.waitForCheck();
        	fail("should be an timeout");
        } catch(Exception e) {
        	assertEquals("timeout", e.getMessage());
        }
    
    }

    public void testCreateDirectoryDetection() throws Exception {
        start();

        createDirectory("dir");
        
        listener.waitForCheck();
        
        assertEquals(1, listener.getCreatedDirectories().size());
        
        stop();
    }

    public void testDeleteFileDetection() throws Exception {
        start();

        final File file = writeFile("file", "file");

        assertTrue("file should exist", file.exists());
        
        listener.waitForCheck();
        
        assertEquals("expecting 1 file created", 1, listener.getCreatedFiles().size());
        //assertEquals("expecting 0 directories changed", 0, listener.getChangedDirectories().size()); // todo investigate why this is failing on Windows
        
        file.delete();
        assertFalse("file should not exist", file.exists());

        listener.waitForCheck();
        
        assertEquals("expecting 1 file deleted", 1, listener.getDeletedFiles().size());
        
        stop();        
    }
    public void testDeleteDirectoryDetection() throws Exception {
        start();

        final File dir = createDirectory("dir");
        createDirectory("dir/sub");
        final File file = writeFile("dir/sub/file", "file");

        listener.waitForCheck();
        
        assertEquals(2, listener.getCreatedDirectories().size());
        assertEquals(1, listener.getCreatedFiles().size());

        delay();
        
        FileUtils.deleteDirectory(dir);
        assertTrue(!dir.exists());
        assertTrue(!file.exists());

        listener.waitForCheck();
        
        assertEquals(2, listener.getDeletedDirectories().size());
        assertEquals(1, listener.getDeletedFiles().size());

        stop();
    }

    public void testModifyFileDetection() throws Exception {
        start();

        writeFile("file", "file");
        
        listener.waitForCheck();
        
        assertEquals(1, listener.getCreatedFiles().size());

        delay();

        writeFile("file", "changed file");

        listener.waitForCheck();
        
        assertEquals(1, listener.getChangedFiles().size());
        
        stop();
    }

    public void testCreatingLocalDirectoryChangesLastModified() throws Exception {
        final long modified = directory.lastModified();

        delay();
        
        createDirectory("directory");

        delay();
               
        assertTrue(directory.lastModified() != modified);
    }

    public void testCreatingLocalFileChangesLastModified() throws Exception {
        final long modified = directory.lastModified();

        delay();
        
        writeFile("file", "file");

        delay();

        assertTrue(directory.lastModified() != modified);
    }

    public void testCreatingSubDirectoryChangesLastModified() throws Exception {
        createDirectory("dir");

        final long modified = directory.lastModified();

        delay();

        createDirectory("dir/sub");

        assertTrue(directory.lastModified() == modified);
    }

    public void testCreatingFileInSubDirectoryChangesLastModified() throws Exception {
        createDirectory("dir");

        final long modified = directory.lastModified();

        delay();
                
        writeFile("dir/file", "file");

        assertTrue(directory.lastModified() == modified);
    }
    
    public void testInterval() throws Exception {

        final long interval = 1000;

        start();
        fam.setInterval(interval);

        listener.waitForCheck();
        long t1 = System.currentTimeMillis();

        listener.waitForCheck();
        long t2 = System.currentTimeMillis();
        
        long diff = t2-t1;
        
        // interval should be at around the same interval
        assertTrue("the interval was set to " + interval + " but the time difference was " + diff, (diff > (interval-50)) && (diff < (interval+50)));
        
        stop();
    }    
}
