/**
 * gvSIG. Desktop Geographic Information System.
 *
 * Copyright (C) 2007-2013 gvSIG Association.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 *
 * For any additional information, do not hesitate to contact us
 * at info AT gvsig.com, or visit our website www.gvsig.com.
 */
package org.gvsig.tools.dynform.impl.jdynformset.subform;

import java.awt.BorderLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

import org.gvsig.tools.lang.Cloneable;
import org.gvsig.tools.dataTypes.DataType;
import org.gvsig.tools.dispose.DisposableIterator;
import org.gvsig.tools.dynform.AbortActionException;
import org.gvsig.tools.dynform.DynFormDefinition;
import org.gvsig.tools.dynform.JDynForm;
import org.gvsig.tools.dynform.JDynForm.JDynFormListener;
import org.gvsig.tools.dynform.JDynFormSet;
import org.gvsig.tools.dynform.impl.ActionStore;
import org.gvsig.tools.dynform.impl.DefaultDynFormManager;
import org.gvsig.tools.dynform.impl.DefaultVisitableSet;
import org.gvsig.tools.dynform.impl.FormSetButtonBar;
import org.gvsig.tools.dynform.impl.FormSetButtonBar.FormSetListener;
import org.gvsig.tools.dynform.spi.DynFormSPIManager;
import org.gvsig.tools.dynobject.DynObject;
import org.gvsig.tools.dynobject.DynObjectSet;
import org.gvsig.tools.exception.BaseException;
import org.gvsig.tools.service.Manager;
import org.gvsig.tools.service.Service;
import org.gvsig.tools.service.ServiceException;
import org.gvsig.tools.service.spi.ServiceManager;
import org.gvsig.tools.visitor.VisitCanceledException;
import org.gvsig.tools.visitor.Visitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SubformJDynFormSet implements JDynFormSet, FormSetListener, JDynFormListener, Service {

	private static final Logger logger = LoggerFactory.getLogger(SubformJDynFormSet.class);
	
	private DynFormSPIManager manager = null;
	private DynFormDefinition definition = null;
	private int layoutMode = USE_PLAIN;
	private JLabel jlabel_messages = null;
	private boolean readOnly = false;
	private JDynForm form = null;
	private FormSetButtonBar buttonBar = null;
	private JPanel contents = null;
	private List values=null;
	private int current=0;
	private DefaultVisitableSet listeners = null;
	private boolean autosave = true;

	private boolean hasActionDelete = true;
	private boolean hasActionNew = true;
	private boolean hasActionUpdate = true;
	private boolean hasActionSearch = true;
	
	private int formWidth = -1;
	private int formHeight = -1;
	
	private List<ActionStore> actionsBuffer = new ArrayList<ActionStore>();
	
	public SubformJDynFormSet(ServiceManager manager, DynFormDefinition definition) throws ServiceException {
		this.manager = (DynFormSPIManager)manager;
		this.definition = definition;
		this.listeners = new DefaultVisitableSet();
	}
	
	public JComponent asJComponent() {
		if( this.contents == null ) {
			try {
				this.initComponents();
			} catch (ServiceException e) {
				throw new RuntimeException(e.getLocalizedMessage(),e);
			}
		}
		this.fireFormMovedToEvent();
		return this.contents;
	}
	
	public void addListener(JDynFormSetListener listener) {
		this.listeners.add(listener);
	}

	public void removeListener(JDynFormSetListener listener) {
		this.listeners.remove(listener);
	}
	
	protected void fireMessageEvent(final String message) {
		try {
			this.listeners.accept(new Visitor() {
				public void visit(Object listener) throws VisitCanceledException, BaseException {
					((JDynFormSetListener)listener).formMessage(message);
				}
			});
		} catch (AbortActionException e) {
			// Do nothing 
		} catch (Exception e) {
			logger.info("Error calling to the form message event.",e);
		}
	}

	protected void fireCloseEvent() {
		try {
			this.listeners.accept(new Visitor() {
				public void visit(Object listener) throws VisitCanceledException, BaseException {
					((JDynFormSetListener)listener).formClose();
				}
			});
		} catch (AbortActionException e) {
			// Do nothing 
		} catch (Exception e) {
			logger.info("Error calling to the form close event.",e);
		}
	}
	
	protected void fireFormMovedToEvent() {
		try {
			this.listeners.accept(new Visitor() {
				public void visit(Object listener) throws VisitCanceledException, BaseException {
					((JDynFormSetListener)listener).formMovedTo(current);
				}
			});
		} catch (AbortActionException e) {
			// Do nothing 
		} catch (Exception e) {
			logger.info("Error calling to the form moved to event.",e);
		}
	}
	
	public JLabel getMessagesJLabel() {
		if( this.jlabel_messages == null ) {
			this.jlabel_messages = new JLabel();
//			this.jlabel_messages.setBorder( BorderFactory.createLoweredBevelBorder());
			this.jlabel_messages.setText(" ");
			this.jlabel_messages.addMouseListener(new MouseAdapter()  {
	            public void mouseClicked(MouseEvent evt) {
	                int count = evt.getClickCount();
	                if (count == 2) {
	                    JOptionPane.showMessageDialog(contents,jlabel_messages.getText(),"Status",JOptionPane.INFORMATION_MESSAGE);
	                }
	            }
	        });
		}
		return this.jlabel_messages;
	}
	
	public void message() {
		this.getMessagesJLabel().setText(" ");
	}
	
	public void message(String msg) {
		this.getMessagesJLabel().setText(msg);
	}
	
	private FormSetButtonBar getButtonBar() throws ServiceException {
		if( this.buttonBar == null ) {
			initComponents();
		}
		return this.buttonBar;
	}
	
	private void initComponents() throws ServiceException {
		this.contents = new JPanel();
		this.contents.setLayout(new BorderLayout());
		
		this.form = this.manager.getDynFormManager().createJDynForm(definition);
		this.form.setUseScrollBars(this.useScrollBars);
		
		if(!actionsBuffer.isEmpty()){
			Iterator it = actionsBuffer.iterator();
			while(it.hasNext()){
				ActionStore actStore = (ActionStore) it.next();
				if(actStore.isSeparator()){
					form.addSeparatorToPopupMenu(
							actStore.getDataType());
				}else{
					form.addActionToPopupMenu(
							actStore.getDataType(), 
							actStore.getActionName(), 
							actStore.getAction());
				}
			}
		}
		if( this.useScrollBars ) {
			if(this.formHeight > -1 && this.formWidth > -1){
				this.setFormSize(this.formWidth, this.formHeight);
			}
		}
		this.form.setShowMessageStatus(false);
		this.form.setLayoutMode(this.layoutMode);
		this.form.addListener(this);

		this.buttonBar = new FormSetButtonBar();
		this.buttonBar.addListener(this);
		this.buttonBar.setActionActive(this.buttonBar.ActionDelete, hasActionDelete);
		this.buttonBar.setActionActive(this.buttonBar.ActionNew, hasActionNew);
		this.buttonBar.setActionActive(this.buttonBar.ActionSave, hasActionUpdate);
		this.buttonBar.setActionActive(this.buttonBar.ActionSearch, hasActionSearch);
		
		this.contents.add(this.buttonBar.asJComponent(), BorderLayout.NORTH);
		this.contents.add(this.form.asJComponent(), BorderLayout.CENTER);
		this.contents.add(this.getMessagesJLabel(), BorderLayout.SOUTH);
		if( this.values != null ) {
			doChangeValue();
		}
		this.form.setReadOnly(this.readOnly);
	}
	
	public int getLayoutMode() {
		return this.layoutMode;
	}
	
	public void setLayoutMode(int layoutMode) {
		if( layoutMode<0 || layoutMode>USE_SEPARATORS) {
			throw new IllegalArgumentException("layoutMode ("+layoutMode+") out of range. Valid values are 0 .. "+ USE_SEPARATORS+".");
		}
		this.layoutMode = layoutMode;
	}

	private void doChangeValue()  throws ServiceException  {
		this.current = 0;
		this.form.setValues((DynObject) this.values.get(this.current));

		this.getButtonBar().setNumrecords(this.values.size());
		this.getButtonBar().setCurrent(this.current);
	}
	
	public void setValues(List values) throws ServiceException {
		List x = new ArrayList();
		x.addAll(values);
		this.values = x;
		if( this.contents != null ) {
			doChangeValue();
			this.getButtonBar().setEnabled(FormSetButtonBar.ActionDelete, true);
			this.getButtonBar().setEnabled(FormSetButtonBar.ActionSave, true);
			this.getButtonBar().setEnabled(FormSetButtonBar.ActionNew, true);
		}
	}

	public void setValues(DynObjectSet values) throws ServiceException {
		List x = new ArrayList();
		DisposableIterator it;
		try {
			it = values.iterator();
		} catch (BaseException e) {
			logger.info("Uf! o se que hacer con este error, lo relanzo sin mas.",e);
			throw new RuntimeException(e);
		}
		while(it.hasNext()) {
			DynObject obj = (DynObject) it.next();
			if( obj instanceof Cloneable ) {
				try {
					obj = (DynObject) ((Cloneable)obj).clone();
				} catch (CloneNotSupportedException e) {
					// Do nothing
				}
			}
			x.add(obj);
		}
		this.values = x;
		if( this.contents != null ) {
			doChangeValue();
			this.getButtonBar().setEnabled(FormSetButtonBar.ActionDelete, values.isDeleteEnabled());
			this.getButtonBar().setEnabled(FormSetButtonBar.ActionSave, values.isUpdateEnabled());
			this.getButtonBar().setEnabled(FormSetButtonBar.ActionNew, values.isUpdateEnabled());
		}
	}

	public boolean isReadOnly() {
		return readOnly;
	}
	
	public void setReadOnly(boolean readOnly) {
		this.readOnly = readOnly;
		if( this.form != null ) { 
			this.form.setReadOnly(readOnly);
		}
	}

	public boolean doActionFirst() {
		if( autosave ) {
			if( !doActionSave() ) {
				return false;
			}
		}
		this.current = 0;
		this.form.setValues((DynObject) this.values.get(this.current));
		this.buttonBar.setCurrent(this.current);
		this.fireFormMovedToEvent();
		return true;
	}

	public boolean doActionPrevious() {
		if( autosave ) {
			if( !doActionSave() ) {
				return false;
			}
		}
		if( this.current > 0 ) {
			this.current--;
		}
		this.form.setValues((DynObject) this.values.get(this.current));
		this.buttonBar.setCurrent(this.current);
		this.fireFormMovedToEvent();
		return true;
	}

	public boolean doActionNext() {
		if( autosave ) {
			if( !doActionSave() ) {
				return false;
			}
		}
		if( this.current < this.values.size() ) {
			this.current++;
		}
		this.form.setValues((DynObject) this.values.get(this.current));
		this.buttonBar.setCurrent(this.current);
		this.fireFormMovedToEvent();
		return true;
	}

	public boolean doActionLast() {
		if( autosave ) {
			if( !doActionSave() ) {
				return false;
			}
		}
		this.current = this.values.size()-1;
		this.form.setValues((DynObject) this.values.get(this.current));
		this.buttonBar.setCurrent(this.current);
		this.fireFormMovedToEvent();
		return true;
	}

	public boolean doActionSave() {
		if( !this.form.isModified() ) {
			return true;
		}
		try {
			this.listeners.accept(new Visitor() {
				public void visit(Object listener) throws VisitCanceledException, BaseException {
					((JDynFormSetListener)listener).formBeforeSave();
				}
			});
		} catch (BaseException e) {
			return true;
		}
		
		List<String> fieldsName = new ArrayList<String>();
		if( !this.form.haveValidValues(fieldsName) ) {
			String errores = "";
			Iterator<String> it= fieldsName.iterator();
			while(it.hasNext()){
				String name = (String)it.next();
				errores = errores +"     - "+ name + "\n";
			}
			int r = confirmDialog(
					"There are incorrect data in the following fields:\n" +errores+"\nContinuing the incorrect values ​will not be saved. Do you want to continue?", "warning", 
					JOptionPane.WARNING_MESSAGE, 
					JOptionPane.YES_NO_OPTION);
			if(  r != JOptionPane.YES_OPTION ) {
				return false;
			}
		}
		if( this.current >=0 && this.current<this.values.size() ) {
			this.form.getValues((DynObject) this.values.get(this.current));
		} else {
			logger.warn("current out of range.");
		}
			
		
		try {
			this.listeners.accept(new Visitor() {
				public void visit(Object listener) throws VisitCanceledException, BaseException {
					((JDynFormSetListener)listener).formAfterSave();
				}
			});
		} catch (BaseException e) {
			return true;
		}
		return true;
	}

	public boolean doActionNew() {
		try {
			this.listeners.accept(new Visitor() {
				public void visit(Object listener) throws VisitCanceledException, BaseException {
					((JDynFormSetListener)listener).formBeforeNew();
				}
			});
		} catch (BaseException e) {
			return true;
		}
		// Do new Actions
		try {
			this.listeners.accept(new Visitor() {
				public void visit(Object listener) throws VisitCanceledException, BaseException {
					((JDynFormSetListener)listener).formAfterNew();
				}
			});
		} catch (BaseException e) {
			return true;
		}
		return true;
	}

	public boolean doActionDelete() {
		try {
			this.listeners.accept(new Visitor() {
				public void visit(Object listener) throws VisitCanceledException, BaseException {
					((JDynFormSetListener)listener).formBeforeDelete();
				}
			});
		} catch (BaseException e) {
			return true;
		}
		// Do new Actions
		try {
			this.listeners.accept(new Visitor() {
				public void visit(Object listener) throws VisitCanceledException, BaseException {
					((JDynFormSetListener)listener).formAfterDelete();
				}
			});
		} catch (BaseException e) {
			return true;
		}		return true;
	}

	public boolean doActionSearch() {
		try {
			this.listeners.accept(new Visitor() {
				public void visit(Object listener) throws VisitCanceledException, BaseException {
					((JDynFormSetListener)listener).formBeforeSearch();
				}
			});
		} catch (BaseException e) {
			return true;
		}
		// Do search
		try {
			this.listeners.accept(new Visitor() {
				public void visit(Object listener) throws VisitCanceledException, BaseException {
					((JDynFormSetListener)listener).formBeforeSearch();
				}
			});
		} catch (BaseException e) {
			return true;
		}
		return true;
	}

	public boolean doActionClose() {
		fireCloseEvent();
		return true;
	}
	
	public boolean isAutosave() {
		return this.autosave;
	}
	
	public void setAutosave(boolean autosave) {
		this.autosave = autosave;
	}
	
	public int confirmDialog(final String message, final String title, final int optionType,
			final int messageType) {
		return JOptionPane.showConfirmDialog(
				this.contents, message,title, optionType, messageType);
	}

	public boolean allowUpdate() {
		try {
			return this.getButtonBar().isActionActive(this.getButtonBar().ActionSave);
		} catch (ServiceException e) {
			logger.info("Button bar isn't initializated");
		}
		return false;
	}

	public boolean allowDelete() {
		try {
			return this.getButtonBar().isActionActive(this.getButtonBar().ActionDelete);
		} catch (ServiceException e) {
			logger.info("Button bar isn't initializated");
		}
		return false;
	}

	public boolean allowNew() {
		try {
			return this.getButtonBar().isActionActive(this.getButtonBar().ActionNew);
		} catch (ServiceException e) {
			logger.info("Button bar isn't initializated");
		}
		return false;
	}

	public boolean allowSearch() {
		try {
			return this.getButtonBar().isActionActive(this.getButtonBar().ActionSearch);
		} catch (ServiceException e) {
			logger.info("Button bar isn't initializated");
		}
		return false;
	}

	public void setAllowUpdate(boolean allowUpdate) {
		hasActionUpdate = allowUpdate;
//		try {
//			this.getButtonBar().setActionActive(this.getButtonBar().ActionSave, allowUpdate);
//		} catch (ServiceException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
	}

	public void setAllowDelete(boolean allowDelete) {
		hasActionDelete = allowDelete;
//		try {
//			this.getButtonBar().setActionActive(this.getButtonBar().ActionDelete, allowDelete);
//		} catch (ServiceException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
	}

	public void setAllowNew(boolean allowNew) {
		hasActionNew = allowNew;
	}

	public void setAllowSearch(boolean allowSearch) {
		hasActionSearch = allowSearch;
//		try {
//			this.getButtonBar().setActionActive(this.getButtonBar().ActionSearch, allowSearch);
//		} catch (ServiceException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
	}

	public void setFormSize(int width, int height) {
		this.formHeight = height;
		this.formWidth = width;
		if(this.form != null){
			this.form.setFormSize(width, height);
		}
	}

	public int getCurrentIndex() {
		return this.current;
	}

	public int countValues() {
		return this.values.size();
	}

	public void setCurrentIndex(int index) {
		if( index <0 || index > countValues() ) {
			throw new IllegalArgumentException("Index ("+index+") out of range [0.."+countValues()+"].");
		}
		this.current = index;
		this.form.setValues((DynObject) this.values.get(this.current));
		this.buttonBar.setCurrent(this.current);
		this.fireFormMovedToEvent();
	}

	public DynObject get(int position) {
		return (DynObject) this.values.get(position);
	}

	public void addActionToPopupMenu(DataType tipo, String name, Action action) {
		try {
			if(this.form==null){
				this.actionsBuffer.add(new ActionStore(tipo, name, action));
			}else{
				this.form.addActionToPopupMenu(tipo, name, action);
			}
		} catch( Exception ex) {
			String s = (tipo==null)? "(null)" : tipo.getName();
			logger.warn("Can't add popup menu '"+name+"' to the fields of type '"+s+"' of the form.", ex);
		}
	}

	public void addSeparatorToPopupMenu(DataType tipo) {
		try {
			if(this.form==null){
				this.actionsBuffer.add(new ActionStore(tipo));
			}else{
				this.form.addSeparatorToPopupMenu(tipo);
			}
		} catch( Exception ex) {
			String s = (tipo==null)? "(null)" : tipo.getName();
			logger.warn("Can't add separator to the popup menu to the fields of type '"+s+"' of the form.", ex);
		}

	}		
	
	public void setUseScrollBars(boolean usesScrolls) {
		this.useScrollBars  = usesScrolls;
	}

	public boolean getUseScrollBars() {
		return useScrollBars;
	}

    public Manager getManager() {
        return this.manager;
    }
        
}
