package org.ethereum.gui;

import org.ethereum.core.Account;
import org.ethereum.core.Block;
import org.ethereum.core.Transaction;
import org.ethereum.core.Wallet;
import org.ethereum.db.ContractDetails;
import org.ethereum.util.ByteUtil;
import org.ethereum.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.BigIntegers;
import org.spongycastle.util.encoders.Hex;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.EtchedBorder;
import javax.swing.plaf.ComboBoxUI;
import javax.swing.plaf.basic.BasicComboPopup;
import javax.swing.table.DefaultTableModel;

import java.awt.*;
import java.awt.event.*;
import java.math.BigInteger;
import java.net.URL;
import java.util.Collection;
import java.util.Map;
/**
 * www.ethereumJ.com
 * @author: Roman Mandeleil
 * Created on: 18/05/14 22:21
 */
class ContractCallDialog extends JDialog implements MessageAwareDialog {

	private static final long serialVersionUID = -7561153561155037293L;

	private Logger logger = LoggerFactory.getLogger("ui");

	private ContractCallDialog dialog;
	private JComboBox<AccountWrapper> creatorAddressCombo;
	private final JTextField gasInput;
	private final JTextField contractAddrInput;

	private JScrollPane contractDataInput;
	private JTextArea   msgDataTA;

	private JLabel statusMsg = null;
	private JLabel playLabel = null;
	private JLabel rejectLabel = null;
	private JLabel approveLabel = null;

    public ContractCallDialog(Frame parent) {
        super(parent, "Call Contract: ", false);
        dialog = this;

        contractAddrInput = new JTextField(5);
        GUIUtils.addStyle(contractAddrInput, "Contract Address: ");
        contractAddrInput.addFocusListener(new FocusAdapter() {
            @Override
            public void focusLost(FocusEvent e) {

                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        populateContractDetails();
                    }
                });

            }
        });

        contractAddrInput.setBounds(70, 30, 350, 45);
        this.getContentPane().add(contractAddrInput);

        gasInput = new JTextField(5);
        GUIUtils.addStyle(gasInput, "Gas: ");

        msgDataTA = new JTextArea();
        msgDataTA.setLineWrap(true);
        contractDataInput = new JScrollPane(msgDataTA);
        GUIUtils.addStyle(msgDataTA, null, false);
        GUIUtils.addStyle(contractDataInput, "Input:");

        msgDataTA.setText("");
        msgDataTA.setCaretPosition(0);

        this.getContentPane().setBackground(Color.WHITE);
        this.getContentPane().setLayout(null);

        contractDataInput.setBounds(70, 80, 350, 165);
        this.getContentPane().add(contractDataInput);

        gasInput.setBounds(330, 260, 90, 45);
        this.getContentPane().add(gasInput);

        URL rejectIconURL = ClassLoader.getSystemResource("buttons/reject.png");
        ImageIcon rejectIcon = new ImageIcon(rejectIconURL);
        rejectLabel = new JLabel(rejectIcon);
        rejectLabel.setToolTipText("Cancel");
        rejectLabel.setCursor(new Cursor(Cursor.HAND_CURSOR));

        URL playIconURL = ClassLoader.getSystemResource("buttons/play.png");
        ImageIcon playIcon = new ImageIcon(playIconURL);
        playLabel = new JLabel(playIcon);
        playLabel.setToolTipText("Play Drafted");
        playLabel.setCursor(new Cursor(Cursor.HAND_CURSOR));
        playLabel.addMouseListener(
                new MouseAdapter() {
                    @Override
                    public void mouseClicked(MouseEvent e) {
                          ContractCallDialog.this.playContractCall();
                    }}
        );

        playLabel.setBounds(438, 100, 42, 42);
        this.getContentPane().add(playLabel);

        JLabel statusMessage = new JLabel("");
        statusMessage.setBounds(50, 360, 400, 50);
        statusMessage.setHorizontalAlignment(SwingConstants.CENTER);
        this.statusMsg = statusMessage;
        this.getContentPane().add(statusMessage);

        rejectLabel.setBounds(260, 325, 45, 45);
        this.getContentPane().add(rejectLabel);
        rejectLabel.setVisible(true);
		rejectLabel.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				dialog.dispose();
			}
		});

        URL approveIconURL = ClassLoader.getSystemResource("buttons/approve.png");
        ImageIcon approveIcon = new ImageIcon(approveIconURL);
        approveLabel = new JLabel(approveIcon);
        approveLabel.setToolTipText("Submit the transaction");
        approveLabel.setCursor(new Cursor(Cursor.HAND_CURSOR));

        approveLabel.setBounds(200, 325, 45, 45);
        this.getContentPane().add(approveLabel);
        approveLabel.setVisible(true);

        approveLabel.addMouseListener(
                new MouseAdapter() {
                    @Override
                    public void mouseClicked(MouseEvent e) {
                        submitContractCall();
                    }
                }
        );

        gasInput.setText("1000");

		JComboBox<AccountWrapper> creatorAddressCombo = new JComboBox<AccountWrapper>() {
			private static final long serialVersionUID = -3748305421049121671L;
			@Override
			public ComboBoxUI getUI() {
				return super.getUI();
			}
		};
        creatorAddressCombo.setOpaque(true);
        creatorAddressCombo.setEnabled(true);

        creatorAddressCombo.setBackground(Color.WHITE);
        creatorAddressCombo.setFocusable(false);

        this.creatorAddressCombo = creatorAddressCombo;

        final Border line = BorderFactory.createLineBorder(Color.LIGHT_GRAY);
        JComponent editor = (JComponent)(creatorAddressCombo.getEditor().getEditorComponent());
        editor.setForeground(Color.RED);

        Wallet wallet = UIEthereumManager.ethereum.getWallet();
        Collection<Account> accounts = wallet.getAccountCollection();

        for (Account account : accounts) {
            creatorAddressCombo.addItem(new AccountWrapper(account));
        }

		creatorAddressCombo.setRenderer(new DefaultListCellRenderer() {
			private static final long serialVersionUID = 6100091092527477892L;

			@Override
			public void paint(Graphics g) {
				setBackground(Color.WHITE);
				setForeground(new Color(143, 170, 220));
				setFont(new Font("Monospaced", 0, 13));
				setBorder(BorderFactory.createEmptyBorder());
				super.paint(g);
			}
		});

        creatorAddressCombo.setPopupVisible(false);

        Object child = creatorAddressCombo.getAccessibleContext().getAccessibleChild(0);
        BasicComboPopup popup = (BasicComboPopup)child;

        JList list = popup.getList();
        list.setSelectionBackground(Color.cyan);
        list.setBorder(null);

		for (int i = 0; i < creatorAddressCombo.getComponentCount(); i++) {
            if (creatorAddressCombo.getComponent(i) instanceof CellRendererPane) {
                CellRendererPane crp = ((CellRendererPane) (creatorAddressCombo.getComponent(i)));
            }
            if (creatorAddressCombo.getComponent(i) instanceof AbstractButton) {
                ((AbstractButton) creatorAddressCombo.getComponent(i)).setBorder(line);
            }
        }
        creatorAddressCombo.setBounds(70, 267, 230, 36);
        this.getContentPane().add(creatorAddressCombo);

        this.getContentPane().revalidate();
        this.getContentPane().repaint();
        this.setResizable(false);
        
        this.setVisible(true);
    }

    private void populateContractDetails() {
		byte[] addr = Utils.addressStringToBytes(contractAddrInput.getText());
		if(addr == null) {
			alertStatusMsg("Not a valid contract address");
        	return;
		}
			
		ContractDetails contractDetails = UIEthereumManager.ethereum
				.getRepository().getContractDetails(addr);
        if (contractDetails == null) {
            alertStatusMsg("No contract for that address");
            return;
        }

        final byte[] programCode = contractDetails.getCode();
        if (programCode == null || programCode.length == 0) {
            alertStatusMsg("Such account exist but no code in the db");
            return;
        }
        
        final Map storageMap = contractDetails.getStorage();

        contractDataInput.setBounds(70, 80, 350, 145);
        contractDataInput.setViewportView(msgDataTA);

        URL expandIconURL = ClassLoader.getSystemResource("buttons/add-23x23.png");
        ImageIcon expandIcon = new ImageIcon(expandIconURL);
        final JLabel expandLabel = new JLabel(expandIcon);
        expandLabel.setToolTipText("Cancel");
        expandLabel.setCursor(new Cursor(Cursor.HAND_CURSOR));
        expandLabel.setBounds(235, 232, 23, 23);
        this.getContentPane().add(expandLabel);
        expandLabel.setVisible(true);

        final Border border = BorderFactory.createEtchedBorder(EtchedBorder.LOWERED);
        final JPanel detailPanel = new JPanel();
        detailPanel.setBorder(border);
        detailPanel.setBounds(135, 242, 230, 2);

        final JPanel spacer = new JPanel();
        spacer.setForeground(Color.white);
        spacer.setBackground(Color.white);
        spacer.setBorder(null);
        spacer.setBounds(225, 232, 40, 20);

        this.getContentPane().add(spacer);
        this.getContentPane().add(detailPanel);

        expandLabel.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                ContractCallDialog.this.setSize(500, 530);

                ContractCallDialog.this.creatorAddressCombo.setBounds(70, 367, 230, 36);
                ContractCallDialog.this.gasInput.setBounds(330, 360, 90, 45);
                ContractCallDialog.this.rejectLabel.setBounds(260, 425, 45, 45);
                ContractCallDialog.this.approveLabel.setBounds(200, 425, 45, 45);
                ContractCallDialog.this.statusMsg.setBounds(50, 460, 400, 50);

                spacer.setVisible(false);
                expandLabel.setVisible(false);
                detailPanel.setVisible(false);

                JTextField contractCode = new JTextField(15);
                contractCode.setText(Hex.toHexString( programCode ));
                GUIUtils.addStyle(contractCode, "Code: ");
                contractCode.setBounds(70, 230, 350, 45);

                JTable storage = new JTable(2, 2);
                storage.setTableHeader(null);
                storage.setShowGrid(false);
                storage.setIntercellSpacing(new Dimension(15, 0));
                storage.setCellSelectionEnabled(false);
                GUIUtils.addStyle(storage);

                JTableStorageModel tableModel = new JTableStorageModel(storageMap);
                storage.setModel(tableModel);

                JScrollPane scrollPane = new JScrollPane(storage);
                scrollPane.setBorder(null);
                scrollPane.getViewport().setBackground(Color.WHITE);
                scrollPane.setBounds(70, 290, 350, 50);


                ContractCallDialog.this.getContentPane().add(contractCode);
                ContractCallDialog.this.getContentPane().add(scrollPane);
            }
        });
        this.repaint();
    }
    
    private void playContractCall() {   	
        byte[] addr = Utils.addressStringToBytes(contractAddrInput.getText());
		if(addr == null) {
			alertStatusMsg("Not a valid contract address");
        	return;
		}
		
		ContractDetails contractDetails = UIEthereumManager.ethereum
				.getRepository().getContractDetails(addr);
        if (contractDetails == null) {
            alertStatusMsg("No contract for that address");
            return;
        }

        final byte[] programCode = contractDetails.getCode();
        if (programCode == null || programCode.length == 0) {
            alertStatusMsg("Such account exist but no code in the db");
            return;
        }

        Transaction tx = createTransaction();
        if (tx == null) return;

        Block lastBlock = UIEthereumManager.ethereum.getBlockchain().getLastBlock();
        ProgramPlayDialog.createAndShowGUI(programCode, tx, lastBlock);
    }

    protected JRootPane createRootPane() {

        Container parent = this.getParent();

        if (parent != null) {
            Dimension parentSize = parent.getSize();
            Point p = parent.getLocation();
            setLocation(p.x + parentSize.width / 4, p.y + 10);
        }

        JRootPane rootPane = new JRootPane();
        KeyStroke stroke = KeyStroke.getKeyStroke("ESCAPE");
        Action actionListener = new AbstractAction() {
            public void actionPerformed(ActionEvent actionEvent) {
                dispose();
            }
        };
        InputMap inputMap = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
        inputMap.put(stroke, "ESCAPE");
        rootPane.getActionMap().put("ESCAPE", actionListener);

        this.setSize(500, 430);

        return rootPane;
    }

    public void infoStatusMsg(String text) {
        this.statusMsg.setForeground(Color.GREEN.darker().darker());
        this.statusMsg.setText(text);
    }

    public void alertStatusMsg(String text) {
        this.statusMsg.setForeground(Color.RED);
        this.statusMsg.setText(text);
    }

    public void submitContractCall() {

        if (!UIEthereumManager.ethereum.isConnected()) {
            dialog.alertStatusMsg("Not connected to any peer");
            return;
        }

        Transaction tx = createTransaction();
        if (tx == null) return;

        if (logger.isInfoEnabled()) {
            logger.info("tx.hash: {}", (new BigInteger(tx.getHash()).toString(16)));
        }
        // SwingWorker
        DialogWorker worker = new DialogWorker(tx, this);
        worker.execute();
    }

	private Transaction createTransaction() {

		byte[] data;
		if (!msgDataTA.getText().trim().equals("")) {
			Object[] lexaList = msgDataTA.getText().split(",");
			data = ByteUtil.encodeDataList(lexaList);
		} else {
			data = new byte[] {};
		}

        byte[] contractAddress = Hex.decode( contractAddrInput.getText());

        Account account = ((AccountWrapper)creatorAddressCombo.getSelectedItem()).getAccount();

        byte[] senderPrivKey = account.getEcKey().getPrivKeyBytes();
        byte[] nonce = account.getNonce() == BigInteger.ZERO ? null : account.getNonce().toByteArray();
        BigInteger gasPrice = new BigInteger("10000000000000");

        BigInteger gasBI = new BigInteger(gasInput.getText());
        byte[] gasValue  = BigIntegers.asUnsignedByteArray(gasBI);
        BigInteger endowment = new BigInteger("1000");

        if (logger.isInfoEnabled()) {
            logger.info("Contract call:");
            logger.info("tx.nonce: {}", nonce == null ? "null" : Hex.toHexString(nonce));
            logger.info("tx.gasPrice: {}", Hex.toHexString(BigIntegers.asUnsignedByteArray( gasPrice )));
            logger.info("tx.gasValue: {}", Hex.toHexString(gasValue));
            logger.info("tx.address: {}", Hex.toHexString(contractAddress));
            logger.info("tx.endowment: {}", Hex.toHexString(BigIntegers.asUnsignedByteArray( endowment)));
            logger.info("tx.data: {}", Hex.toHexString(data));
        }

        Transaction tx = UIEthereumManager.ethereum.createTransaction(account.getNonce(),
                gasPrice, gasBI,
                contractAddress, endowment, data);

        try {
            tx.sign(senderPrivKey);
        } catch (Exception e1) {
            dialog.alertStatusMsg("Failed to sign the transaction");
            return null;
        }
        return tx;
    }

	public class AccountWrapper {

		private Account account;

        public AccountWrapper(Account account) {
            this.account = account;
        }

        public Account getAccount() {
            return account;
        }

		@Override
		public String toString() {
			String addressShort = Utils.getAddressShortString(account.getEcKey().getAddress());
			String valueShort = Utils.getValueShortString(account.getBalance());
			String result = String.format(" By: [%s] %s", addressShort,
					valueShort);
			return result;
		}
    }

    private class JTableStorageModel extends DefaultTableModel {
        private JTableStorageModel(Map<String, String> data) {

            if (data != null) {

                this.setColumnCount(2);
                this.setRowCount(data.size());

                int i = 0;
                for (String key : data.keySet()) {
                    this.setValueAt(key, i, 0);
                    this.setValueAt(data.get(key), i, 1);
                    ++i;
                }
            }
        }
    }

    public static void main(String args[]) {
        ContractCallDialog ccd = new ContractCallDialog(null);
        ccd.setVisible(true);
        ccd.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
        ccd.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosed(WindowEvent e) {
                UIEthereumManager.ethereum.close();
            }
        });
    }
}