/*--------------------------------*- Java -*---------------------------------*\
 |		 o                                                                   |                                                                                     
 |    o     o       | HelyxOS: The Open Source GUI for OpenFOAM              |
 |   o   O   o      | Copyright (C) 2012-2016 ENGYS                          |
 |    o     o       | http://www.engys.com                                   |
 |       o          |                                                        |
 |---------------------------------------------------------------------------|
 |	 License                                                                 |
 |   This file is part of HelyxOS.                                           |
 |                                                                           |
 |   HelyxOS is free software; you can redistribute it and/or modify it      |
 |   under the terms of the GNU General Public License as published by the   |
 |   Free Software Foundation; either version 2 of the License, or (at your  |
 |   option) any later version.                                              |
 |                                                                           |
 |   HelyxOS is distributed in the hope that it will be useful, but WITHOUT  |
 |   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or   |
 |   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License   |
 |   for more details.                                                       |
 |                                                                           |
 |   You should have received a copy of the GNU General Public License       |
 |   along with HelyxOS; if not, write to the Free Software Foundation,      |
 |   Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA            |
\*---------------------------------------------------------------------------*/

/*
 * Copyright 2012 Krzysztof Otrebski (krzysztof.otrebski@gmail.com)
 *
 *    Licensed 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 eu.engys.util.filechooser.authentication;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.Map;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.vfs2.UserAuthenticationData;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

public class AuthStoreUtils {

    private static final java.util.logging.Logger LOGGER = java.util.logging.Logger.getLogger(AuthStoreUtils.class.getName());

    public static final String USER = "user";
    public static final String HOST = "host";
    public static final String PROTOCOL = "protocol";
    public static final String ENTRY = "Entry";
    public static final String USER_AUTHENTICATION_DATA = "UserAuthenticationData";
    public static final String TYPE = "Type";
    public static final String DATA = "Data";
    public static final String ALGORITHM_BLOW_FISH = "Blowfish";
    public static final int SALT_LENGTH = 64;
    private char[] password = null;

    private PasswordProvider passwordProvider;

    public AuthStoreUtils(PasswordProvider passwordProvider) {
        this.passwordProvider = passwordProvider;
    }

    public void save(AuthStore authStore, OutputStream out) throws IOException {
        Collection<UserAuthenticationInfo> all = authStore.getAll();
        for (UserAuthenticationInfo userAuthenticationInfo : all) {
            UserAuthenticationData userAuthenticationData = authStore.getUserAuthenticationData(userAuthenticationInfo);
        }
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder documentBuilder = null;
        try {
            documentBuilder = documentBuilderFactory.newDocumentBuilder();
            Document document = documentBuilder.newDocument();
            Element root = document.createElement("root");

            document.appendChild(root);
            for (UserAuthenticationInfo userAuthenticationInfo : all) {
                UserAuthenticationDataWrapper userAuthenticationData = authStore.getUserAuthenticationData(userAuthenticationInfo);
                Element entry = document.createElement(ENTRY);
                entry.setAttribute(PROTOCOL, userAuthenticationInfo.getProtocol());
                entry.setAttribute(HOST, userAuthenticationInfo.getHost());
                entry.setAttribute(USER, userAuthenticationInfo.getUser());
                Map<UserAuthenticationData.Type, char[]> addedTypes = userAuthenticationData.getAddedTypes();

                for (UserAuthenticationData.Type type : addedTypes.keySet()) {
                    Element elementUserAuthenticationData = document.createElement(USER_AUTHENTICATION_DATA);
                    char[] data = userAuthenticationData.getData(type);
                    String value;
                    // if (UserAuthenticationData.PASSWORD.equals(type)) {
                    // if (password == null){
                    // password =
                    // passwordProvider.getPassword("Enter password for password store");
                    // }
                    // if (password == null || password.length==0){
                    // throw new
                    // IOException("Password for password store not entered");
                    // }
                    // value = saltAndEncrypt(data);
                    // } else {
                    // value = new String(data);
                    // }
                    value = new String(data);
                    Element elementType = document.createElement(TYPE);
                    elementType.setTextContent(type.toString());
                    Element elementData = document.createElement(DATA);
                    elementData.setTextContent(value);
                    elementUserAuthenticationData.appendChild(elementType);
                    elementUserAuthenticationData.appendChild(elementData);
                    entry.appendChild(elementUserAuthenticationData);
                }
                root.appendChild(entry);
            }

            TransformerFactory transformerFactory = TransformerFactory.newInstance();

            Transformer transformer = transformerFactory.newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.setOutputProperty(OutputKeys.STANDALONE, "yes");
            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
            DOMSource source = new DOMSource(document);
            StreamResult result = new StreamResult(out);
            transformer.transform(source, result);
        } catch (Exception e) {
            throw new IOException(e);
        }

    }

    private String saltAndEncrypt(char[] data) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
        String value;
        Hex hex = new Hex();
        char[] saltedData = addSalt(data);

        byte[] encode = encrypt(saltedData, password);
        value = new String(hex.encode(encode));
        return value;
    }

    protected char[] addSalt(char[] data) {
        char[] saltedData = new char[SALT_LENGTH + data.length];
        for (int i = 0; i < SALT_LENGTH; i++) {
            saltedData[i] = 'a';// (char) random.nextInt();
        }
        for (int i = 0; i < data.length; i++) {
            saltedData[i + SALT_LENGTH] = data[i];
        }
        return saltedData;
    }

    protected char[] removeSalt(char[] data) {
        char[] deSalted = new char[data.length - SALT_LENGTH];
        System.arraycopy(data, SALT_LENGTH, deSalted, 0, deSalted.length);
        return deSalted;
    }

    public void load(AuthStore authStore, InputStream in) throws IOException {
        try {
            XMLReader xmlReader = XMLReaderFactory.createXMLReader();
            xmlReader.setContentHandler(new AuthStoreHandler(authStore));
            xmlReader.parse(new InputSource(in));
        } catch (SAXException e) {
            throw new IOException(e);
        }
    }

    private class AuthStoreHandler extends DefaultHandler {

        private UserAuthenticationDataWrapper userAuthenticationData;
        private UserAuthenticationInfo info;
        private StringBuilder sb = new StringBuilder();
        private String data;
        private String type;

        private AuthStore authStore;

        private AuthStoreHandler(AuthStore authStore) {
            this.authStore = authStore;
        }

        @Override
        public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {

            if (ENTRY.equals(localName)) {
                userAuthenticationData = new UserAuthenticationDataWrapper();
                info = new UserAuthenticationInfo(atts.getValue(PROTOCOL), atts.getValue(HOST), atts.getValue(USER));

            }
            sb.setLength(0);

        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            if (ENTRY.equals(localName)) {
                authStore.add(info, userAuthenticationData);
            } else if (USER_AUTHENTICATION_DATA.equals(localName)) {
                // if (UserAuthenticationData.PASSWORD.equals(new
                // UserAuthenticationData.Type(type))) {
                // if (password == null){
                // password =
                // passwordProvider.getPassword("Enter password for password store");
                // }
                // if (password == null || password.length==0){
                // throw new
                // SAXException("Password for password store not entered");
                // }
                // Hex hex = new Hex();
                // try {
                // byte[] decode = (byte[]) hex.decode(data.trim());
                // byte[] decrypted = decrypt(decode, password);
                // char[] passwordWithSalt = bytesToChars(decrypted);
                // char[] password = removeSalt(passwordWithSalt);
                // data = new String(password);
                // } catch (Exception e) {
                // password=null;
                // throw new SAXException("Can't decrypt password", e);
                // }
                // }
                userAuthenticationData.setData(new UserAuthenticationData.Type(type), data.toCharArray());
            } else if (DATA.equals(localName)) {
                data = sb.toString();
            } else if (TYPE.equals(localName)) {
                type = sb.toString();
            }

            sb.setLength(0);
        }

        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            sb.append(ch, start, length);
        }
    }

    protected byte[] encrypt(char[] bytes, char[] password) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
        byte[] passBytes = charsToBytes(password);
        SecretKeySpec secretKeySpec = new SecretKeySpec(passBytes, ALGORITHM_BLOW_FISH);

        Cipher cipher = Cipher.getInstance(ALGORITHM_BLOW_FISH);

        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
        byte[] encrypted = cipher.doFinal(new String(bytes).getBytes("UTF-8"));
        return encrypted;
    }

    protected byte[] decrypt(byte[] bytes, char[] password) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
        byte[] passBytes = charsToBytes(password);
        SecretKeySpec secretKeySpec = new SecretKeySpec(passBytes, ALGORITHM_BLOW_FISH);
        Cipher cipher = Cipher.getInstance(ALGORITHM_BLOW_FISH);
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
        byte[] decrypted = cipher.doFinal(bytes);
        return decrypted;
    }

    protected byte[] charsToBytes(char[] chars) throws UnsupportedEncodingException {
        return new String(chars).getBytes("UTF-8");
    }

    protected char[] bytesToChars(byte[] bytes) {
        return new String(bytes, Charset.forName("UTF-8")).toCharArray();
    }

    public PasswordProvider getPasswordProvider() {
        return passwordProvider;
    }

    public void setPasswordProvider(PasswordProvider passwordProvider) {
        this.passwordProvider = passwordProvider;
    }

}
