
package com.fsck.k9.ui.messageview;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;

import android.app.Activity;
import android.app.Fragment;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.fsck.k9.Account;
import com.fsck.k9.Identity;
import com.fsck.k9.K9;
import com.fsck.k9.R;
import com.fsck.k9.crypto.CryptoHelper;
import com.fsck.k9.crypto.OpenPgpApiHelper;
import com.fsck.k9.helper.IdentityHelper;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;

import com.fsck.k9.mail.internet.MessageExtractor;
import com.fsck.k9.mail.internet.MimeUtility;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpApi;
import org.openintents.openpgp.util.OpenPgpServiceConnection;

public class MessageOpenPgpViewOld extends LinearLayout {

    private Context mContext;
    private MessageViewFragment mFragment;
    private RelativeLayout mSignatureLayout = null;
    private ImageView mSignatureStatusImage = null;
    private TextView mSignatureUserId = null;
    private TextView mText = null;
    private ProgressBar mProgress;
    private Button mGetKeyButton;

    private OpenPgpServiceConnection mOpenPgpServiceConnection;
    private OpenPgpApi mOpenPgpApi;

    private String mOpenPgpProvider;
    private Message mMessage;

    private PendingIntent mMissingKeyPI;

    private static final int REQUEST_CODE_DECRYPT_VERIFY = 12;

    String mData;
    Account mAccount;

    public MessageOpenPgpViewOld(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
    }

    public void setupChildViews() {
        mSignatureLayout = (RelativeLayout) findViewById(R.id.openpgp_signature_layout);
        mSignatureStatusImage = (ImageView) findViewById(R.id.openpgp_signature_status);
        mSignatureUserId = (TextView) findViewById(R.id.openpgp_user_id);
        mText = (TextView) findViewById(R.id.openpgp_text);
        mProgress = (ProgressBar) findViewById(R.id.openpgp_progress);
        mGetKeyButton = (Button) findViewById(R.id.openpgp_get_key);

        mGetKeyButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                getMissingKey();
            }
        });
    }

    public void setFragment(Fragment fragment) {
        mFragment = (MessageViewFragment) fragment;
    }

    /**
     * Fill the decrypt layout with signature data, if known, make controls
     * visible, if they should be visible.
     */
    public void updateLayout(Account account, String decryptedData,
            final OpenPgpSignatureResult signatureResult,
            final Message message) {

        // set class variables
        mAccount = account;
        mOpenPgpProvider = mAccount.getOpenPgpProvider();
        mMessage = message;

        // only use this view if a OpenPGP Provider is set
        if (mOpenPgpProvider == null) {
            return;
        }

        Activity activity = mFragment.getActivity();
        if (activity == null) {
            return;
        }
        // bind to service
        mOpenPgpServiceConnection = new OpenPgpServiceConnection(activity,
                mOpenPgpProvider);
        mOpenPgpServiceConnection.bindToService();

        if ((message == null) && (decryptedData == null)) {
            this.setVisibility(View.GONE);

            // don't process further
            return;
        }
        if (decryptedData != null && signatureResult == null) {
            // encrypted-only

            MessageOpenPgpViewOld.this.setBackgroundColor(mFragment.getResources().getColor(
                    R.color.openpgp_blue));
            mText.setText(R.string.openpgp_successful_decryption);

            // don't process further
            return;
        } else if (decryptedData != null && signatureResult != null) {
            // signed-only and signed-and-encrypted

            switch (signatureResult.getStatus()) {
                case OpenPgpSignatureResult.SIGNATURE_ERROR:
                    // TODO: signature error but decryption works?
                    mText.setText(R.string.openpgp_signature_invalid);
                    MessageOpenPgpViewOld.this.setBackgroundColor(mFragment.getResources().getColor(
                            R.color.openpgp_red));

                    mGetKeyButton.setVisibility(View.GONE);
                    mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
                    mSignatureLayout.setVisibility(View.GONE);
                    break;

                case OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED:
                    if (signatureResult.isSignatureOnly()) {
                        mText.setText(R.string.openpgp_signature_valid_certified);
                    }
                    else {
                        mText.setText(R.string.openpgp_successful_decryption_valid_signature_certified);
                    }
                    MessageOpenPgpViewOld.this.setBackgroundColor(mFragment.getResources().getColor(
                            R.color.openpgp_green));

                    mGetKeyButton.setVisibility(View.GONE);
                    mSignatureUserId.setText(signatureResult.getUserId());
                    mSignatureStatusImage.setImageResource(R.drawable.overlay_ok);
                    mSignatureLayout.setVisibility(View.VISIBLE);

                    break;

                case OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY:
                    if (signatureResult.isSignatureOnly()) {
                        mText.setText(R.string.openpgp_signature_unknown_text);
                    }
                    else {
                        mText.setText(R.string.openpgp_successful_decryption_unknown_signature);
                    }
                    MessageOpenPgpViewOld.this.setBackgroundColor(mFragment.getResources().getColor(
                            R.color.openpgp_orange));
                    
                    mGetKeyButton.setVisibility(View.VISIBLE);
                    mSignatureUserId.setText(R.string.openpgp_signature_unknown);
                    mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
                    mSignatureLayout.setVisibility(View.VISIBLE);

                    break;

                case OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED:
                    if (signatureResult.isSignatureOnly()) {
                        mText.setText(R.string.openpgp_signature_valid_uncertified);
                    }
                    else {
                        mText.setText(R.string.openpgp_successful_decryption_valid_signature_uncertified);
                    }
                    MessageOpenPgpViewOld.this.setBackgroundColor(mFragment.getResources().getColor(
                            R.color.openpgp_orange));

                    mGetKeyButton.setVisibility(View.GONE);
                    mSignatureUserId.setText(signatureResult.getUserId());
                    mSignatureStatusImage.setImageResource(R.drawable.overlay_ok);
                    mSignatureLayout.setVisibility(View.VISIBLE);

                    break;

                default:
                    break;
            }

            // don't process further
            return;
        }

        // Start new decryption/verification
        CryptoHelper helper = new CryptoHelper();
        if (helper.isEncrypted(message) || helper.isSigned(message)) {
            // start automatic decrypt
            decryptAndVerify(message);
        } else {
            try {
                // check for PGP/MIME encryption
                Part pgp = MimeUtility.findFirstPartByMimeType(message, "application/pgp-encrypted");
                if (pgp != null) {
                    Toast.makeText(mContext, R.string.pgp_mime_unsupported, Toast.LENGTH_LONG)
                            .show();
                }
            } catch (MessagingException e) {
                // nothing to do...
            }
        }
    }

    private void decryptAndVerify(final Message message) {
        this.setVisibility(View.VISIBLE);
        mProgress.setVisibility(View.VISIBLE);
        MessageOpenPgpViewOld.this.setBackgroundColor(mFragment.getResources().getColor(
                R.color.openpgp_orange));
        mText.setText(R.string.openpgp_decrypting_verifying);

        // waiting in a new thread
        Runnable r = new Runnable() {

            @Override
            public void run() {
                try {
                    // get data String
                    Part part = MimeUtility.findFirstPartByMimeType(message, "text/plain");
                    if (part == null) {
                        part = MimeUtility.findFirstPartByMimeType(message, "text/html");
                    }
                    if (part != null) {
                        mData = MessageExtractor.getTextFromPart(part);
                    }

                    // wait for service to be bound
                    while (!mOpenPgpServiceConnection.isBound()) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                        }
                    }

                    mOpenPgpApi = new OpenPgpApi(getContext(),
                            mOpenPgpServiceConnection.getService());

                    decryptVerify(new Intent());

                } catch (MessagingException me) {
                    Log.e(K9.LOG_TAG, "Unable to decrypt email.", me);
                }

            }
        };

        new Thread(r).start();
    }

    private void decryptVerify(Intent intent) {
        intent.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
        intent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);

        Identity identity = IdentityHelper.getRecipientIdentityFromMessage(mAccount, mMessage);
        String accName = OpenPgpApiHelper.buildAccountName(identity);
        intent.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, accName);

        InputStream is = new ByteArrayInputStream(mData.getBytes(Charset.forName("UTF-8")));
        final ByteArrayOutputStream os = new ByteArrayOutputStream();

        DecryptVerifyCallback callback = new DecryptVerifyCallback(os, REQUEST_CODE_DECRYPT_VERIFY);

        mOpenPgpApi.executeApiAsync(intent, is, os, callback);
    }

    private void getMissingKey() {
        try {
            mFragment.getActivity().startIntentSenderForResult(
                    mMissingKeyPI.getIntentSender(),
                    REQUEST_CODE_DECRYPT_VERIFY, null, 0, 0, 0);
        } catch (SendIntentException e) {
            Log.e(K9.LOG_TAG, "SendIntentException", e);
        }
    }

    /**
     * Called on successful decrypt/verification
     */
    private class DecryptVerifyCallback implements OpenPgpApi.IOpenPgpCallback {
        ByteArrayOutputStream os;
        int requestCode;

        private DecryptVerifyCallback(ByteArrayOutputStream os, int requestCode) {
            this.os = os;
            this.requestCode = requestCode;
        }

        @Override
        public void onReturn(Intent result) {
            switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
                case OpenPgpApi.RESULT_CODE_SUCCESS: {
                    try {
                        final String output = os.toString("UTF-8");

                        OpenPgpSignatureResult sigResult = null;
                        if (result.hasExtra(OpenPgpApi.RESULT_SIGNATURE)) {
                            sigResult = result.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
                        }

                        if (K9.DEBUG)
                            Log.d(K9.LOG_TAG, "result: " + os.toByteArray().length
                                    + " str=" + output);

                        // missing key -> PendingIntent to get keys
                        mMissingKeyPI = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);

                        mProgress.setVisibility(View.GONE);
                        mFragment.setMessageWithOpenPgp(output, sigResult);
                    } catch (UnsupportedEncodingException e) {
                        Log.e(K9.LOG_TAG, "UnsupportedEncodingException", e);
                    }
                    break;
                }
                case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: {
                    PendingIntent pi = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
                    try {
                        mFragment.getActivity().startIntentSenderForResult(
                                pi.getIntentSender(),
                                requestCode, null, 0, 0, 0);
                    } catch (SendIntentException e) {
                        Log.e(K9.LOG_TAG, "SendIntentException", e);
                    }
                    break;
                }
                case OpenPgpApi.RESULT_CODE_ERROR: {
                    OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
                    handleError(error);
                    break;
                }
            }
        }
    }

    public boolean handleOnActivityResult(int requestCode, int resultCode, Intent data) {
        if (K9.DEBUG)
            Log.d(K9.LOG_TAG, "onActivityResult resultCode: " + resultCode);

        // try again after user interaction
        if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE_DECRYPT_VERIFY) {
            /*
             * The data originally given to the decryptVerify() method, is again
             * returned here to be used when calling decryptVerify() after user
             * interaction. The Intent now also contains results from the user
             * interaction, for example selected key ids.
             */
            decryptVerify(data);

            return true;
        }

        return false;
    }

    private void handleError(final OpenPgpError error) {
        Activity activity = mFragment.getActivity();
        if (activity == null) {
            return;
        }
        activity.runOnUiThread(new Runnable() {

            @Override
            public void run() {
                mProgress.setVisibility(View.GONE);

                if (K9.DEBUG) {
                    Log.d(K9.LOG_TAG, "OpenPGP Error ID:" + error.getErrorId());
                    Log.d(K9.LOG_TAG, "OpenPGP Error Message:" + error.getMessage());
                }

                mText.setText(mFragment.getString(R.string.openpgp_error) + " "
                        + error.getMessage());
                MessageOpenPgpViewOld.this.setBackgroundColor(mFragment.getResources().getColor(
                        R.color.openpgp_red));
            }
        });
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        // bind to service if a OpenPGP Provider is available
        if (mOpenPgpProvider != null) {
            mOpenPgpServiceConnection = new OpenPgpServiceConnection(mFragment.getActivity(),
                    mOpenPgpProvider);
            mOpenPgpServiceConnection.bindToService();
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();

        if (mOpenPgpServiceConnection != null) {
            mOpenPgpServiceConnection.unbindFromService();
        }
    }

}
