package com.sap.dbmtk.demo;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Iterator;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

import net.miginfocom.swing.MigLayout;

import org.apache.log4j.Logger;

import com.sap.dbmtk.demo.SqlStatement.Type;
import com.sap.dbmtk.demo.ui.GTable;
import com.sap.dbmtk.demo.utils.Configuration;
import com.sap.dbmtk.demo.utils.ImageLabel;
import com.sap.dbmtk.demo.utils.StringUtil;
import com.sap.dbmtk.demo.utils.SwingUtils;

public abstract class AddOrChangeAbstract
extends JDialog
{
	private static Logger _logger = Logger.getLogger(AddOrChangeAbstract.class);
	private static final long serialVersionUID = 1L;

	public  static final ImageIcon NO_PICTURE = SwingUtils.readImageIcon(DemoApp.class, "images/no_photo.jpg");

	public  static final int       MAX_PICTURE_SIZE = 50*1024;

	public final static String     PROPKEY_APP_PREFIX  = "DemoClient.";

	public final static String     PROPKEY_blankToNull = PROPKEY_APP_PREFIX + "AddOrChange.blank.to.null";
	public final static boolean    DEFAULT_blankToNull = false;

//	private final static boolean   _blankToNull = Configuration.getCombinedConfiguration().getBooleanProperty(PROPKEY_blankToNull, DEFAULT_blankToNull);

	protected String     _name  = "";
	protected Window     _owner = null;
	private   OpType     _opType;
//	private   Connection _conn  = null;
	private   ConnectionProvider _connProvider  = null;

	private JLabel  _description   = new JLabel();
	private JLabel  _deleteWarning = new JLabel("<html><font color=\"red\">Are you <b>sure</b> you want to delete</font></html>");
	private JLabel  _problemDesc   = new JLabel();

	private JButton _ok     = new JButton("OK");
	private JButton _cancel = new JButton("Cancel");

	protected int   _ret       = CANCEL;
	protected int   _status    = UNKNOWN;


	public enum OpType { INSERT, UPDATE, DELETE };

	public static final int CANCEL   = -1;
	public static final int OK       = 1;

	public static final int UNKNOWN  = 0;
	public static final int FAIL     = -1;
	public static final int SUCCESS  = 1;

	// PICTURE stuff
	protected JLabel     _picture_lbl    = new JLabel("Picture");
	protected JTextField _picture_txt    = new JTextField();
	protected JButton    _picture_but    = new JButton("...");
	protected ImageLabel _picture_ilbl   = new ImageLabel(NO_PICTURE, new Dimension(170, 220)); // boundary 170*220
	protected byte[]     _picture_ba     = null;
	protected String     _picture_lastLoaded = "";

	private boolean getConf_blankToNull()
	{
		return Configuration.getCombinedConfiguration().getBooleanProperty(PROPKEY_blankToNull, DEFAULT_blankToNull);
	}

	private static String createDescription(OpType type, String name)
	{
		String prefix = "UNKNOWN";
		String suffix = "";
		switch (type)
		{
		case INSERT: prefix = "New ";    suffix = ".";         break;
		case UPDATE: prefix = "Change "; suffix = " details."; break;
		case DELETE: prefix = "Remove "; suffix = " details."; break;
		}
		return prefix + name + suffix;
	}

	public AddOrChangeAbstract(String name, Window owner, ConnectionProvider connProvider, OpType opType)
	{
		super(owner, createDescription(opType, name), ModalityType.APPLICATION_MODAL);
		_name         = name;
		_owner        = owner;
		_connProvider = connProvider;
		_opType       = opType;
	}


	protected void init()
	{
		setLayout(new MigLayout());

		_description.setText(createDescription(_opType, _name));
		_description.setFont(new java.awt.Font(Font.DIALOG, Font.BOLD, 14));

		_problemDesc.setForeground(Color.RED);

		add(_description,    "span, wrap 15");

		// do initialization in the real implementor
		add(createPanel(),   "grow, push, wrap");

		add(new JLabel(),    "split, span, growx, pushx"); // dummy label to push everything to the right
		add(_problemDesc,    "hidemode 3");
		add(_deleteWarning,  "hidemode 3");
		add(_ok,             "tag ok");
		add(_cancel,         "tag cancel");

		// Set visibility for some components
		_deleteWarning.setVisible(false);
		if (OpType.DELETE.equals(_opType))
			_deleteWarning.setVisible(true);

		if ( isDelete())
		{
			_picture_txt    .setEnabled(false);
			_picture_but    .setEnabled(false);
		}

		_cancel.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent e)
			{
				_ret = CANCEL;
				setVisible(false);
			}
		});

		_ok.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent e)
			{
				_status = FAIL;
				switch (_opType)
				{
				case INSERT: if (internal_doInsert()) _status = SUCCESS;  break;
				case UPDATE: if (internal_doUpdate()) _status = SUCCESS;  break;
				case DELETE: if (internal_doDelete()) _status = SUCCESS;  break;
				}

//				doInsertOrUpdate(_add);
				if (_status == SUCCESS)
				{
					_ret = OK;
					setVisible(false);
				}

				// ALWAYS DO: to check connection status
				if (DemoApp.hasInstance())
					DemoApp.getInstance().refreshStatusBar();
			}
		});

		_picture_but.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent e)
			{
				JFileChooser fc = new JFileChooser();
				int returnVal = fc.showOpenDialog(AddOrChangeAbstract.this);

				if (returnVal == JFileChooser.APPROVE_OPTION)
				{
					File file = fc.getSelectedFile();
					loadImage(file.toString());
				}
			}
		});

		_picture_txt.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent e)
			{
				loadImage( _picture_txt.getText() );
			}
		});
		_picture_txt.addFocusListener(new FocusListener()
		{
			@Override
			public void focusLost(FocusEvent e)
			{
				loadImage( _picture_txt.getText() );
			}

			@Override public void focusGained(FocusEvent e) {}
		});

		pack();
		setLocationRelativeTo(_owner);
		
		validateContent();
	}

	public void validateContent()
	{
		String problem = getProblemText();

		if (problem == null)
		{
			_ok.setEnabled(true);
			_problemDesc.setText("");
		}
		else
		{
			_ok.setEnabled(false);
			_problemDesc.setText(problem);
		}
	}

	public String getProblemText()
	{
		return null;
	}


	public Object sqlFix(Object obj, boolean addQuotes)
	{
		return sqlFix(obj, true, addQuotes);
	}
	public Object sqlFix(Object obj, boolean emptyStringToNull, boolean addQuotes)
	{
		if (obj == null)
			return "NULL";

		if (obj instanceof String)
		{
			String str = (String) obj;

			if (emptyStringToNull && "".equals(str))
				return "NULL";

			if (str.equalsIgnoreCase("null"))
    			return "NULL";

    		if (addQuotes)
    		{
    			str = str.replace("'", "''");
    			return "'" + str + "'";
    		}

    		return str;
		}
		return obj;
	}

	public String getStringValue(JTextField field)
	{
		return getStringValue(field, getConf_blankToNull());
	}
	public String getStringValue(JTextField field, boolean emptyIsNull)
	{
		String str = field.getText();
		if (str == null)
			return null;

		if (str.equalsIgnoreCase("NULL"))
			return null;

		if (emptyIsNull && str.trim().equals(""))
			return null;

		return str;
	}

	public BigDecimal getBigDecimalValue(JFormattedTextField field)
	{
		Object o = field.getValue();
		if (o == null)
			return null;

		if (o instanceof Number)
		{
			return new BigDecimal( ((Number)o).doubleValue() );
		}

		try
		{
			return new BigDecimal( o.toString() );
		}
		catch (NumberFormatException e)
		{
			return null;
		}
	}

	public BigDecimal getBigDecimalValue(JTextField field)
	{
		String str = field.getText();
		if (str == null)
			return null;

		if (str.equalsIgnoreCase("NULL"))
			return null;

		if (str.trim().equals(""))
			return null;

		try
		{
			return new BigDecimal( str );
		}
		catch (NumberFormatException e)
		{
			e.printStackTrace();
			return null;
		}
	}

	public byte[] getPicture()
	{
		return _picture_ba;
	}

	/**
	 * @return OK or CANCEL
	 */
	public int open()
	{
		setVisible(true);
		return _ret;
	}

	public int getStatus()
	{
		return _status;
	}

	public OpType getOpType()
	{
		return _opType;
	}

	public boolean isInsert()
	{
		return OpType.INSERT.equals(_opType);
	}
	public boolean isUpdate()
	{
		return OpType.UPDATE.equals(_opType);
	}
	public boolean isDelete()
	{
		return OpType.DELETE.equals(_opType);
	}

	public ConnectionProvider getConnectionProvider()
	{
		return _connProvider;
	}

	public Connection getConnection()
	throws SQLException
	{
		return _connProvider.getConnection();
	}

	abstract public void setFields(GTable table, int vrow);

	abstract public JPanel createPanel();

//	abstract public void doInsertOrUpdate(boolean isInsert);
	abstract public boolean doInsert();
	abstract public boolean doUpdate();
	abstract public boolean doDelete();

	private boolean internal_doInsert()
	{
		boolean retval = doInsert();

		SqlStatement.showWarningMessages(getConnectionProvider(), getOwner(), "Warning messages from db for: "+getName());

		return retval;
	}

	private boolean internal_doUpdate()
	{
		boolean retval = doUpdate();

		SqlStatement.showWarningMessages(getConnectionProvider(), getOwner(), "Warning messages from db for: "+getName());
		
		return retval;
	}

	private boolean internal_doDelete()
	{
		boolean retval = doDelete();
		
		SqlStatement.showWarningMessages(getConnectionProvider(), getOwner(), "Warning messages from db for: "+getName());
		
		return retval;
	}
	
	public byte[] getImageFromDb(String procStr, String keyCol, Integer key)
	{
		SqlStatement stmnt = new SqlStatement(Type.CALLABLE, procStr);

		stmnt.addParam( SqlStatementParam.createInParam(keyCol, Types.INTEGER, key) );

		stmnt.addParam( SqlStatementParam.createOracleResultSetParam("a_cursor") );

		try
		{
			ResultSet rs = stmnt.executeQuery(getConnectionProvider());

			byte[] ba = null;
			while (rs.next())
			{
				ba = rs.getBytes(1);
			}
			SqlStatement.putWarningMessages(getConnectionProvider(), null, rs);
			rs.close();

			return ba;
		}
		catch (SQLException e)
		{
			SqlStatement.showErrorMessage(e, stmnt, getOwner(), "Getting Picture <b>failed.</b>");
			return null;
		}
	}


	private byte[] compressImage(BufferedImage image, float quality)
	throws IOException
	{
		// Get a ImageWriter for jpeg format.
		Iterator<ImageWriter> writers = ImageIO.getImageWritersBySuffix("jpeg");
		if ( !writers.hasNext() )
			throw new IllegalStateException("No writers found");
		ImageWriter writer = (ImageWriter) writers.next();

		// Create the ImageWriteParam to compress the image.
		ImageWriteParam param = writer.getDefaultWriteParam();
		param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
		param.setCompressionQuality(quality);

		// The output will be a ByteArrayOutputStream (in memory)
		ByteArrayOutputStream bos = new ByteArrayOutputStream(32768);
		ImageOutputStream ios = ImageIO.createImageOutputStream(bos);
		writer.setOutput(ios);
		writer.write(null, new IIOImage(image, null, null), param);
		ios.flush(); // otherwise the buffer size will be zero!

		return bos.toByteArray();

		// From the ByteArrayOutputStream create a RenderedImage.
//		ByteArrayInputStream in = new ByteArrayInputStream(bos.toByteArray());
//		RenderedImage out = ImageIO.read(in);
//		int size = bos.toByteArray().length;
	}

	protected void loadImage(String fileStr)
	{
		if (StringUtil.isNullOrBlank(fileStr))
			return;

		if (fileStr.equals(_picture_lastLoaded))
		{
			_logger.info("loadImage(): The filename '"+fileStr+"' already seems to be loaded..." );
			return;
		}

		try
		{
			File f = new File(fileStr);

			byte[] imageData = new byte[(int) f.length()];
			DataInputStream dis = new DataInputStream(new FileInputStream(f));
			dis.readFully(imageData);
			dis.close();

			if (f.length() > MAX_PICTURE_SIZE)
			{
				// convert byte array back to BufferedImage
				InputStream in = new ByteArrayInputStream(imageData);
				BufferedImage bImageFromConvert = ImageIO.read(in);

				// Try to compress the image
				long compSize = f.length();
				float compQuality = 1.0f;
				float[] compQualities = {1.0f, 0.9f, 0.8f, 0.7f, 0.6f, 0.5f, 0.4f, 0.3f, 0.2f, 0.1f, 0.09f, 0.08f, 0.097f, 0.06f, 0.05f, 0.04f, 0.03f, 0.02f, 0.01f};
				for (int cr=0; cr<compQualities.length; cr++)
				{
					compQuality = compQualities[cr];
					imageData = compressImage(bImageFromConvert, compQuality);
					compSize  = imageData.length;
					if (compSize < MAX_PICTURE_SIZE)
						break;
				}

				if (compSize < MAX_PICTURE_SIZE)
				{
					String htmlMsg =
							"<html>"
							+ "I had to <b>compress</b> the picture, it was above the limit.<br>"
							+ "The maximum uncompressed size is "+StringUtil.bytesToHuman(MAX_PICTURE_SIZE)+"<br>"
							+ "<br>"
							+ "The origin file: "+f+"<br>"
							+ "The origin size was "+StringUtil.bytesToHuman(f.length())+"<br>"
							+ "<br>"
							+ "The used compression quality was "+compQuality+"<br>"
							+ "The final compressed size was "+StringUtil.bytesToHuman(compSize)+"<br>"
							+ "<br>"
							+ "<i>JPEG files is preferred, other filetypes might not compress well.</i><br>"
							+ "</html>";
					SwingUtils.showWarnMessage(this, "Picture was compressed", htmlMsg, null);
				}
				else
				{
					String htmlMsg =
							"<html>"
							+ "File size it <b>to large</b><br>"
							+ "<b>Sorry</b> you can't use this file!<br>"
							+ "<br>"
							+ "The file: "+f+"<br>"
							+ "The origin size was "+StringUtil.bytesToHuman(f.length())+"<br>"
							+ "<br>"
							+ "The result of unsuccessful compression:<br>"
							+ "The lowest used compression quality was "+compQuality+"<br>"
							+ "The lowest compressed size achived was "+StringUtil.bytesToHuman(compSize)+"<br>"
							+ "<br>"
							+ "<i>JPEG files is preferred, other filetypes might not compress well.</i><br>"
							+ "<br>"
							+ "The maximum allowed size is <b>"+StringUtil.bytesToHuman(MAX_PICTURE_SIZE)+"</b><br>"
							+ "<b>You need to choose another file</b><br>"
							+ "<br>"
							+ "</html>";
					SwingUtils.showErrorMessage(this, "Problem", htmlMsg, null);
					return;
				}
			}

			ImageIcon pic = new ImageIcon(imageData);

			_picture_ilbl.setIcon(pic);
			_picture_txt .setText(fileStr);
			_picture_ba  = imageData;

			_picture_lastLoaded = fileStr;
		}
		catch (Throwable e)
		{
			String htmlMsg =
					"<html>"
					+ "Reading file <b>failed.</b><br>"
					+ "<br>"
					+ "Exception: " + e + "<br>"
					+ "<br>"
					+ "</html>";
			SwingUtils.showErrorMessage(this, "Problem", htmlMsg, e);
		}
	}

	protected void loadImage(byte[] image)
	{
		if (image == null)
			return;

		ImageIcon pic = new ImageIcon(image);
		_picture_ilbl.setIcon(pic);
//		_picture_ba  = image;
	}

}
