/*
 * Copyright (c) 2016 Route1 Inc.  All rights reerved.  <sales@route1.com>
 * http://www.route1.com
 *
 * You may use, distribute and modify this code under the
 * terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * This code is provided “as -is,” without warranty of any kind,
 * express, implied or statutory, including but not limited to
 * any implied warranties or conditions of quality, performance,
 * merchantability, merchantable quality, durability,
 * non-infringement or of fitness for a particular purpose.
 * See the GPLv2 for more details.
 *
 * Contributor:  A. M. Jehad Sarkar  <amjehad.sarkar@route1.com>
*/

package com.routeone.dc.vpn;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.os.Handler;
import android.provider.ContactsContract;
import android.util.Log;
import android.widget.Toast;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Enumeration;

import de.blinkt.openvpn.BuildConfig;
import de.blinkt.openvpn.R;
import de.blinkt.openvpn.core.ProfileManager;

public class ForwardedDcIntentReceiver extends BroadcastReceiver {

    private static final String TAG = ForwardedDcIntentReceiver.class.getSimpleName();

    //private Context        mContext;
    private Activity       mActivity;

    private Handler        mHandler = new Handler();
    private ProgressDialog mProgressDialog;
    private String         mPin;
    private String         mCredentialType;

    private ArrayList<X509Certificate> mCertificates;
    private PrivateKey mPrivateKey;

    private final String TRUST_FILE_NAME = "trust.pem";
    private final String TA_KEY_FILE_NAME = "ta.key";
    //public void setContext(Context aContext)
    public void setContext(Activity aContext)
    {
        mActivity = aContext;
    }

    public void setProgressDialog(ProgressDialog aProgressDialog) {
        this.mProgressDialog = aProgressDialog;
    }

//    public void setHandler(Handler aHandler)
//    {
//        this.mHandler = aHandler;
//    }

    public void setPin(String aPin)
    {
        this.mPin = aPin;
    }
    public String getPin()
    {
        return this.mPin;
    }

    public void setCredentialType(String aCredentialType) {
        this.mCredentialType = aCredentialType;
    }

    public ArrayList<X509Certificate> getCertificates() {
        return mCertificates;
    }

    public PrivateKey getPrivateKey() {
        return mPrivateKey;
    }


    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "onReceive()");
        DcUtils.disableRequestTimer(mHandler);
        if(mCertificates == null)
            mCertificates = new ArrayList<>();
        Bundle extras = intent.getExtras();
        if (extras == null) {
            Log.e(TAG, "onReceive: " + context.getString(R.string.corruput_dc_response));
            //Toast.makeText(context, R.string.corruput_dc_response, Toast.LENGTH_SHORT).show();
            DcUtils.showErrorDialog(mActivity, R.string.corruput_dc_response);
            DcUtils.dismissProgress(mProgressDialog);
            return;
        }

        if (intent.getAction().equals(DcContainerResponseReceiver.ACTION_FORWARDED_RESPONSE)) {
            int message = extras.getInt(DcContainerInterface.ARG_MESSAGE);
            switch (message) {
                case DcContainerInterface.MSG_ON_PIN_VERIFIED:
                    boolean pinVerified = extras.getBoolean(DcContainerInterface.ARG_PIN_VERIFIED);
                    if (pinVerified) {
                        Log.d(TAG, "onReceive: PIN verified successfully");
                        if (mProgressDialog == null) {
                            mProgressDialog = new ProgressDialog(context);
                        }
                        if(mCredentialType != null) {
                            mProgressDialog = DcUtils.showProgress(context.getString(R.string.requesting_credentials), mProgressDialog, mActivity);
                            DcUtils.enableDcRequestTimer(mHandler, mActivity, mProgressDialog);
                            DcContainerInterface.requestCredentials(mActivity, mCredentialType, mPin);
                        }
                        else
                        {
                            mProgressDialog = DcUtils.showProgress(context.getString(R.string.requesting_configurations), mProgressDialog, mActivity);
                            DcUtils.enableDcRequestTimer(mHandler, mActivity, mProgressDialog);
                            DcContainerInterface.requestConfiguration(mActivity, "AirWatch", "VPN", mPin);

                            //Toast.makeText(context, R.string.pin_verification_successful, Toast.LENGTH_SHORT).show();
                        }

                    } else {
                        Log.d(TAG, "onReceive: " + context.getString(R.string.pin_verification_failed));
                        //Toast.makeText(context, R.string.pin_verification_failed, Toast.LENGTH_SHORT).show();
                        DcUtils.showErrorDialog(mActivity, R.string.pin_verification_failed);
                        DcUtils.dismissProgress(mProgressDialog);

                        // Clear the callback since PIN verification failed, to ensure that it's not called
                        // accidentally later e.g. on editing/saving the profile.
                        this.setOnRetrieveCredential(null);
                    }
                    break;

                case DcContainerInterface.MSG_ON_RETRIEVE_CREDENTIAL:
                    //getUserCertificateAlias(context, "Tunnel", "000000");
                    byte[] privateKeyBytes = extras.getByteArray(DcContainerInterface.ARG_PRIVATE_KEY);
                    String certChainString = extras.getString(DcContainerInterface.ARG_CERT_CHAIN);
                    if (privateKeyBytes == null || certChainString == null || certChainString.isEmpty()) {
                        Log.e(TAG, "onReceive: " + context.getString(R.string.dc_response_data_missing));
                        //Toast.makeText(context, R.string.dc_response_data_missing, Toast.LENGTH_SHORT).show();
                        DcUtils.showErrorDialog(mActivity, R.string.dc_response_data_missing);
                        DcUtils.dismissProgress(mProgressDialog);
                        break;
                    }

                    // extract certificates
                    mCertificates.clear();
                    String certArray[] = certChainString.split("-----BEGIN CERTIFICATE-----");
                    try {
                        for (int i = 1; i < certArray.length; i++) {
                            certArray[i] = "-----BEGIN CERTIFICATE-----" + certArray[i];
                            byte[]             certData    = certArray[i].getBytes();
                            CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
                            Certificate certificate = certFactory.generateCertificate(new ByteArrayInputStream(certData));
                            if (certificate instanceof X509Certificate) {
                                mCertificates.add((X509Certificate) certificate);
                            } else {
                                Log.w(TAG, "onReceive: Certificate is not in X.509 cert. instance. skipping");
                                throw new Exception("Bad Certificate Format");
                            }
                        }
                    } catch (Exception e) {
                        Log.e(TAG, "onReceive: " + context.getString(R.string.cert_chain_extraction_failed) + e.getMessage());
                        //Toast.makeText(context, R.string.cert_chain_extraction_failed, Toast.LENGTH_SHORT).show();
                        DcUtils.showErrorDialog(mActivity, R.string.cert_chain_extraction_failed);
                        DcUtils.dismissProgress(mProgressDialog);
                        break;
                    }
                    if (mCertificates.isEmpty()) {
                        Log.e(TAG, "onReceive: " + context.getString(R.string.cert_chain_extraction_failed));
                        //Toast.makeText(context, R.string.cert_chain_extraction_failed, Toast.LENGTH_SHORT).show();
                        DcUtils.showErrorDialog(mActivity, R.string.cert_chain_extraction_failed);
                        DcUtils.dismissProgress(mProgressDialog);
                        break;
                    }

                    // extract PrivateKey
                    try {
                        Log.d(TAG, "onReceive: PrivateKey bytes:\n" + new String(privateKeyBytes));
                        //byte [] decodedBytes = Base64.decode(privateKeyBytes, Base64.DEFAULT);
                        //Log.d(TAG, "onReceive: PrivateKey bytes after decode:\n" + new String(decodedBytes));
                        mPrivateKey = KeyFactory.getInstance("RSA").generatePrivate(
                                new PKCS8EncodedKeySpec(privateKeyBytes)); // E/ForwardedDcIntentReceiver: onReceive: Failed to decode privateKeyjava.lang.RuntimeException: error:0D07209B:asn1 encoding routines:ASN1_get_object:too long

                        ////////////////////////////////////////////////////////////////////////////
                        // Derived Credentials are retrieved successfully. Call the callback if it's set.
                        if(onRetrieveCredential != null) {
                            onRetrieveCredential.run();
                        }
                        ////////////////////////////////////////////////////////////////////////////

                    } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
                        Log.e(TAG, "onReceive: " + context.getString(R.string.failed_to_decode_priv_key) + e.getMessage());
                        //Toast.makeText(context, R.string.failed_to_decode_priv_key, Toast.LENGTH_SHORT).show();
                        DcUtils.showErrorDialog(mActivity, R.string.failed_to_decode_priv_key);
                        DcUtils.dismissProgress(mProgressDialog);
                        break;
                    }
                    DcUtils.dismissProgress(mProgressDialog);
                    break;
                case DcContainerInterface.MSG_ON_RETRIEVE_VPN_CONFIGURATION:
                    String configuration = extras.getString(DcContainerInterface.ARG_VPN_CONFIG);
                    onRetrieveConfiguration(configuration);
                    break;

                case 0:
                default:
                    Log.e(TAG, "onReceive: unknown 'arg.message' value: " + message + "; "
                            + context.getString(R.string.corruput_dc_response));
                    //Toast.makeText(context, R.string.corruput_dc_response, Toast.LENGTH_SHORT).show();
                    DcUtils.showErrorDialog(mActivity, R.string.corruput_dc_response);
                    DcUtils.dismissProgress(mProgressDialog);
                    
            }
        } else if (intent.getAction().equals(DcContainerResponseReceiver.ACTION_FORWARDED_ERROR)) {
            String errorMessage = intent.getExtras().getString(DcContainerInterface.ARG_MESSAGE, "UNKNOWN ERROR OCCURRED");
            Log.e(TAG, "onReceive(): " + context.getString(R.string.error_occurred, errorMessage));
            DcUtils.dismissProgress(mProgressDialog);
            DcUtils.showErrorDialog(mActivity, R.string.error_occurred, errorMessage);
        } else {
            Log.e(TAG, "onReceive: " + context.getString(R.string.unknown_dc_response));
            DcUtils.dismissProgress(mProgressDialog);
            DcUtils.showErrorDialog(mActivity, R.string.unknown_dc_response);
        }
    }

    private void useDummyConfiguration() {
        ProfileManager pm = ProfileManager.getInstance(mActivity);

        onRetrieveConfiguration("client\n" +
                "dev tun\n" +
                "float\n" +
                "proto udp\n" +
                "remote 192.168.207.135 443\n" +
                "resolv-retry infinite\n" +
                "key-direction 1\n" +
                "nobind\n" +
                "persist-key\n" +
                "persist-tun\n" +
                "ca \"trust.pem\"\n" +
                "tls-auth \"ta.key\" 1\n" +
                "#auth-user-pass\n" +
                "auth-nocache\n" +
                "cipher AES-256-CBC\n" +
                "verb 3\n" +
                "Route1DCType Tunnel\n" +
                "Route1ProfileEditable 1");
    }

    private void onRetrieveConfiguration(String data)
    {
        ProfileManager pm = ProfileManager.getInstance(mActivity);
        if(pm!=null)
        {
            Route1ConfigConverter converter = new Route1ConfigConverter();
            DcUtils.dismissProgress(mProgressDialog);
            mProgressDialog = null;

            converter.saveProfile(pm, mActivity, data);
            /*
            converter.saveProfile(pm, mActivity, "client\n" +
                    "dev tun\n" +
                    "interface not description\n" +
                    "proto udp\n" +
                    "remote 192.168.206.215 443\n" +
                    "resolv-retry infinite\n" +
                    "nobind\n" +
                    "persist-key\n" +
                    "persist-tun\n" +
                    "pkcs12 KeyManagement\n" +
                    "ca ca.crt\n" +
                    "cipher AES-256-CBC\n" +
                    "verb 3");
                    */
        }
    }


    private Runnable onRetrieveCredential = null;
    public void setOnRetrieveCredential(Runnable runnable) {
        onRetrieveCredential = runnable;
    }

    private void useDummyCertificate1(Context aContext)
    {
        //DUMMY certificate
        String alias = "a1bba39a40e418baace394f9befdc4cf42617444";
        InputStream certIS = null;
        try {
            certIS = aContext.getResources().getAssets().open("ipad2.p12");
            KeyStore p12 = KeyStore.getInstance("pkcs12");
            p12.load(certIS, "password".toCharArray());
            Certificate[] caChain = p12.getCertificateChain("a1bba39a40e418baace394f9befdc4cf42617444");
            byte[] key = p12.getKey(alias, "password".toCharArray()).getEncoded();
            KeyFactory kf = KeyFactory.getInstance("RSA");
            mPrivateKey = kf.generatePrivate(new PKCS8EncodedKeySpec(key));
            mCertificates.clear();
            for (int i = 0; i<caChain.length; i++)
            {
                mCertificates.add((X509Certificate)caChain[i]);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnrecoverableKeyException e) {
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        }
        //DUMMY certificates
    }
    private String getUserCertificateAlias(Context aContext, String aCertName, String aPassword)
    {
        InputStream certIS = null;
        try {
            certIS = aContext.getResources().getAssets().open(aCertName);
            KeyStore p12 = KeyStore.getInstance("pkcs12");
            p12.load(certIS, aPassword.toCharArray());
            Enumeration enumeration = p12.aliases();
            if(enumeration.hasMoreElements()) {
                String alias = (String)enumeration.nextElement();
                int index = alias.indexOf("root");
                if(index <= 0)
                    index = alias.indexOf("intermediate");
                if(index > 0 )
                    alias = alias.substring(0, index);

                return alias;
                //System.out.println("alias name: " + alias);
                //Certificate certificate = p12.getCertificate(alias);
                //System.out.println(certificate.toString());

            }
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return "";
    }
    private void useDummyCertificate2(Context aContext)
    {
        //DUMMY certificate
        Log.e(TAG, "DEBUGGING: Calling: useDummyCertificate2()");
        Toast.makeText(aContext, "DEBUGGING: Calling: useDummyCertificate2()", Toast.LENGTH_SHORT).show();

        String alias = getUserCertificateAlias(aContext, "TunnelDC.p12", "000000");
        InputStream certIS = null;
        try {
            certIS = aContext.getResources().getAssets().open("TunnelDC.p12");
            KeyStore p12 = KeyStore.getInstance("pkcs12");
            p12.load(certIS, "000000".toCharArray());
            Certificate[] caChain = p12.getCertificateChain(alias);
            byte[] key = p12.getKey(alias, "000000".toCharArray()).getEncoded();
            KeyFactory kf = KeyFactory.getInstance("RSA");
            mPrivateKey = kf.generatePrivate(new PKCS8EncodedKeySpec(key));
            mCertificates.clear();
            for (int i = 0; i<caChain.length; i++)
            {
                mCertificates.add((X509Certificate)caChain[i]);
            }

            ////////////////////////////////////////////////////////////////////////////
            // Derived Credentials are retrieved successfully. Call the callback if it's set.
            if(onRetrieveCredential != null) {
                onRetrieveCredential.run();
            }
            ////////////////////////////////////////////////////////////////////////////

        } catch (IOException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnrecoverableKeyException e) {
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        }
        //DUMMY certificates
    }

}
