package org.netbeans.modules.nodejs;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.codehaus.jackson.map.ObjectMapper;
import org.junit.After;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
import org.netbeans.modules.nodejs.api.NodeJSUtils;
import org.netbeans.modules.nodejs.api.Stubs;
import org.netbeans.modules.nodejs.node.LibrariesChildFactory;
import org.netbeans.modules.nodejs.node.ProjectNodeKey;
import org.netbeans.spi.project.ProjectState;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.Lookup;

/**
 *
 * @author Tim Boudreau
 */
public class StubsTest {
    File prjdir;
    FileObject prjFo;
    NodeJSProject prj;
    FileObject booDir;

    NodeJSProject booProject;

    @Before
    public void setup () throws Exception {
        File tmp = new File( System.getProperty( "java.io.tmpdir" ) );
        prjdir = new File( tmp, "test-" + System.currentTimeMillis() );
        assertTrue( prjdir.getAbsolutePath(), prjdir.mkdirs() );
        for (String name : new String[]{"index.js", "other.js", "package.json"}) {
            copy( name );
        }
        prjFo = FileUtil.toFileObject( prjdir );
        prj = new NodeJSProject( prjFo, new PS() );
        NodeJSProjectFactory fac = Lookup.getDefault().lookup( NodeJSProjectFactory.class );
        fac.register( prj );
        File modulesdir = new File( prjdir, "node_modules" );
        assertTrue( modulesdir.getAbsolutePath(), modulesdir.mkdir() );

        File boo = new File( modulesdir, "boo" );
        assertTrue( boo.getAbsolutePath(), boo.mkdir() );

        copy( "package_1.json", "package.json", boo );
        copy( "boo.js", "boo.js", boo );

        booDir = FileUtil.toFileObject( boo );

        booProject = new NodeJSProject( booDir, new PS() );
        fac.register( booProject );
    }

    @After
    public void teardown () throws IOException {
        System.out.println( "PROJECT FO " + prjFo.getPath() );
        prjFo.delete();
    }

    private void copy ( String name ) throws IOException {
        copy( name, name, prjdir );
    }

    private void copy ( String name, String destName, File prjdir ) throws IOException {
        try (InputStream in = StubsTest.class.getResourceAsStream( name )) {
            assertNotNull(in);
            File f = new File( prjdir, destName );
            assertTrue( f.getAbsolutePath(), f.createNewFile() );
            try (FileOutputStream out = new FileOutputStream( f )) {
                FileUtil.copy( in, out );
            }
        }
    }

    @Test
    public void test () throws Exception {
        Stubs stubs = Stubs.getDefault();
        assertNotNull( stubs );
        FileObject actualHttp = stubs.getStubs( null ).getRoot().getFileObject( "http.js" );
        assertNotNull( actualHttp );

        NodeJSUtils utils = NodeJSUtils.getDefault();
        assertNotNull( utils );
        FileObject index = prj.getProjectDirectory().getFileObject( "index.js" );
        assertNotNull( index );
        FileObject other = utils.resolve( "./other", index );
        assertNotNull( other );
        assertEquals( "other.js", other.getNameExt() );
        assertEquals( index.getParent(), other.getParent() );

        FileObject http = utils.resolve( "http", index );
        assertNotNull( http );

        assertEquals( actualHttp, http );

        LibrariesChildFactory f = new LibrariesChildFactory( prj );
        Set<String> libs = new HashSet<>();
        for (ProjectNodeKey key : f.libraries()) {
            libs.add( key.toString() );
            if (!"boo".equals( key.toString() )) {
                assertTrue( key.isBuiltIn() );
            } else {
                assertFalse( key.isBuiltIn() );
            }
        }
        assertTrue( libs.contains( "util" ) );
        assertTrue( libs.contains( "fs" ) );
        assertTrue( libs.contains( "http" ) );
        assertFalse( libs.contains( "url" ) );
        assertTrue( libs.contains( "boo" ) );

        FileObject boo = utils.resolve( "boo", index );
        assertNotNull( boo );
        assertEquals( booDir.getFileObject( "boo.js" ), boo );

        NodeJSProjectProperties props = prj.getLookup().lookup( NodeJSProjectProperties.class );
        assertNotNull( props );
        assertEquals( "testproject", props.getDisplayName() );

        assertEquals( "joe@mail.example", props.getAuthorEmail() );

        // #8 - Ensure the main file path is relative
        props = booProject.getLookup().lookup( NodeJSProjectProperties.class );

        FileObject fo = props.getMainFile();
        assertEquals("boo", fo.getName());

        props.setMainFile( booProject.getProjectDirectory().getFileObject( "moo.js" ));

        fo = props.getMainFile();
        assertNotNull("Main file should be moo.js but was null", fo);
        assertEquals("moo", fo.getName());

        FileObject booMetadata = booProject.getProjectDirectory().getFileObject("package.json");
        try (InputStream in = booMetadata.getInputStream()) {
            ObjectMapper m = new ObjectMapper();
            Map<String,Object> map = m.readValue( in, Map.class);
            String path = (String) map.get("main");
            assertNotNull(path);
            assertEquals("./boo.js", path);
        }

        props.setAuthor( "" );
        props.setAuthorEmail( "" );
        props.save();
        try (InputStream in = booMetadata.getInputStream()) {
            ObjectMapper m = new ObjectMapper();
            Map<String,Object> map = m.readValue( in, Map.class);
            assertFalse( "If author fields are clear, empty entries "
                    + "should not remain in package.json", map.containsKey( "author" ));
        }
        // Test libraries resolver
        LibrariesResolver resolver = booProject.getLookup().lookup(LibrariesResolver.class);
        WaitForChange cl = new WaitForChange();
        resolver.addChangeListener( cl );
        boolean initiallyMissing = resolver.hasMissingLibraries();
        cl.await();
        assertFalse(initiallyMissing);
        assertFalse(resolver.hasMissingLibraries());
        
        ProjectMetadata md = booProject.getLookup().lookup( ProjectMetadata.class );
        Map<String,Object> deps = new HashMap<>();
        cl = new WaitForChange();
        resolver.addChangeListener( cl );
        deps.put("blurt", "1.0.0");
        md.addMap( "dependencies", deps );
        cl.await();
        assertTrue(resolver.hasMissingLibraries());
        
        FileObject node_modules = booProject.getProjectDirectory().createFolder( "node_modules" );
        cl = new WaitForChange();
        resolver.addChangeListener( cl );
        FileObject blurt = node_modules.createFolder( "blurt" );
        cl.await();
        assertFalse( resolver.hasMissingLibraries() );
    }
    
    static class WaitForChange implements ChangeListener {
        final CountDownLatch latch = new CountDownLatch(1);
        
        void await() throws InterruptedException {
            latch.await( 5, TimeUnit.SECONDS );
        }

        @Override
        public void stateChanged ( ChangeEvent e ) {
            latch.countDown();
        }
        
    }

    private static class PS implements ProjectState {

        @Override
        public void markModified () {
        }

        @Override
        public void notifyDeleted () throws IllegalStateException {
        }
    }

}

@@ -2188,32 +2188,73 @@
 %22boo.js%22, boo );
+%0A        copy( %22boo.js%22, %22moo.js%22, boo );
 %0A%0A        booDir
@@ -2509,24 +2509,26 @@
 etPath() );%0A
+//
         prjF

Fix test