/*
 * 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.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.zip.CRC32;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;

/**
 * Create an update tracking file automatically.
 * @author  Michal Zlamal
 */
public class MakeListOfNBM extends Task {
    File outputFile = null;
    String moduleName = null;
    boolean pok = true;
    FileSet fs = null;
    private ArrayList<String> locales;
    private ArrayList<String> brandings;

    public MakeListOfNBM() {
        // initialize locales and brandings lists, add empty value at beginning
        locales = new ArrayList<>();
        locales.add("");
        brandings = new ArrayList<>();
        brandings.add("");
    }

    /** Sets the directory used to create the NBM list file */
    public void setOutputfiledir(File s) {
        outputFile = s;
        log("Setting outputfile to " + s, Project.MSG_DEBUG);
    }

    public FileSet createFileSet() {
        return (fs = new FileSet());
    }

    /** Sets the module file */
    public void setModule(String s) {
        moduleName = s;
        log("Setting moduleName to " + s, Project.MSG_DEBUG);
    }

    public void setTargetName(String t) {
        pok = false;
        log("<"+this.getTaskName()+"> attribute targetname has been DEPRECATED");
    }

    /** Sets the list of locales in multilanguage build */
    public void setLocales (String s) {
        for (String st : s.split("[, ]+")) {
            locales.add(st);
        }
    }

    public List<String> getLocales () {
        return this.locales;
    }

    /** Sets the list of brandings in multilanguage build */
    public void setBrandings (String s) {
        if (s.startsWith("${")) return;
        StringTokenizer st = new StringTokenizer(s,", ");
        while (st.hasMoreTokens()) {
            brandings.add(st.nextToken());
        }
    }

    public List<String> getBrandings () {
        return this.brandings;
    }

    @Override
    public void execute () throws BuildException {
        if (!pok) throw new BuildException("Use the fileset to specify the content of the NBM");
        if ( outputFile == null ) throw new BuildException( "You have to specify output directoty" );
        if ( moduleName == null ) throw new BuildException( "You have to specify the main module's file" );
        if ( fs == null ) throw new BuildException( "You have to specify the fileset of files included in this module" );

        UpdateTracking track = new UpdateTracking( outputFile.getAbsolutePath() );
        Attributes attr;
        JarFile jar = null;
        File module = new File( outputFile, moduleName );
        try {
            jar = new JarFile(module);
            attr = jar.getManifest().getMainAttributes();
        } catch (IOException ex) {
            throw new BuildException("Can't get manifest attributes for module jar file "+module.getAbsolutePath(), ex, getLocation());
        } finally {
            try {
                if (jar != null) jar.close();
            } catch( IOException ex1 ) {
                String exmsg = ex1.getMessage();
                if (exmsg == null) exmsg = "Unknown error";
                log("Caught I/O Exception (msg:\""+exmsg+"\") when trying to close jar file "+module.getAbsolutePath(),Project.MSG_WARN);
                }
            }

        boolean[] osgi = new boolean[1];
        String codename = JarWithModuleAttributes.extractCodeName(attr, osgi);
        if (codename == null) {
            throw new BuildException("Manifest in jar file "+module.getAbsolutePath()+" does not contain OpenIDE-Module", getLocation());
        }
        String cnb = codename.replaceFirst("/\\d+$", "");

        log("Generating Auto Update information for " + cnb);

        String versionTag = osgi[0] ? "Bundle-Version" : "OpenIDE-Module-Specification-Version"; // NOI18N
        String versionSpecNum = attr.getValue(versionTag);
        if (versionSpecNum == null) {
            log("Manifest in jar file "+module.getAbsolutePath()+" does not contain tag " + versionTag);
            versionSpecNum = "0";
        }

        UpdateTracking.Version version = track.addNewModuleVersion( codename, versionSpecNum );

        fs.createInclude().setName("config" + File.separator + "Modules" + File.separator + track.getTrackingFileName()); //NOI18N
        
        updateFileSetForLorB (fs);
        // get directory scanner for "default" files
        DirectoryScanner ds = fs.getDirectoryScanner( this.getProject() );
        ds.scan();

        // check if we need also localized and branded files
        String lmnl = this.getProject().getProperty("locmakenbm.locales"); // NOI18N
        String lmnb = this.getProject().getProperty("locmakenbm.brands"); // NOI18N
        
        if ((!(lmnl == null)) && (!(lmnl.trim().equals("")))) { // NOI18N
            // property locmakenbm.locales is set, let's update the included fileset for locales
            // defined in that property

            java.util.StringTokenizer tokenizer = new StringTokenizer( lmnl, ", ") ; //NOI18N
            int cntTok = tokenizer.countTokens();
            String[] lmnLocales = new String[cntTok];
            for (int j=0; j < cntTok; j++) {
                String s = tokenizer.nextToken();
                lmnLocales[j] = s;
                log("  lmnLocales[j] == "+lmnLocales[j], Project.MSG_DEBUG); // NOI18N
            }

            // handle brandings
            String[] lmnBrands = null;
            if ((!(lmnb == null)) && (!(lmnb.trim().equals("")))) { // NOI18N
                tokenizer = new StringTokenizer( lmnb, ", ") ; //NOI18N
                cntTok = tokenizer.countTokens();
                lmnBrands = new String[cntTok];
                for (int j=0; j < cntTok; j++) {
                    String s = tokenizer.nextToken();
                    lmnBrands[j] = s;
                    log("  lmnBrands[j] == "+lmnBrands[j], Project.MSG_DEBUG); // NOI18N
                }
            }

            // update fileset for localized/branded files
        
            String[] englishFiles = ds.getIncludedFiles();
            int sepPos, extPos;
            String dirName, fname, filename, fext, newinc, ei_codename;
            String moduleJar = null;
            boolean skipLocaleDir = false;
            for (int k=0; k < englishFiles.length; k++) {
                // skip records for already localized/branded files
                if ((englishFiles[k].lastIndexOf("/locale/") >= 0) || // NOI18N
                     (englishFiles[k].lastIndexOf(File.separator+"locale"+File.separator) >= 0)) {  // NOI18N
                    skipLocaleDir=true;
                } else {
                    skipLocaleDir=false;
                }
                log("Examining file " + englishFiles[k], Project.MSG_DEBUG);
                sepPos = englishFiles[k].lastIndexOf(File.separator);
                if (sepPos < 0) {
                    dirName = ""; //NOI18N
                    filename = englishFiles[k];
                } else {
                    dirName = englishFiles[k].substring(0,sepPos);
                    filename = englishFiles[k].substring(sepPos+File.separator.length());
                }
                extPos = filename.lastIndexOf('.'); //NOI18N
                if (extPos < 0) {
                    fname = filename;
                    fext = ""; //NOI18N
                } else {
                    fname = filename.substring(0, extPos);
                    fext = filename.substring(extPos);
                }
                for (int j=0; j < lmnLocales.length; j++) {
                    // localized files
                    if (skipLocaleDir) {
                    	newinc = dirName + File.separator + fname + "_"+lmnLocales[j]+"*" + fext; //NOI18N
                    } else {
                        newinc = dirName + File.separator + "locale" + File.separator + fname + "_"+lmnLocales[j]+"*" + fext; //NOI18N
                    }
                    log("  adding include mask \""+newinc+"\"", Project.MSG_DEBUG);
                    fs.setIncludes( newinc );
                    // localized & branded files
                    if (!(lmnBrands == null)) {
                    	for (int i=0; i < lmnBrands.length; i++) {
                            if (skipLocaleDir) {
                    	        newinc = dirName + File.separator + fname + "_"+lmnBrands[i]+"_"+lmnLocales[j]+"*" + fext; //NOI18N
                            } else {
                    	        newinc = dirName + File.separator + "locale" + File.separator + fname + "_"+lmnBrands[i]+"_"+lmnLocales[j]+"*" + fext; //NOI18N
                            }
                            log("  adding include mask \""+newinc+"\"", Project.MSG_DEBUG);
                            fs.setIncludes( newinc );
                        }
                    }
                }
            }
            // update directory scanner
            ds = fs.getDirectoryScanner(this.getProject());
            ds.scan();
        }

        String include[] = ds.getIncludedFiles();
        log("Including files " + Arrays.toString(include), Project.MSG_VERBOSE);
        for( int j=0; j < include.length; j++ ){
            String path = include[j].replace(File.separatorChar, '/');
            if (osgi[0] && !path.equals(moduleName) &&
                    !path.equals("config/Modules/" + cnb.replace('.', '-') + ".xml")) {
                throw new BuildException("Cannot include other files with an OSGi bundle: " + path, getLocation());
            }
            try {
                File inFile = new File( ds.getBasedir(), include[j] );
                CRC32 crc = UpdateTracking.crcForFile(inFile);
                String abs = inFile.getAbsolutePath();
                String prefix = ds.getBasedir().getAbsolutePath() + File.separatorChar;
                if (! abs.startsWith(prefix)) throw new IllegalStateException(abs);
                version.addFileWithCrc(abs.substring(prefix.length()).replace(File.separatorChar, '/'), Long.toString( crc.getValue() ) );
            } catch (IOException ex) {
                log( ex.toString() );
            }
        }
        track.write();
    }

    private void updateFileSetForLorB(FileSet fs) {
        if ((locales.size() == 1) && (brandings.size() == 1)) return;
        // update the fileset only if we have got at least one locale or one branding
        DirectoryScanner ds = fs.getDirectoryScanner();
        String[] included = ds.getIncludedFiles();
        ArrayList<String> newIncludes = new ArrayList<>();
        String dirName; String filename; String fname; String fext; String newinc;
        
        for (String include : included) {
            include = include.replace(File.separatorChar, '/');
            int sepPos = include.lastIndexOf('/');
            if (sepPos < 0) {
                dirName = ""; //NOI18N
                filename = include;
            } else {
                dirName = include.substring(0,sepPos);
                filename = include.substring(sepPos+1);
            }
            int extPos = filename.lastIndexOf('.'); //NOI18N
            if (extPos < 0) {
                fname = filename;
                fext = ""; //NOI18N
            } else {
                fname = filename.substring(0, extPos);
                fext = filename.substring(extPos);
            }
            for (String branding : brandings) {
                for (String loc : locales) {
                    newinc = dirName + "/locale/" + fname;
                    if (branding.length()>0) newinc += "_" + branding;
                    if (loc.length()>0) newinc += "_" + loc;
                    newinc+=fext;
                    if (newinc.startsWith("/")) newinc = newinc.substring(1); //avoid root referring masks on unix boxes
                    newIncludes.add(newinc);
                    log("Added include mask: "+newinc,Project.MSG_VERBOSE);
                    if (dirName.length() == 0) {
                        // if file is located in root of the cluster, add also
                        // a mask without "locale/" subdirectory
                        newinc = fname;
                        if (branding.length()>0) newinc += "_" + branding;
                        if (loc.length()>0) newinc += "_" + loc;
                        newinc+=fext;
                        newIncludes.add(newinc);
                        log("Added cluster-root include mask: "+newinc,Project.MSG_VERBOSE);
                    }
                }
            }
        }
        for (String inc : newIncludes) {
            fs.setIncludes(inc);
        }
    }
}
