/*
 * 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.
 */

// XXX rewrite to NbModuleSuite

package org.netbeans.core.validation;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.Map;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import org.netbeans.junit.NbTestCase;

/** Tests for resources contained in modules.
 *
 * @author radim
 */
public class ResourcesTest extends NbTestCase {

    // TODO the idea is to check for duplicates
    //      for patterns that are not effective in NB environment
    private Logger LOG;

    public ResourcesTest(String testName) {
        super(testName);
    }

    @Override
    protected Level logLevel() {
        return Level.INFO;
    }

    protected void setUp() throws Exception {
        LOG = Logger.getLogger("TEST-" + getName());
        
        super.setUp();
    }

    /** If package contains only one resource it is not too frendly to our classloaders.
     * Generally not a big problem OTOH we want to avoid this if this is simple.
     */
    public void testOneInPackage() throws Exception {
        SortedSet<Violation> violations = new TreeSet<Violation>();
        for (File f: org.netbeans.core.startup.Main.getModuleSystem().getModuleJars()) {
            // check JAR files only
            if (!f.getName().endsWith(".jar"))
                continue;
            
            // ignore branding
            if (f.getName().endsWith("_nb.jar"))
                continue;
            
            // a lot of alarms for 3rd party JARs
            if (f.getName().contains("modules/ext/"))
                continue;
            
            SortedMap<String, Integer> resourcesPerPackage = new TreeMap<String, Integer>();
            JarFile jar = new JarFile(f);
            Enumeration<JarEntry> entries = jar.entries();
            JarEntry entry;
            int entryCount = 0;
            while (entries.hasMoreElements()) {
                entry = entries.nextElement();
                if (entry.isDirectory())
                    continue;
                
                String name = entry.getName();
                String prefix = (name.lastIndexOf('/') >= 0)?
                    name.substring(0, name.lastIndexOf('/')): "";
                if (prefix.startsWith("META-INF")
                || prefix.startsWith("1.0/")
                || prefix.startsWith("com/")
                || prefix.startsWith("javax/")
                || prefix.startsWith("freemarker/")
                || prefix.startsWith("org/apache/tomcat")
                || prefix.startsWith("org/apache/lucene")
                || prefix.startsWith("org/w3c/")
//                || prefix.startsWith("")
                || prefix.startsWith("org/netbeans/modules/openide/actions")
                || prefix.startsWith("org/netbeans/modules/openide/awt")
                || prefix.startsWith("org/netbeans/modules/openide/windows")
                || prefix.startsWith("org/openide/explorer/propertysheet") // in deprecated core/settings
                || prefix.startsWith("org/openide/io")
                || prefix.startsWith("org/netbeans/api")
                || prefix.startsWith("org/netbeans/spi")
                || prefix.startsWith("org/netbeans/core/execution/beaninfo")
                || prefix.startsWith("org/netbeans/modules/web/monitor")
                || prefix.matches("org/netbeans/.*/[as]pi.*")
                        ) {
                    continue;
                }
                
                entryCount++;
                Integer count = resourcesPerPackage.get(prefix);
                if (count != null) {
                    resourcesPerPackage.put(prefix, count+1);
                }
                else {
                    resourcesPerPackage.put(prefix, 1);
                }
            }
            if (entryCount > 1) { // filter library wrappes (they have only Bundle.properties)
                for (Map.Entry<String, Integer> pkgInfo: resourcesPerPackage.entrySet()) {
                    if (pkgInfo.getValue().equals(1)) {
                        violations.add(new Violation(pkgInfo.getKey(), jar.getName(), " has package with just one resource"));
                    }
                }
            }
        }
        if (!violations.isEmpty()) {
            StringBuilder msg = new StringBuilder();
            msg.append("Some JARs in IDE contains sparsely populated packages ("+violations.size()+"):\n");
            for (Violation viol: violations) {
                msg.append(viol).append('\n');
            }
            fail(msg.toString());
        }
        //                    assertTrue (entry.toString()+" should have line number table", v.foundLineNumberTable());
    }
    
    /** Historically we had problems with some images.
     */
    public void testImageCanBeRead() throws Exception {
        ImageIO.setUseCache(false);
        ImageReader PNG_READER = ImageIO.getImageReadersByMIMEType("image/png").next();
        ImageReader GIF_READER = ImageIO.getImageReadersByMIMEType("image/gif").next();
        
        SortedSet<Violation> violations = new TreeSet<Violation>();
        for (File f: org.netbeans.core.startup.Main.getModuleSystem().getModuleJars()) {
            // check JAR files only
            if (!f.getName().endsWith(".jar"))
                continue;
            
            JarFile jar = new JarFile(f);
            Enumeration<JarEntry> entries = jar.entries();
            JarEntry entry;
            BufferedImage img;
            while (entries.hasMoreElements()) {
                entry = entries.nextElement();
                if (entry.isDirectory())
                    continue;
                
                String name = entry.getName();
                if (!name.endsWith(".gif")
                        && !name.endsWith(".png")) {
                    continue;
                }
                try {
                    img = ImageIO.read(jar.getInputStream(entry));
                }
                catch (IOException ioe) {
                    violations.add(new Violation(name, jar.getName(), " cannot be read"));
                    continue;
                }
                catch (IndexOutOfBoundsException ioobe) {
                    violations.add(new Violation(name, jar.getName(), " cannot be read"));
                    continue;
                }
                // more aggressive way - use reader matching to file extension
                if (name.endsWith(".png")) {
                    ImageInputStream stream = ImageIO.createImageInputStream(jar.getInputStream(entry));
                    ImageReadParam param = PNG_READER.getDefaultReadParam();
                    try {
                        PNG_READER.setInput(stream, true, true);
                        img = PNG_READER.read(0, param);
                    }
                    catch (IOException ioe1) {
                        violations.add(new Violation(name, jar.getName(), "Not a PNG image"));
                        continue;
                    }
                    stream.close();
                }
                else if (name.endsWith(".gif")) {
                    ImageInputStream stream = ImageIO.createImageInputStream(jar.getInputStream(entry));
                    ImageReadParam param = GIF_READER.getDefaultReadParam();
                    try {
                        GIF_READER.setInput(stream, true, true);
                        img = GIF_READER.read(0, param);
                    }
                    catch (IOException ioe1) {
                        violations.add(new Violation(name, jar.getName(), "Not a GIF image"));
                        continue;
                    }
                    stream.close();
                }
            }
        }
        if (!violations.isEmpty()) {
            StringBuilder msg = new StringBuilder();
            msg.append("Some images in IDE have problems ("+violations.size()+"):\n");
            for (Violation viol: violations) {
                msg.append(viol).append('\n');
            }
            fail(msg.toString());
        }
        //                    assertTrue (entry.toString()+" should have line number table", v.foundLineNumberTable());
    }
    
    private static class Violation implements Comparable<Violation> {
        String entry;
        String jarFile;
        String comment;
        Violation(String entry, String jarFile, String comment) {
            this.entry = entry;
            this.jarFile = jarFile;
            this.comment = comment;
        }
    
        public int compareTo(Violation v2) {
            String second = v2.entry + v2.jarFile;
            return (entry +jarFile).compareTo(second);
        }
        
        @Override
        public String toString() {
            return comment + ": " + entry+" in "+jarFile;
        }
    }

    /** Too large or too small (empty) files are suspicious.
     *  There should be just couple of them: splash image
     */
    public void testUnusualFileSize() throws Exception {
        SortedSet<Violation> violations = new TreeSet<Violation>();
        for (File f: org.netbeans.core.startup.Main.getModuleSystem().getModuleJars()) {
            // check JAR files only
            if (!f.getName().endsWith(".jar"))
                continue;
            
            JarFile jar = new JarFile(f);
            Enumeration<JarEntry> entries = jar.entries();
            JarEntry entry;
            BufferedImage img;
            while (entries.hasMoreElements()) {
                entry = entries.nextElement();
                if (entry.isDirectory())
                    continue;
                
                long len = entry.getSize();
                if (len >= 0 && len < 10) {
                    violations.add(new Violation(entry.getName(), jar.getName(), " is too small ("+len+" bytes)"));
                }
                if (len >= 200 * 1024) {
                    violations.add(new Violation(entry.getName(), jar.getName(), " is too large ("+len+" bytes)"));
                }
            }
        }
        if (!violations.isEmpty()) {
            StringBuilder msg = new StringBuilder();
            msg.append("Some files have extreme size ("+violations.size()+"):\n");
            for (Violation viol: violations) {
                msg.append(viol).append('\n');
            }
            fail(msg.toString());
        }
    }
    
    /** Scan for accidentally commited files that get into product
     */
    public void testInappropraiteEntries() throws Exception {
        SortedSet<Violation> violations = new TreeSet<Violation>();
        for (File f: org.netbeans.core.startup.Main.getModuleSystem().getModuleJars()) {
            // check JAR files only
            if (!f.getName().endsWith(".jar"))
                continue;
            
            if (!f.getName().endsWith("cssparser-0-9-4-fs.jar")) // #108644
                continue;
            
            JarFile jar = new JarFile(f);
            Enumeration<JarEntry> entries = jar.entries();
            JarEntry entry;
            BufferedImage img;
            while (entries.hasMoreElements()) {
                entry = entries.nextElement();
                if (entry.isDirectory())
                    continue;
                
                if (entry.getName().endsWith("Thumbs.db")) {
                    violations.add(new Violation(entry.getName(), jar.getName(), " should not be in module JAR"));
                }
                if (entry.getName().contains("nbproject/private")) {
                    violations.add(new Violation(entry.getName(), jar.getName(), " should not be in module JAR"));
                }
            }
        }
        if (!violations.isEmpty()) {
            StringBuilder msg = new StringBuilder();
            msg.append("Some files does not belong to module JARs ("+violations.size()+"):\n");
            for (Violation viol: violations) {
                msg.append(viol).append('\n');
            }
            fail(msg.toString());
        }
    }
}
