/*
 * 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.netbeans.nbbuild;

import java.awt.image.BufferedImage;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.MessageFormat;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.imageio.ImageIO;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;

/**
 *
 * @author Jaroslav Tulach
 */
public class PrintIcon extends Task {
    private FileSet first;
    private FileSet second;
    
    
    
    
    /** Creates a new instance of PrintIcon */
    public PrintIcon() {
    }

    private File duplicates;
    public void setDuplicates(File f) {
        duplicates = f;
    }

    private File difference;
    public void setDifference(File f) {
        difference = f;
    }
    
    /**
     * 
     * @return 
     */
    public FileSet createFirstPool() {
        if (first != null) {
            throw new BuildException();
        }
        first = new FileSet();
        return first;
    }
    
    public FileSet createSecondPool() {
        if (second != null) {
            throw new BuildException();
        }
        second = new FileSet();
        return second;
    }
    
    @Override
    public void execute() throws BuildException {
        if (first == null) {
            throw new BuildException("You need to specify firstpool element for this task!"); // NOI18N
        }
        
        try {
            
            SortedSet<IconInfo> firstSet = new TreeSet<>();
            for (String f : first.getDirectoryScanner(getProject()).getIncludedFiles()) {
                File baseDir = first.getDir(getProject());
                File file = new File(baseDir, f);
                firstSet.add(new IconInfo(file.toURI().toURL(), getProject()));
            }

            SortedSet<IconInfo> sndSet = new TreeSet<>();
            if (second != null) {
                for (String f : second.getDirectoryScanner(getProject()).getIncludedFiles()) {
                    File baseDir = second.getDir(getProject());
                    File file = new File(baseDir, f);
                    sndSet.add(new IconInfo(file.toURI().toURL(), getProject()));
                }
            }
            
            if (duplicates != null) {
                Set<IconInfo> both = new TreeSet<>(firstSet);
                both.addAll(sndSet);
                
                try (BufferedWriter os = new BufferedWriter(new FileWriter(duplicates))) {
                    IconInfo prev = null;
                    boolean prevPrinted = false;
                    for (IconInfo info : both) {
                        IconInfo p = prev;
                        prev = info;
                        if (p == null || p.hash != info.hash) {
                            prevPrinted = false;
                            continue;
                        }
                        
                        if (!prevPrinted) {
                            os.write(p.toString());
                            os.newLine();
                            prevPrinted = true;
                        }
                        
                        os.write(info.toString());
                        os.newLine();
                    }
                }
            }
            if (difference != null) {
                SortedSet<IconInfo> union = new TreeSet<>(firstSet);
                union.addAll(sndSet);
                
                try (BufferedWriter os = new BufferedWriter(new FileWriter(difference))) {
                    for (IconInfo info : union) {
                        if (!contains(firstSet, info.hash)) {
                            os.write('+');
                            os.write(info.toString());
                            os.newLine();
                            continue;
                        }
                        if (!contains(sndSet, info.hash)) {
                            os.write('-');
                            os.write(info.toString());
                            os.newLine();
                            continue;
                        }
                    }
                }
            }
            
        } catch (IOException ex) {
            throw new BuildException(ex);
        }
        
    }
    
    private static boolean contains(SortedSet<IconInfo> set, int hashCode) {
        IconInfo fake = new IconInfo("", "", hashCode);
        Set<IconInfo> greaterOrEqual = set.tailSet(fake);
        if (greaterOrEqual.isEmpty()) {
            return false;
        }
        IconInfo first = greaterOrEqual.iterator().next();
        return hashCode == first.hash;
    }

    static final int hash(Throwable t) {
        String msg = t.getMessage();
        if (msg != null) {
            return 7 + msg.hashCode();
        }
        return 5 + t.getClass().hashCode();
    }
    
    private static final class IconInfo implements Comparable<IconInfo> {
        final String name;
        final String path;
        final int hash;
        
        public IconInfo(URL from, Project p) throws IOException {
            this.path = from.toExternalForm();
            int last = this.path.lastIndexOf('/');
            assert last >= 0;
            this.name = this.path.substring(last + 1);
            
            p.log("Parsing " + from, Project.MSG_VERBOSE);
            BufferedImage image;
            int _hash;
            try {
                try (InputStream is = from.openStream()) {
                    image = ImageIO.read(is);
                }
                int w = image.getWidth();
                int h = image.getHeight();
                _hash = w * 3 + h * 7;
                
                for (int i = 0; i < w; i++) {
                    for (int j = 0; j < h; j++) {
                        int rgb = image.getRGB(i, j);
                        _hash += (rgb >> 2);
                    }
                }
            } catch (IOException | IndexOutOfBoundsException e) {
                p.log("Broken icon at " + from, Project.MSG_WARN);
                _hash = hash(e);
            }
            
            this.hash = _hash;
        }
        
        public IconInfo(String name, String path, int hash) {
            this.name = name;
            this.path = path;
            this.hash = hash;
        }
        
        @Override
        public int hashCode() {
            return hash;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            final IconInfo other = (IconInfo) obj;

            if (this.path != other.path &&
                (this.path == null || !this.path.equals(other.path)))
                return false;
            if (this.hash != other.hash)
                return false;
            return true;
        }
    
        public int compareTo(IconInfo another) {
            if (hash != another.hash) {
                return hash - another.hash;
            }
            
            return path.compareTo(another.path);
        }
        
        public @Override String toString() {
            String h = Integer.toHexString(hash);
            if (h.length() < 8) {
                h = "00000000".substring(h.length()) + h;
            }
            String n = name;
            if (n.length() < 30) {
                n = n + "                              ".substring(n.length());
            }
            
            return MessageFormat.format("{0} {1} {2}", h, n, path);
        }
    } // end of IconInfo
}
