/* InsertHyperlinkWindowCtrl.java

{{IS_NOTE
	Purpose:
		
	Description:
		
	History:
		Sep 7, 2010 11:34:37 AM , Created by Sam
}}IS_NOTE

Copyright (C) 2009 Potix Corporation. All Rights Reserved.

*/
package org.zkoss.zssex.ui.dialog;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.zkoss.lang.Strings;
import org.zkoss.poi.ss.util.AreaReference;
import org.zkoss.poi.ss.formula.SheetNameFormatter;
import org.zkoss.util.resource.Labels;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.InputEvent;
import org.zkoss.zk.ui.event.SerializableEventListener;
import org.zkoss.zk.ui.select.annotation.Listen;
import org.zkoss.zk.ui.select.annotation.Wire;
import org.zkoss.zss.api.model.Book;
import org.zkoss.zss.api.model.Hyperlink;
import org.zkoss.zss.api.model.Hyperlink.HyperlinkType;
import org.zkoss.zss.api.model.Sheet;
import org.zkoss.zss.model.SName;
import org.zkoss.zss.ui.Spreadsheet;
import org.zkoss.zssex.ui.dialog.impl.DialogCallbackEvent;
import org.zkoss.zssex.ui.dialog.impl.RefocusDialogCtrlBase;
import org.zkoss.zul.Button;
import org.zkoss.zul.Combobox;
import org.zkoss.zul.Comboitem;
import org.zkoss.zul.ListModelList;
import org.zkoss.zul.Messagebox;
import org.zkoss.zul.Textbox;
import org.zkoss.zul.Window;

/**
 * @author Sam
 * @author dennis
 */
public class InsertHyperlinkCtrl extends RefocusDialogCtrlBase {
	private static final long serialVersionUID = -5670705789890002388L;

	private final static String URI = "~./zssex/dlg/insertHyperlink.zul";
	
	public final static String ARG_DISPLAY = "headerSize";
	public final static String ARG_LINKTYPE = "linktype";
	public final static String ARG_ADDRESS = "address";
	
	private final String mailPreAppend = "mailto:";
	
	@Wire
	private Button webBtn;
	@Wire
	private Button docBtn; //ZSS-1137
	@Wire
	private Button mailBtn;
	@Wire
	private Textbox displayBox;
	@Wire
	private Component webContent;
	@Wire
	private Component mailContent;
	@Wire
	private Component docContent; //ZSS-1137
	
//	private final static String WEBLINK_CONTENT_URI = "~./zssex/dlg/hyperlink/webLink.zul";
//	private final static String DOCLINK_CONTENT_URI = "~./zssex/dlg/hyperlink/docLink.zul";
//	private final static String MAILLINK_CONTENT_URI = "~./zssex/dlg/hyperlink/mailLink.zul";
	
	private String linkDisplay;
	private String webAddress;
	private String mailAddress;
	private String subject;
	private Hyperlink.HyperlinkType linkType;
	private boolean autoDisplay = true;
	
	//ZSS-1137
	private String docAddress;
	private Spreadsheet ss;
	private Book book;
	private DocItem docSelectedItem;
	private String cellReference = "A1";
	
	public static void show(EventListener<DialogCallbackEvent> callback,Hyperlink.HyperlinkType type, String address, String display, Spreadsheet ss) {
		Map arg = newArg(callback);
		arg.put(ARG_DISPLAY, display);
		arg.put(ARG_ADDRESS, address);
		arg.put(ARG_LINKTYPE, type);
		arg.put(ARG_SPREADSHEET, ss);
		
		Window comp = (Window)Executions.createComponents(URI, null, arg);
		comp.doModal();
		return;
	}
	
	public void doAfterCompose(Window comp) throws Exception {
		super.doAfterCompose(comp);
		
		Map arg = Executions.getCurrent().getArg();
		
		linkDisplay = (String)arg.get(ARG_DISPLAY);
		String linkAddress = (String)arg.get(ARG_ADDRESS);
		linkType = (Hyperlink.HyperlinkType)arg.get(ARG_LINKTYPE);
		ss = (Spreadsheet)arg.get(ARG_SPREADSHEET); //ZSS-1137
		book = ss.getBook();
		
		//ZSS-1137
		comp.setTitle(Labels.getLabel(
				Strings.isBlank(linkAddress) ? 
						"zssex.dlg.hyperlink.insert" : "zssex.dlg.hyperlink.edit"));
			
		if(!Strings.isBlank(linkDisplay)){
			displayBox.setValue(linkDisplay);
			autoDisplay = false;
		}
		
		//we only support mail and URL now
		if(linkType==HyperlinkType.EMAIL){
			String[] linkData = linkAddress.split("\\?subject=");
			mailAddress = linkData[0].substring(mailPreAppend.length()).trim();
			if (linkData.length > 1) {
				subject = linkData[1].trim();
			}
			setLinkType(Hyperlink.HyperlinkType.EMAIL);
		} else if (linkType == HyperlinkType.DOCUMENT) { //ZSS-1137
			docAddress = linkAddress;
			final Object[] objs = Spreadsheet.parseLinkAddress(book, linkAddress);
			final String sheetScope = (String) objs[1];
			final String ref = (String) objs[2];
			//ref could be a Name range
			final SName docSelectedName = (SName) objs[0];
			if (docSelectedName != null) {
				docSelectedItem = new DocItem(DocItemType.NAME, docAddress, docSelectedName.getRefersToFormula());
				cellReference = "A1";
			} else if (!Strings.isBlank(ref)){ //ref is a Range reference; ZSS-1211: ref could be blank
				docSelectedItem = new DocItem(DocItemType.SHEET, sheetScope, "");
				try {
					cellReference = new AreaReference(ref).formatAsString();
				} catch(IllegalArgumentException ex) {
					docSelectedItem = null;
					cellReference = "A1";
				}
			} else { //ZSS-1211: for blank ref
				docSelectedItem = null;
				cellReference = "";
			}
			setLinkType(Hyperlink.HyperlinkType.DOCUMENT);
		} else {
			webAddress = linkAddress;
			setLinkType(Hyperlink.HyperlinkType.URL);
		}
	}
	
	@Listen("onChange=#displayBox")
	public void onChange$displayBox() {
		autoDisplay = false;
	}

	@Listen("onClick=#webBtn")
	public void onClick$webBtn() {
		setLinkType(HyperlinkType.URL);
	}
	//ZSS-1137
	@Listen("onClick=#docBtn")
	public void onClick$docBtn() {
		setLinkType(HyperlinkType.DOCUMENT);
	}
	@Listen("onClick=#mailBtn")
	public void onClick$mailBtn() {
		setLinkType(HyperlinkType.EMAIL);
	}

	@Listen("onOK=window; onClick=#okBtn;")
	public void onClick$okBtn() {
		String addr = getAddress();
		if (Strings.isBlank(addr)) {
			Messagebox.show(Labels.getLabel("zssex.dlg.hyperlink.input_address"));
			return;
		}
		
		String display = displayBox.getValue();
		if(Strings.isBlank(display) || (linkType == HyperlinkType.DOCUMENT && autoDisplay)){ //ZSS-1137
			display = addr;
		}
		
		detach();
		postCallback(ON_OK, newMap(
			newEntry(ARG_DISPLAY, display),
			newEntry(ARG_ADDRESS, addr),
			newEntry(ARG_LINKTYPE, linkType)));
	}

	@Listen("onCancel=window; onClick=#cancelBtn")
	public void onCancel(){
		detach();
		postCallback(ON_CANCEL, null);
	}

	/**
	 * Returns link address
	 * @return
	 */
	private String getAddress() {
		if (linkType==HyperlinkType.EMAIL)
			return getMailAddress();
		else if (linkType == HyperlinkType.DOCUMENT) //ZSS-1137
			return getDocAddress();
		else
			return getWebAddress();
		
	}
	
	private void setLinkType(HyperlinkType type) {
		linkType = type;
		mailBtn.setDisabled(false);
		mailContent.setVisible(false);
		
		webBtn.setDisabled(false);
		webContent.setVisible(false);

		//ZSS-1137
		docBtn.setDisabled(false);
		docContent.setVisible(false);

		if(linkType==HyperlinkType.EMAIL){
			initMail();
		} else if (linkType == HyperlinkType.DOCUMENT){ //ZSS-1137
			initDoc();
		}else{
			initWeb();
		}
	}
	
	private void initMail() {
		mailContent.setVisible(true);
		mailBtn.setDisabled(true);
		final Textbox mailAddr = (Textbox)mailContent.getFellow("mailAddr");
		final Textbox mailSubject = (Textbox)mailContent.getFellow("mailSubject");
		
		if(autoDisplay){
			mailAddr.addEventListener(Events.ON_CHANGING, new SerializableEventListener() {
				private static final long serialVersionUID = 359865890436191154L;
				@Override
				public void onEvent(Event evt) throws Exception {
					if(!autoDisplay) return;
					String mail = mailSubject.getValue();
					String val = mailPreAppend + ((InputEvent)evt).getValue() + 
								(mail != null && mail != "" ? "?subject=" + mailSubject.getValue() : "");
					displayBox.setValue(val);
				}
			});
			
			mailSubject.addEventListener(Events.ON_CHANGING, new SerializableEventListener() {
				private static final long serialVersionUID = 578268550436191154L;
				@Override
				public void onEvent(Event evt) throws Exception {
					if(!autoDisplay) return;
					String mail = mailAddr.getValue();
					if (mail != null  && mail != "")
						displayBox.setValue(mailPreAppend + mailAddr.getValue() + "?subject=" + ((InputEvent)evt).getValue());
				}
			});
		}
		if(Strings.isBlank(mailAddr.getValue())){
			mailAddr.setValue(mailAddress);
		}
		if(Strings.isBlank(mailSubject.getValue())){
			mailSubject.setValue(subject);
		}
		mailAddr.focus();
	}

	
	private void initWeb() {
		webContent.setVisible(true);
		webBtn.setDisabled(true);
		final Combobox addr = (Combobox)webContent.getFellow("addrCombobox");
		if(autoDisplay){
			addr.addEventListener(Events.ON_CHANGING, new SerializableEventListener() {
				private static final long serialVersionUID = 337106550436789524L;
				@Override
				public void onEvent(Event evt) {
					if(!autoDisplay) return;
					displayBox.setValue(((InputEvent)evt).getValue());
				}
			});
			addr.addEventListener(Events.ON_BLUR, new SerializableEventListener() {
				private static final long serialVersionUID = 337106550436191154L;

				@Override
				public void onEvent(Event evt) {
					if(!autoDisplay) return;
					displayBox.setValue(addr.getValue());
				}
			});
			addr.addEventListener(Events.ON_SELECT, new SerializableEventListener() {
				private static final long serialVersionUID = -1070888525728585266L;

				@Override
				public void onEvent(Event evt) throws Exception {
					if(!autoDisplay) return;
					Comboitem seld = (Comboitem)addr.getSelectedItem();
					if (seld != null)
						displayBox.setValue(seld.getLabel());
				}
			});
		}
		if(Strings.isBlank(addr.getValue())){
			addr.setValue(webAddress);
		}
		addr.focus();
	}

	/**
	 * Returns the string of web page address, return null if component not found
	 * @return
	 */
	private String getWebAddress() {
		Combobox address = (Combobox)webContent.getFellow("addrCombobox");
		String val = address.getValue();
		return val == null ? "" : val;
	}

	//ZSS-1137
	private String getDocAddress() {
		if (docSelectedItem.type == DocItemType.NAME) {
			return docSelectedItem.name;
		} else {
			final Textbox cellRef = (Textbox)docContent.getFellow("cellRef");
			final String cellRefText = cellRef.getValue();
			if (Strings.isBlank(cellRefText))
				return null;
			final String refText0 = docSelectedItem.name + "!" + cellRefText; 
			final String refText = SheetNameFormatter.unformat(docSelectedItem.name) + "!" + cellRefText;
			try {
				final AreaReference areaRef = new AreaReference(refText);
				final String result = areaRef.formatAsString(); //normalize 
				return result.equalsIgnoreCase(refText0) ? result : null;
			} catch (IllegalArgumentException ex) {
				return null;
			}
		}
	}
	
	private String getMailAddress() {
		String mailAddr = ((Textbox)mailContent.getFellow("mailAddr")).getValue();
		if ("".equals(mailAddr))
			return "";
		
		String subject = ((Textbox)mailContent.getFellow("mailSubject")).getValue();
		return "mailto:" + mailAddr + (("".equals(subject)) ? "" : "?subject=" + subject);
	}

	//ZSS-1137
	private void initDoc() {
		docContent.setVisible(true);
		docBtn.setDisabled(true);
		final Combobox refSheet = (Combobox)docContent.getFellow("refSheet");
		final Textbox cellRef = (Textbox)docContent.getFellow("cellRef");
		
		buildDocumentListbox(refSheet, cellRef, book);
		cellRef.addEventListener(Events.ON_CHANGING, new EventListener() {
			
			@Override
			public void onEvent(Event evt) throws Exception {
				if(!autoDisplay) return;
				displayBox.setValue(docSelectedItem.name + "!" + ((InputEvent)evt).getValue());
			}
		});
		cellRef.focus();
	}
	
	//ZSS-1137: return the currently selected one if any
	private DocItem prepareSheetItems(List<DocItem> docItems) {
		final DocItemType type = DocItemType.SHEET; 
		
		int s = book.getNumberOfSheets();
		DocItem target = null;
		for(int i=0;i<s;i++){
			Sheet sheet = book.getSheetAt(i);
			if (sheet.isHidden()) continue;
			final DocItem x = new DocItem(type, SheetNameFormatter.format(sheet.getSheetName()), "");
			docItems.add(x);
			if (x.equals(docSelectedItem)) {
				target = x;
			}
		}
		return target;
	}
	
	//ZSS-1137: return the currently selected one if any
	private DocItem prepareDefinedNameItems(List<DocItem> docItems) {
		final DocItemType type = DocItemType.NAME;

		DocItem target = null;
		for (SName name : book.getInternalBook().getNames()) {
			if (name.getRefersToSheetName() != null) {
				final String namename = (name.getApplyToSheetName() != null ? 
						SheetNameFormatter.format(name.getApplyToSheetName())+"!" : "") + name.getName();
				final String extra = name.getRefersToFormula();
				final DocItem x = new DocItem(type, namename, extra);
				docItems.add(x);
				if (x.equals(docSelectedItem)) {
					target = x;
				}
			}
		}
		return target;
	}
	
	//ZSS-1137
	private void buildDocumentListbox(final Combobox refSheet, final Textbox cellRef, final Book book) {
		if (refSheet != null) {
			if (book == null) {
				return;
			}
			
			final List<DocItem> docItems = new ArrayList<DocItem>();
			DocItem selectedSheet = prepareSheetItems(docItems);
			DocItem selectedName = prepareDefinedNameItems(docItems);
			if (selectedSheet != null) { // select on sheet
				docSelectedItem = selectedSheet;
			} else if (selectedName != null) { // select on name
				docSelectedItem = selectedName;
				cellRef.setDisabled(true);
			} else { // neither; select on current sheet
				final String rawSheetName = ss.getSelectedSheetName();
				final String sheetName = SheetNameFormatter.format(rawSheetName);
				docSelectedItem = new DocItem(DocItemType.SHEET, sheetName, "");
//				cellReference = "A1"; //ZSS-1211: should not change it; could be blank in some excel file
			}
			final ListModelList<DocItem> model = new ListModelList<DocItem>(docItems);
			if (docSelectedItem != null) {
				model.addToSelection(docSelectedItem);
				refSheet.setValue(docSelectedItem.toString());
			}
			refSheet.setModel(model);
			cellRef.setValue(cellReference);
			
			if (autoDisplay) {
				displayBox.setValue(getDocAddress());
			}
			
			refSheet.addEventListener(Events.ON_SELECT, new EventListener() {
				@Override
				public void onEvent(Event evt) throws Exception {
					final Set<DocItem> selecteds = model.getSelection();
					if (selecteds.isEmpty()) return;
					docSelectedItem = selecteds.iterator().next();
					cellRef.setDisabled(docSelectedItem.type == DocItemType.NAME);
					if(!autoDisplay) return;
					displayBox.setValue(getDocAddress());
				}
			});
		}
	}
	
	//ZSS-1137
	enum DocItemType {
		SHEET, NAME;
	}
	//ZSS-1137
	class DocItem {
		private final DocItemType type;
		private final String name;
		private final String extra;
		
		DocItem(DocItemType t, String n, String ext) {
			type = t;
			name = n;
			extra = ext;
		}
		
		@Override
		public String toString() {
			return name + (type == DocItemType.NAME ? " ( " + extra + " )": "");
		}
		
		@Override
		public int hashCode() {
			return (type.hashCode() * 31 + name.hashCode()) * 31 + extra.hashCode(); 
		}
		
		@Override
		public boolean equals(Object other) {
			if (other == this) return true;
			if (!(other instanceof DocItem)) return false;
			final DocItem o = (DocItem) other;
			return o.type == this.type 
					&& o.name.equals(this.name) 
					&& o.extra.equals(this.extra);
		}
	}
}