/*
 * 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.openpgp.ant;

import org.apache.tools.ant.Task;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.util.FileNameMapper;
import org.apache.tools.ant.util.GlobPatternMapper;
import org.apache.tools.ant.util.FileUtils;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Mapper;
import org.apache.commons.openpgp.*;
import org.bouncycastle.openpgp.PGPException;

import java.io.*;
import java.util.Collection;
import java.util.ArrayList;
import java.util.Iterator;

/**
 */
public class OpenPgpSignerTask extends Task {
    private File secring;
    private File pubring;
    private String password;
    private String keyId;
    private Collection tosign = new ArrayList();
    private File artefact;
    private boolean asciiarmor = true;
    private Mapper mapperElement;

    /**
     * set the secret keyring
     * @param secring secret keyring file
     */
    public void setSecring(File secring) {
        this.secring = secring;
    }

    /**
     * set the public keyring
     * @param pubring public keyring file
     */
    public void setPubring(File pubring) {
        this.pubring = pubring;
    }

    /**
     * set the key id
     * @param keyId
     */
    public void setKeyId(String keyId) {
        this.keyId = keyId;
    }

    /**
     * asciiarmor the signature ?
     * @param asciiarmor ascii armored signature ?
     */
    public void setAsciiarmor(boolean asciiarmor) {
        this.asciiarmor = asciiarmor;
    }

    /**
     * set the value of the password
     * @param password value of the password
     */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * artefact to be signed
     * @param artefact artefact to be signed
     */
    public void setArtefact(File artefact) {
        this.artefact = artefact;
    }


    public void add(FileSet fs) {
        tosign.add(fs);
    }

    /**
     * Define the mapper to map source to destination files.
     * @return a mapper to be configured.
     * @exception org.apache.tools.ant.BuildException if more than one mapper is defined.
     */
    public Mapper createMapper() throws BuildException {
        if (mapperElement != null) {
            throw new BuildException("Cannot define more than one mapper",
                    getLocation());
        }
        mapperElement = new Mapper(getProject());
        return mapperElement;
    }

    public void execute() {
        if (secring == null) {
            throw new BuildException("secring attribute compulsory");
        }
        if (pubring == null) {
            throw new BuildException("pubring attribute compulsory");
        }
        if (password == null) {
            throw new BuildException("password attribute compulsory");
        }
        if (tosign.size() == 0 && artefact == null) {
            throw new BuildException("supply the attribute tosign or one nested fileset");
        }
        if (!secring.exists() || !secring.canRead()) {
            throw new  BuildException("secret keyring file does not exist or is not readable");
        }
        if (!pubring.exists() || !pubring.canRead()) {
            throw new  BuildException("public keyring file does not exist or is not readable");
        }
        FileInputStream secStream;
        FileInputStream pubStream;
        KeyRing keyRing = null;
        try {
            secStream = new FileInputStream(secring);
            pubStream = new FileInputStream(pubring);
            keyRing = new BouncyCastleKeyRing(secStream,
                    pubStream, password.toCharArray() );
        } catch (IOException ioe) {
            throw new BuildException(ioe);
        } catch (PGPException pgpe) {
            throw new BuildException(pgpe);
        }
        if (artefact != null) {
            dosign(keyRing, artefact);
        }
        if (tosign.size() != 0) {
            for (Iterator it = tosign.iterator(); it.hasNext(); ) {
                FileSet fs = (FileSet) it.next();
                dosign(keyRing, fs);
            }
        }
        FileUtils.close(secStream);
        FileUtils.close(pubStream);
    }
    private void dosign(KeyRing keyRing, FileSet fs) {
        DirectoryScanner ds = fs.getDirectoryScanner(getProject());
        String[] artefacts = ds.getIncludedFiles();
        for (int counter = 0; counter < artefacts.length; counter++) {
            dosign(keyRing,
                    new File(fs.getDir(getProject()), artefacts[counter]), fs.getDir(getProject()), artefacts[counter]);
        }
    }
    private void dosign(KeyRing keyRing, File oneartefact) {
        dosign(keyRing, oneartefact, oneartefact.getParentFile(), oneartefact.getName());
    }
    private void dosign(KeyRing keyRing, File oneartefact, File basedir, String relpath) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        File signature;

        try {
            fis = new FileInputStream(oneartefact);
            FileNameMapper mapper = getMapper();
            String [] mappedFiles = mapper.mapFileName(relpath);
            if (mappedFiles == null || mappedFiles.length != 1) {
                throw new BuildException("mapper returned more or less than one output");
            }
            signature = new File(basedir, mappedFiles[0]);
            fos = new FileOutputStream(signature);
            OpenPgpSigner signer = new BouncyCastleOpenPgpSigner();
            signer.detachedSign(fis, fos, keyId, keyRing, asciiarmor);
        } catch (FileNotFoundException fnfe) {
            throw new BuildException(fnfe);
        } catch (IOException ioe) {
            throw new BuildException(ioe);
        } catch (OpenPgpException opgpe) {
            throw new BuildException(opgpe);
        }
        FileUtils.close(fos);
        FileUtils.close(fis);

    }
    /**
     * returns the mapper to use based on nested elements or the
     */
    private FileNameMapper getMapper() {
        FileNameMapper mapper = null;
        if (mapperElement != null) {
            mapper = mapperElement.getImplementation();
        } else {
            mapper = new GlobPatternMapper();
            mapper.setFrom("*");
            if (asciiarmor) {
                mapper.setTo("*.asc");
            } else {
                mapper.setTo("*.sig");
            }
        }
        return mapper;
    }

}
