// Remote
//
// Douglas
//
// Remote.java

/*  Copyright 2011 Douglas Thrift
 *
 *  This file is part of Big Screen Bot.
 *
 *  Big Screen Bot 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 3 of the License, or
 *  (at your option) any later version.
 *
 *  Big Screen Bot 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 Big Screen Bot.  If not, see <http://www.gnu.org/licenses/>.
 */

package net.douglasthrift.bigscreenbot;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.UnknownHostException;
import java.net.URL;

import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;

import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import java.util.ArrayList;
import java.util.List;

import javax.jmdns.JmDNS;
import javax.jmdns.ServiceInfo;

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;

import com.google.anymote.common.AnymoteFactory;
import com.google.anymote.common.ErrorListener;

import com.google.anymote.device.DeviceAdapter;
import com.google.anymote.device.MessageReceiver;

import com.google.polo.exception.PoloException;

import com.google.polo.pairing.PairingContext;

import com.google.polo.ssl.DummySSLSocketFactory;
import com.google.polo.ssl.SslUtil;

public class Remote
{
    private static final String STORE = "bigscreenbot.keystore";
    private static final char[] PASSWORD = "b1GsSC33Nb0T".toCharArray();
    private static final char[] NULL = new char[]{};
    private static final String TYPE = "_anymote._tcp.local.";
    private static final String LOCAL_ALIAS = "anymote-remote";
    private static final String REMOTE_ALIAS = "anymote-server-%1$X";
    private Settings settings;
    private JmDNS mdns;
    private KeyStore store;
    SSLSocketFactory factory;

    public Remote(Settings settings) throws UnknownHostException, IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException, KeyManagementException, GeneralSecurityException
    {
        this.settings = settings;

        String interfaze = settings.getProperty("interface");

        if (interfaze != null)
            mdns = JmDNS.create(InetAddress.getByName(interfaze));
        else
            mdns = JmDNS.create();

        store = KeyStore.getInstance(KeyStore.getDefaultType());

        FileInputStream stream = null;

        try
        {
            stream = new FileInputStream(STORE);

            store.load(stream, PASSWORD);
        }
        catch (FileNotFoundException exception)
        {
            store.load(null, PASSWORD);
        }
        finally
        {
            if (stream != null)
                stream.close();
        }

        if (!store.containsAlias(LOCAL_ALIAS))
        {
            KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
            KeyPair pair = generator.generateKeyPair();
            X509Certificate certificate = SslUtil.generateX509V3Certificate(pair, "CN=anymote/bigscreenbot/" + System.getProperty("os.name") + "/" + System.getProperty("os.arch") + "/" + System.getProperty("user.name") + "@" + InetAddress.getLocalHost().getHostName());

            store.setKeyEntry(LOCAL_ALIAS, pair.getPrivate(), NULL, new Certificate[]{ certificate });

            store();
        }

        KeyManagerFactory factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());

        factory.init(store, NULL);

        final javax.net.ssl.X509ExtendedKeyManager manager = (javax.net.ssl.X509ExtendedKeyManager)factory.getKeyManagers()[0];

        this.factory = DummySSLSocketFactory.fromKeyManagers(new KeyManager[]{
            new javax.net.ssl.X509ExtendedKeyManager()
            {
                @Override
                public java.security.PrivateKey getPrivateKey(String alias)
                {
                    java.security.PrivateKey key = manager.getPrivateKey(alias);

                    System.out.println("getPrivateKey(" + alias + ") -> " + key);

                    return key;
                }

                @Override
                public X509Certificate[] getCertificateChain(String alias)
                {
                    X509Certificate[] chain = manager.getCertificateChain(alias);

                    System.out.println("getCertificateChain(" + alias + ") -> " + java.util.Arrays.toString(chain));

                    return chain;
                }

                @Override
                public String chooseServerAlias(String keyType, java.security.Principal[] issuers, java.net.Socket socket)
                {
                    String alias = manager.chooseServerAlias(keyType, issuers, socket);

                    System.out.println("chooseServerAlias(" + keyType + ", " + java.util.Arrays.toString(issuers) + ", " + socket + ") -> " + alias);

                    return alias;
                }

                @Override
                public String[] getServerAliases(String keyType, java.security.Principal[] issuers)
                {
                    String[] aliases = manager.getServerAliases(keyType, issuers);

                    System.out.println("getServerAliases(" + keyType + ", " + java.util.Arrays.toString(issuers) + ") -> " + java.util.Arrays.toString(aliases));

                    return aliases;
                }

                @Override
                public String chooseClientAlias(String[] keyType, java.security.Principal[] issuers, java.net.Socket socket)
                {
                    String alias = manager.chooseClientAlias(keyType, issuers, socket);

                    System.out.println("chooseClientAlias(" + java.util.Arrays.toString(keyType) + ", " + java.util.Arrays.toString(issuers) + ", " + socket + ") -> " + alias);

                    return alias;
                }

                @Override
                public String[] getClientAliases(String keyType, java.security.Principal[] issuers)
                {
                    String[] aliases = manager.getClientAliases(keyType, issuers);

                    System.out.println("getClientAliases(" + keyType + ", " + java.util.Arrays.toString(issuers) + ") -> " + java.util.Arrays.toString(aliases));

                    return aliases;
                }
            }
        });
    }

    public List<String> listDevices()
    {
        List<String> devices = new ArrayList<String>();

        for (ServiceInfo info : mdns.list(TYPE))
            devices.add(info.getName());

        return devices;
    }

    public void startPairDevice(String device) throws MalformedURLException, UnknownHostException, IOException, PoloException
    {
        ServiceInfo info = mdns.getServiceInfo(TYPE, device);
        InetAddress address;
        int port;

        if (info != null)
        {
            address = info.getInetAddresses()[0];
            port = info.getPort();
        }
        else
        {
            URL url = new URL("http://" + device);

            address = InetAddress.getByName(url.getHost());
            port = url.getPort();

            if (port == -1)
                port = 9551;
        }

        SSLSocket socket = (SSLSocket)factory.createSocket(address, port);
        PairingContext context = PairingContext.fromSslSocket(socket, false);
    }

    public void finishPairDevice(String device, String code)
    {
    }

    public void fling(String url)
    {
    }

    public void fling(String device, String url)
    {
    }

    @Override
    protected void finalize() throws IOException
    {
        mdns.close();
    }

    private void store() throws FileNotFoundException, IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException
    {
        FileOutputStream stream = null;

        try
        {
            stream = new FileOutputStream(STORE);

            store.store(stream, PASSWORD);
        }
        finally
        {
            if (stream != null)
                stream.close();
        }
    }
}

// vim: expandtab
