package com.dgex.offspring.ui;

import java.io.UnsupportedEncodingException;

import nxt.Account;
import nxt.Constants;
import nxt.NxtException.ValidationException;
import nxt.Transaction;
import nxt.util.Convert;

import org.apache.log4j.Logger;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Text;

import com.dgex.offspring.config.Config;
import com.dgex.offspring.nxtCore.service.IAccount;
import com.dgex.offspring.nxtCore.service.INxtService;
import com.dgex.offspring.nxtCore.service.TransactionException;
import com.dgex.offspring.swt.wizard.GenericTransactionWizard;
import com.dgex.offspring.swt.wizard.IGenericTransaction;
import com.dgex.offspring.swt.wizard.IGenericTransactionField;
import com.dgex.offspring.swt.wizard.IMultiLineTextTransactionField;
import com.dgex.offspring.ui.messaging.MessageCrypto;
import com.dgex.offspring.user.service.IUserService;

public class SendMessageWizard extends GenericTransactionWizard {

  static Logger logger = Logger.getLogger(SendMessageWizard.class);

  final IGenericTransactionField fieldRecipient = new IGenericTransactionField() {

    private Text textRecipient;
    private Text textRecipientReadonly;

    @Override
    public String getLabel() {
      return "Recipient";
    }

    @Override
    public Object getValue() {
      String recipientValue = textRecipient.getText().trim();
      try {
        return Convert.parseUnsignedLong(recipientValue);
      }
      catch (RuntimeException e) {
        logger.error("Parse Recipient ID", e);
      }
      return null;
    }

    @Override
    public Control createControl(Composite parent) {
      textRecipient = new Text(parent, SWT.BORDER);
      textRecipient.setMessage("account number");
      if (accountId != null)
        textRecipient.setText(Convert.toUnsignedLong(accountId));
      else
        textRecipient.setText("");
      textRecipient.addModifyListener(new ModifyListener() {

        @Override
        public void modifyText(ModifyEvent e) {
          requestVerification();
        }
      });
      return textRecipient;
    }

    @Override
    public Control createReadonlyControl(Composite parent) {
      textRecipientReadonly = new Text(parent, SWT.BORDER);
      textRecipientReadonly.setText("");
      textRecipientReadonly.setEditable(false);
      return textRecipientReadonly;
    }

    @Override
    public boolean verify(String[] message) {
      String recipientValue = textRecipient.getText().trim();
      if ("0".equals(recipientValue)) {
        message[0] = "Missing recipient";
        return false;
      }
      try {
        Convert.parseUnsignedLong(recipientValue);
      }
      catch (RuntimeException e) {
        message[0] = "Incorrect recipient";
        return false;
      }
      textRecipientReadonly.setText(recipientValue);
      return true;
    }
  };

  final IGenericTransactionField fieldReferenced = new IGenericTransactionField() {

    private Text textReferenced;
    private Text textReferencedReadonly;

    @Override
    public String getLabel() {
      return "In-reply to";
    }

    @Override
    public Object getValue() {
      String recipientValue = textReferenced.getText().trim();
      if (recipientValue.isEmpty()) {
        return null;
      }
      try {
        return Convert.parseUnsignedLong(recipientValue);
      }
      catch (RuntimeException e) {
        logger.error("Parse Recipient ID", e);
      }
      return null;
    }

    @Override
    public Control createControl(Composite parent) {
      textReferenced = new Text(parent, SWT.BORDER);
      textReferenced.setMessage("transaction id or empty");
      if (referencedTransactionId != null)
        textReferenced.setText(Convert.toUnsignedLong(referencedTransactionId));
      else
        textReferenced.setText("");
      textReferenced.addModifyListener(new ModifyListener() {

        @Override
        public void modifyText(ModifyEvent e) {
          requestVerification();
        }
      });
      return textReferenced;
    }

    @Override
    public Control createReadonlyControl(Composite parent) {
      textReferencedReadonly = new Text(parent, SWT.BORDER);
      textReferencedReadonly.setText("");
      textReferencedReadonly.setEditable(false);
      return textReferencedReadonly;
    }

    @Override
    public boolean verify(String[] message) {
      String recipientValue = textReferenced.getText().trim();
      if (!recipientValue.isEmpty()) {
        try {
          /* Long id = */Convert.parseUnsignedLong(recipientValue);
          // Transaction t = Nxt.getBlockchain().getTransaction(id);
          // if (t == null) {
          // message[0] = "Referenced transaction does not exist";
          // return false;
          // }
        }
        catch (RuntimeException e) {
          message[0] = "Incorrect referenced transaction";
          return false;
        }
      }
      textReferencedReadonly.setText(recipientValue);
      return true;
    }
  };

  final IGenericTransactionField fieldEncrypt = new IGenericTransactionField() {

    private Button buttonEncrypt;
    private Button buttonEncryptReadonly;

    @Override
    public String getLabel() {
      return "Encrypt";
    }

    @Override
    public Object getValue() {
      return Boolean.valueOf(buttonEncrypt.getSelection());
    }

    @Override
    public Control createControl(Composite parent) {
      buttonEncrypt = new Button(parent, SWT.CHECK);
      buttonEncrypt
          .setText("Encrypt message (only you and recipient can read this message)");
      buttonEncrypt.setSelection(useEncryption);
      buttonEncrypt.addSelectionListener(new SelectionAdapter() {

        @Override
        public void widgetSelected(org.eclipse.swt.events.SelectionEvent e) {
          requestVerification();
        };
      });
      return buttonEncrypt;
    }

    @Override
    public Control createReadonlyControl(Composite parent) {
      buttonEncryptReadonly = new Button(parent, SWT.CHECK);
      buttonEncryptReadonly
          .setText("Private message (Private/Public key encryption)");
      buttonEncryptReadonly.setSelection(true);
      return buttonEncryptReadonly;
    }

    @Override
    public boolean verify(String[] message) {
      buttonEncryptReadonly.setSelection((boolean) getValue());
      return true;
    }
  };

  final IMultiLineTextTransactionField fieldMessage = new IMultiLineTextTransactionField() {

    private Text textMessage;
    private Text textMessageReadonly;

    @Override
    public String getLabel() {
      return "Message";
    }

    @Override
    public Object getValue() {
      return textMessage.getText().trim();
    }

    @Override
    public Control createControl(Composite parent) {
      textMessage = new Text(parent, SWT.MULTI | SWT.BORDER | SWT.WRAP
          | SWT.V_SCROLL | SWT.H_SCROLL);
      textMessage.addModifyListener(new ModifyListener() {

        @Override
        public void modifyText(ModifyEvent e) {
          requestVerification();
        }
      });
      return textMessage;
    }

    @Override
    public Control createReadonlyControl(Composite parent) {
      textMessageReadonly = new Text(parent, SWT.BORDER);
      textMessageReadonly.setText("");
      textMessageReadonly.setEditable(false);
      return textMessageReadonly;
    }

    @Override
    public boolean verify(String[] message) {
      String text = textMessage.getText().trim();

      /* Message cannot excede max length */
      try {
        int length = text.getBytes("UTF-8").length;
        if ((Boolean) fieldEncrypt.getValue()) {
          length += Config.MAGIC_ENCRYPTED_MESSAGE_NUMBER.length + 32;
        }
        else {
          length += Config.MAGIC_UNENCRYPTED_MESSAGE_NUMBER.length;
        }
        if (length > Constants.MAX_ARBITRARY_MESSAGE_LENGTH) {
          message[0] = "Message is to long (max 1000 bytes, yours is " + length
              + "bytes)";
          return false;
        }
      }
      catch (UnsupportedEncodingException e) {
        e.printStackTrace();
        message[0] = "UTF-8 character set not supported on your machine";
        return false;
      }

      textMessageReadonly.setText(text);
      return true;
    }
  };

  private Long accountId = null;
  private Long referencedTransactionId = null;
  private boolean useEncryption = true;

  public SendMessageWizard(final IUserService userService, final INxtService nxt,
 Long accountId, Long referencedTransactionId,
      boolean useEncryption) {
    super(userService);
    this.accountId = accountId;
    this.referencedTransactionId = referencedTransactionId;
    this.useEncryption = useEncryption;
    setWindowTitle("Send Message");
    setTransaction(new IGenericTransaction() {

      @Override
      public String sendTransaction(String[] message) {

        IAccount sender = user.getAccount();
        Long recipient = (Long) fieldRecipient.getValue();
        Long referencedId = (Long) fieldReferenced.getValue();
        boolean encrypt = (Boolean) fieldEncrypt.getValue();
        String messageValue = (String) fieldMessage.getValue();

        PromptFeeDeadline dialog = new PromptFeeDeadline(getShell());
        if (dialog.open() != Window.OK) {
          message[0] = "Invalid fee and deadline";
          return null;
        }
        int fee = dialog.getFee();
        short deadline = dialog.getDeadline();

        try {
          byte[] data;
          if (encrypt) {
            byte[] theirPublicKey = Account.getAccount(recipient)
                .getPublicKey();
            data = MessageCrypto.encrypt(messageValue, sender.getPrivateKey(),
                theirPublicKey);
          }
          else {
            byte[] magic = Config.MAGIC_UNENCRYPTED_MESSAGE_NUMBER;
            byte[] bytes = messageValue.getBytes("UTF-8");
            data = new byte[magic.length + bytes.length];

            System.arraycopy(magic, 0, data, 0, magic.length);
            System.arraycopy(bytes, 0, data, magic.length, bytes.length);
          }

          Transaction t = nxt.createSendMessageTransaction(sender, recipient,
              data, deadline, fee, referencedId);
          return t.getStringId();
        }
        catch (TransactionException e) {
          message[0] = e.getMessage();
        }
        catch (ValidationException e) {
          message[0] = e.getMessage();
        }
        catch (UnsupportedEncodingException e) {
          message[0] = e.getMessage();
        }
        return null;
      }

      @Override
      public IGenericTransactionField[] getFields() {
        return new IGenericTransactionField[] { fieldSender, fieldRecipient,
            fieldReferenced, fieldEncrypt, fieldMessage };
      }

      @Override
      public boolean verifySender(String message[]) {
        if (user == null) {
          message[0] = "Invalid sender";
          return false;
        }
        if (user.getAccount().isReadOnly()) {
          message[0] = "This is a readonly account";
          return false;
        }
        if (user.getAccount().getBalance() < 1) {
          message[0] = "Insufficient balance";
          return false;
        }
        return true;
      }
    });
  }
}