/*
 * Licensed to The Apereo Foundation under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.
 *
 * The Apereo Foundation licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
*/
package org.unitime.timetable.gwt.client.rooms;

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

import org.unitime.timetable.gwt.client.Client;
import org.unitime.timetable.gwt.client.ToolBox;
import org.unitime.timetable.gwt.client.aria.AriaButton;
import org.unitime.timetable.gwt.client.events.AcademicSessionSelectionBox;
import org.unitime.timetable.gwt.client.page.UniTimeNotifications;
import org.unitime.timetable.gwt.client.page.UniTimePageHeader;
import org.unitime.timetable.gwt.client.page.UniTimePageLabel;
import org.unitime.timetable.gwt.client.widgets.LoadingWidget;
import org.unitime.timetable.gwt.client.widgets.SimpleForm;
import org.unitime.timetable.gwt.client.widgets.UniTimeHeaderPanel;
import org.unitime.timetable.gwt.client.widgets.FilterBox.Chip;
import org.unitime.timetable.gwt.client.widgets.UniTimeTable.MouseClickListener;
import org.unitime.timetable.gwt.client.widgets.UniTimeTable.TableEvent;
import org.unitime.timetable.gwt.client.widgets.UniTimeTableHeader;
import org.unitime.timetable.gwt.client.widgets.UniTimeTableHeader.AriaOperation;
import org.unitime.timetable.gwt.client.widgets.UniTimeTableHeader.HasColumnName;
import org.unitime.timetable.gwt.client.widgets.UniTimeTableHeader.Operation;
import org.unitime.timetable.gwt.command.client.GwtRpcResponse;
import org.unitime.timetable.gwt.command.client.GwtRpcService;
import org.unitime.timetable.gwt.command.client.GwtRpcServiceAsync;
import org.unitime.timetable.gwt.resources.GwtConstants;
import org.unitime.timetable.gwt.resources.GwtMessages;
import org.unitime.timetable.gwt.resources.GwtResources;
import org.unitime.timetable.gwt.shared.AcademicSessionProvider;
import org.unitime.timetable.gwt.shared.AcademicSessionProvider.AcademicSessionChangeEvent;
import org.unitime.timetable.gwt.shared.AcademicSessionProvider.AcademicSessionChangeHandler;
import org.unitime.timetable.gwt.shared.EventInterface.EncodeQueryRpcRequest;
import org.unitime.timetable.gwt.shared.EventInterface.EncodeQueryRpcResponse;
import org.unitime.timetable.gwt.shared.EventInterface.FilterRpcRequest;
import org.unitime.timetable.gwt.shared.EventInterface.FilterRpcResponse;
import org.unitime.timetable.gwt.shared.EventInterface.RoomFilterRpcRequest;
import org.unitime.timetable.gwt.shared.EventInterface.SaveFilterDefaultRpcRequest;
import org.unitime.timetable.gwt.shared.RoomInterface.RoomDetailInterface;
import org.unitime.timetable.gwt.shared.RoomInterface.RoomPropertiesInterface;
import org.unitime.timetable.gwt.shared.RoomInterface.RoomPropertiesRequest;
import org.unitime.timetable.gwt.shared.RoomInterface.RoomSharingDisplayMode;
import org.unitime.timetable.gwt.shared.RoomInterface.RoomsPageMode;

import com.google.gwt.aria.client.Roles;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.Cursor;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.http.client.URL;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.TakesValue;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Window.Location;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.MenuBar;
import com.google.gwt.user.client.ui.MenuItem;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * @author Tomas Muller
 */
public class RoomsPage extends Composite {
	protected static final GwtMessages MESSAGES = GWT.create(GwtMessages.class);
	protected static final GwtResources RESOURCES = GWT.create(GwtResources.class);
	protected static final GwtConstants CONSTANTS = GWT.create(GwtConstants.class);
	private static GwtRpcServiceAsync RPC = GWT.create(GwtRpcService.class);
	
	private AcademicSessionProvider iSession = null;
	private RoomFilterBox iFilter = null;
	private AriaButton iSearch = null;
	private AriaButton iNew = null;
	private AriaButton iMore = null;
	private AriaButton iEditRoomSharing = null;
	private RoomsTable iRoomsTable = null;
	private RoomDetail iRoomDetail = null;
	
	private VerticalPanel iRoomsPanel = null;
	private SimplePanel iRootPanel;
	
	private SimplePanel iPanel = null;
	private SimpleForm iFilterForm = null;
	private HorizontalPanel iFilterPanel = null;
	private RoomsPageMode iMode = RoomsPageMode.COURSES;
	private RoomPropertiesInterface iProperties = null;
	private RoomEdit iRoomEdit;
	private RoomDepartmentsEdit iRoomDepartmentsEdit;
	private UniTimeHeaderPanel iHeaderPanel = null;
	private HistoryToken iHistoryToken = null;
	
	public RoomsPage() {
		if (Location.getParameter("mode") != null)
			iMode = RoomsPageMode.valueOf(Location.getParameter("mode").toUpperCase());
		iHistoryToken = new HistoryToken(iMode);

		iPanel = new SimplePanel();
		
		iRoomsPanel = new VerticalPanel();
		iRoomsPanel.addStyleName("unitime-RoomsPanel");
		iRoomsPanel.setWidth("100%");
		
		ClickHandler clickSearch = new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				changeUrl();
				search(null);
			}
		};
		
		ClickHandler clickNew = new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				iRoomEdit.setRoom(null);
				iRoomEdit.show();
			}
		};
		
		ClickHandler clickMore = new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				if (iRoomsTable == null) return;
				
				final PopupPanel popup = new PopupPanel(true);
				MenuBar menu = new UniTimeTableHeader.MenuBarWithAccessKeys();
				
				if (iProperties != null && iProperties.isCanSaveFilterDefaults()) {
					MenuItem item = new MenuItem(MESSAGES.buttonClear(), true, new Command() {
						@Override
						public void execute() {
							popup.hide();
							iFilter.setValue(iHistoryToken.getDefaultParameter("q", ""), true);
							hideResults();
							changeUrl();
						}
					});
					Roles.getMenuitemRole().setAriaLabelProperty(item.getElement(), UniTimeHeaderPanel.stripAccessKey(MESSAGES.buttonClear()));
					menu.addItem(item);
					menu.addSeparator();
				}
				
				boolean first = true;
				List<Operation> showHide = iRoomsTable.getShowHideOperations();
				if (!showHide.isEmpty()) {
					MenuBar submenu = new MenuBar(true);
					for (final Operation op: showHide) {
						MenuItem item = new MenuItem(op.getName(), true, new Command() {
							@Override
							public void execute() {
								popup.hide();
								op.execute();
							}
						});
						if (op instanceof AriaOperation)
							Roles.getMenuitemRole().setAriaLabelProperty(item.getElement(), ((AriaOperation)op).getAriaLabel());
						else
							Roles.getMenuitemRole().setAriaLabelProperty(item.getElement(), UniTimeHeaderPanel.stripAccessKey(op.getName()));
						submenu.addItem(item);
					}
					MenuItem columns = new MenuItem(MESSAGES.opColumns(), submenu);
					columns.getElement().getStyle().setCursor(Cursor.POINTER);
					menu.addItem(columns);
					first = false;
				}
				
				List<Operation> sorts = iRoomsTable.getSortOperations();
				if (!sorts.isEmpty()) {
					MenuBar submenu = new MenuBar(true);
					for (final Operation op: sorts) {
						String name = op.getName();
						if (op instanceof HasColumnName)
							name = ((HasColumnName)op).getColumnName();
						MenuItem item = new MenuItem(name, true, new Command() {
							@Override
							public void execute() {
								popup.hide();
								op.execute();
							}
						});
						if (op instanceof AriaOperation)
							Roles.getMenuitemRole().setAriaLabelProperty(item.getElement(), ((AriaOperation)op).getAriaLabel());
						else
							Roles.getMenuitemRole().setAriaLabelProperty(item.getElement(), UniTimeHeaderPanel.stripAccessKey(op.getName()));
						submenu.addItem(item);
					}
					MenuItem columns = new MenuItem(MESSAGES.opSort(), submenu);
					columns.getElement().getStyle().setCursor(Cursor.POINTER);
					menu.addItem(columns);
					first = false;
				}
				
				if (iRoomsTable.hasDepartmentOperations()) {
					List<Operation> depts = iRoomsTable.getDepartmentOperations();
					if (!depts.isEmpty()) {
						MenuBar submenu = new MenuBar(true);
						for (final Operation op: depts) {
							MenuItem item = new MenuItem(op.getName(), true, new Command() {
								@Override
								public void execute() {
									popup.hide();
									op.execute();
								}
							});
							if (op instanceof AriaOperation)
								Roles.getMenuitemRole().setAriaLabelProperty(item.getElement(), ((AriaOperation)op).getAriaLabel());
							else
								Roles.getMenuitemRole().setAriaLabelProperty(item.getElement(), UniTimeHeaderPanel.stripAccessKey(op.getName()));
							submenu.addItem(item);
						}
						MenuItem columns = new MenuItem(MESSAGES.opDepartmentFormat(), submenu);
						columns.getElement().getStyle().setCursor(Cursor.POINTER);
						menu.addItem(columns);
						first = false;
					}
				}

				if (iRoomsTable.hasOrientationOperations()) {
					MenuBar orientation = null;
					if (!RoomCookie.getInstance().isGridAsText()) {
						if (orientation == null) orientation = new MenuBar(true);
						MenuItem item = new MenuItem(MESSAGES.opOrientationAsText(), true, new Command() {
							@Override
							public void execute() {
								popup.hide();
								RoomCookie.getInstance().setOrientation(true, RoomCookie.getInstance().areRoomsHorizontal());
								iRoomsTable.refreshTable();
							}
						});
						Roles.getMenuitemRole().setAriaLabelProperty(item.getElement(), MESSAGES.opOrientationAsText());
						orientation.addItem(item);
					}
					if (RoomCookie.getInstance().isGridAsText()) {
						if (orientation == null) orientation = new MenuBar(true);
						MenuItem item = new MenuItem(MESSAGES.opOrientationAsGrid(), true, new Command() {
							@Override
							public void execute() {
								popup.hide();
								RoomCookie.getInstance().setOrientation(false, RoomCookie.getInstance().areRoomsHorizontal());
								iRoomsTable.refreshTable();
							}
						});
						Roles.getMenuitemRole().setAriaLabelProperty(item.getElement(), MESSAGES.opOrientationAsGrid());
						orientation.addItem(item);
					}
					if (!RoomCookie.getInstance().isGridAsText() && RoomCookie.getInstance().areRoomsHorizontal()) {
						if (orientation == null) orientation = new MenuBar(true);
						MenuItem item = new MenuItem(MESSAGES.opOrientationVertical(), true, new Command() {
							@Override
							public void execute() {
								popup.hide();
								RoomCookie.getInstance().setOrientation(false, false);
								iRoomsTable.refreshTable();
							}
						});
						Roles.getMenuitemRole().setAriaLabelProperty(item.getElement(), MESSAGES.opOrientationVertical());
						orientation.addItem(item);
					}
					if (!RoomCookie.getInstance().isGridAsText() && !RoomCookie.getInstance().areRoomsHorizontal()) {
						if (orientation == null) orientation = new MenuBar(true);
						MenuItem item = new MenuItem(MESSAGES.opOrientationHorizontal(), true, new Command() {
							@Override
							public void execute() {
								popup.hide();
								RoomCookie.getInstance().setOrientation(false, true);
								iRoomsTable.refreshTable();
							}
						});
						Roles.getMenuitemRole().setAriaLabelProperty(item.getElement(), MESSAGES.opOrientationHorizontal());
						orientation.addItem(item);
					}
					if (iProperties != null && iProperties.hasModes() && !RoomCookie.getInstance().isGridAsText()) {
						boolean firstMode = true;
						for (final RoomSharingDisplayMode mode: iProperties.getModes()) {
							if (!mode.toHex().equals(RoomCookie.getInstance().getMode())) {
								if (orientation == null) orientation = new MenuBar(true);
								else if (firstMode) orientation.addSeparator();
								firstMode = false;
								MenuItem item = new MenuItem(mode.getName(), true, new Command() {
									@Override
									public void execute() {
										popup.hide();
										RoomCookie.getInstance().setMode(RoomCookie.getInstance().areRoomsHorizontal(), mode.toHex());
										iRoomsTable.refreshTable();
									}
								});
								Roles.getMenuitemRole().setAriaLabelProperty(item.getElement(), mode.getName());
								orientation.addItem(item);
							}
						}
					}
					if (orientation != null) {
						MenuItem columns = new MenuItem(MESSAGES.opOrientation(), orientation);
						columns.getElement().getStyle().setCursor(Cursor.POINTER);
						menu.addItem(columns);
						first = false;
					};
				}
				
				for (final Operation op: iRoomsTable.getOtherOperations()) {
					MenuItem item = new MenuItem(op.getName(), true, new Command() {
						@Override
						public void execute() {
							popup.hide();
							op.execute();
						}
					});
					if (op instanceof AriaOperation)
						Roles.getMenuitemRole().setAriaLabelProperty(item.getElement(), ((AriaOperation)op).getAriaLabel());
					else
						Roles.getMenuitemRole().setAriaLabelProperty(item.getElement(), UniTimeHeaderPanel.stripAccessKey(op.getName()));
					if (op.hasSeparator() && !first)
						menu.addSeparator();
					menu.addItem(item);
					first = false;
				}
				
				popup.add(menu);
				popup.showRelativeTo((UIObject)event.getSource());
				menu.focus();
			}
		};
		
		ClickHandler clickEditRoomSharing = new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				RoomFilterRpcRequest request = iFilter.getElementsRequest();
				request.getOptions().remove("department");
				LoadingWidget.execute(request, new AsyncCallback<FilterRpcResponse>() {
					@Override
					public void onFailure(Throwable caught) {
						iFilter.setErrorHint(MESSAGES.failedToLoadRooms(caught.getMessage()));
						UniTimeNotifications.error(MESSAGES.failedToLoadRooms(caught.getMessage()), caught);
					}
					@Override
					public void onSuccess(FilterRpcResponse result) {
						iFilter.clearHint();
						if (result == null || result.getResults() == null || result.getResults().isEmpty()) {
							iFilter.setErrorHint(MESSAGES.errorNoRoomsMatchingFilter());
							UniTimeNotifications.error(MESSAGES.errorNoRoomsMatchingFilter());
						} else {
							iRoomDepartmentsEdit.setRooms(result.getResults(), iRoomsTable.hasSortBy() ? Integer.valueOf(iRoomsTable.getSortBy()) : null);
							iRoomDepartmentsEdit.show();
						}
					}
				}, MESSAGES.waitLoadingRooms());
				
			}
		};
		
		if (iMode.hasSessionSelection()) {
			iFilterForm = new SimpleForm();
			
			iHeaderPanel = new UniTimeHeaderPanel(MESSAGES.sectFilter());
			iFilterForm.addHeaderRow(iHeaderPanel);
			
			iSession = new AcademicSessionSelectionBox(iHistoryToken.getParameter("term"), "Rooms") {
				@Override
				protected void onInitializationSuccess(List<AcademicSession> sessions) {
					iFilterForm.setVisible(sessions != null && !sessions.isEmpty());
					UniTimePageHeader.getInstance().getRight().setVisible(false);
					UniTimePageHeader.getInstance().getRight().setPreventDefault(true);
					setup(getAcademicSessionId(), CONSTANTS.searchWhenPageIsLoaded() && (iHistoryToken.hasParameter("id") || iHistoryToken.hasParameter("q")));
				}
				
				@Override
				protected void onInitializationFailure(Throwable caught) {
					UniTimeNotifications.error(MESSAGES.failedLoadSessions(caught.getMessage()), caught);
				}
			};
			iSession.addAcademicSessionChangeHandler(new AcademicSessionChangeHandler() {
				@Override
				public void onAcademicSessionChange(AcademicSessionChangeEvent event) {
					setup(event.getNewAcademicSessionId(), iRoomsTable != null && iRoomsTable.isVisible() && iRoomsTable.getRowCount() > 1);					
				}
			});;
			iFilterForm.addRow(MESSAGES.propAcademicSession(), (Widget)iSession);
			
			iFilter = new RoomFilterBox(iSession);
			iFilterForm.addRow(MESSAGES.propRoomFilter(), iFilter);
			
			iRoomsPanel.add(iFilterForm);
			
			iHeaderPanel.addButton("search", MESSAGES.buttonSearch(), clickSearch);
			iHeaderPanel.addButton("more", MESSAGES.buttonMoreOperations(), clickMore);
			iHeaderPanel.addButton("new", MESSAGES.buttonAddNewRoom(), clickNew);
			iHeaderPanel.addButton("sharing", MESSAGES.buttonEditRoomSharing(), clickEditRoomSharing);
			iHeaderPanel.setEnabled("more", false);
			iHeaderPanel.setEnabled("new", false);
			iHeaderPanel.setEnabled("sharing", false);
		} else {
			iFilterPanel = new HorizontalPanel();
			iFilterPanel.setSpacing(3);
			
			Label filterLabel = new Label(MESSAGES.propFilter());
			iFilterPanel.add(filterLabel);
			iFilterPanel.setCellVerticalAlignment(filterLabel, HasVerticalAlignment.ALIGN_MIDDLE);
			
			iSession = new Session();
			iFilter = new RoomFilterBox(iSession);
			iFilterPanel.add(iFilter);
			
			iSearch = new AriaButton(MESSAGES.buttonSearch());
			iSearch.addStyleName("unitime-NoPrint");
			iSearch.addClickHandler(clickSearch);
			iFilterPanel.add(iSearch);
			
			iMore = new AriaButton(MESSAGES.buttonMoreOperations());
			iMore.setEnabled(false);
			iMore.addStyleName("unitime-NoPrint");
			iMore.addClickHandler(clickMore);
			iFilterPanel.add(iMore);

			iNew = new AriaButton(MESSAGES.buttonAddNewRoom());
			iNew.setEnabled(false);
			iNew.addStyleName("unitime-NoPrint");
			iNew.addClickHandler(clickNew);
			iFilterPanel.add(iNew);
			
			iEditRoomSharing = new AriaButton(MESSAGES.buttonEditRoomSharing());
			iEditRoomSharing.setEnabled(false);
			iEditRoomSharing.setVisible(false);
			iEditRoomSharing.addStyleName("unitime-NoPrint");
			iEditRoomSharing.addClickHandler(clickEditRoomSharing);
			iFilterPanel.add(iEditRoomSharing);
					
			iRoomsPanel.add(iFilterPanel);
			iRoomsPanel.setCellHorizontalAlignment(iFilterPanel, HasHorizontalAlignment.ALIGN_CENTER);
			
			setup(null, CONSTANTS.searchWhenPageIsLoaded() && (iHistoryToken.hasParameter("id") || iHistoryToken.hasParameter("q")));
		}
		
		iRoomsTable = new RoomsTable(iMode);
		iRoomsTable.getElement().getStyle().setMarginTop(10, Unit.PX);
		ScrollPanel scroll = new ScrollPanel(iRoomsTable);
		scroll.addStyleName("scroll");
		iRoomsPanel.add(scroll);
		
		iRootPanel = new SimplePanel(iRoomsPanel);
		iPanel.setWidget(iRootPanel);
		
		initWidget(iPanel);
		
		History.addValueChangeHandler(new ValueChangeHandler<String>() {
			@Override
			public void onValueChange(ValueChangeEvent<String> event) {
				if (!iInitialized) return;
				if ("id=add".equals(event.getValue()))
					iHistoryToken.parse(event.getValue());
				else
					iHistoryToken.reset(event.getValue());
				updateFilter(iRoomsTable.isVisible());
			}
		});
		
		iRoomsTable.addOperation(new Operation() {
			@Override
			public void execute() {
				export("rooms.pdf");
			}
			
			@Override
			public boolean isApplicable() {
				return iRoomsTable.getRowCount() > 0 && (iProperties != null && iProperties.isCanExportPdf());
			}
			
			@Override
			public boolean hasSeparator() {
				return false;
			}
			
			@Override
			public String getName() {
				return MESSAGES.opExportPDF();
			}
		});
		
		iRoomsTable.addOperation(new Operation() {
			@Override
			public void execute() {
				export("rooms.csv");
			}
			
			@Override
			public boolean isApplicable() {
				return iRoomsTable.getRowCount() > 0 && (iProperties != null && iProperties.isCanExportCsv());
			}
			
			@Override
			public boolean hasSeparator() {
				return false;
			}
			
			@Override
			public String getName() {
				return MESSAGES.opExportCSV();
			}
		});
		
		iRoomsTable.addOperation(new Operation() {
			@Override
			public void execute() {
				export("rooms.xls");
			}
			
			@Override
			public boolean isApplicable() {
				return iRoomsTable.getRowCount() > 0 && (iProperties != null && iProperties.isCanExportCsv());
			}
			
			@Override
			public boolean hasSeparator() {
				return false;
			}
			
			@Override
			public String getName() {
				return MESSAGES.opExportXLS();
			}
		});
		
		iRoomsTable.addMouseClickListener(new MouseClickListener<RoomDetailInterface>() {
			@Override
			public void onMouseClick(final TableEvent<RoomDetailInterface> event) {
				if (event.getData() == null || !event.getData().isCanShowDetail()) return;
				iRoomDetail.setRoom(event.getData());
				iRoomDetail.show();
			}
		});		
		
		iRoomDetail = new RoomDetail(iMode) {
			@Override
			protected void onHide() {
				iRootPanel.setWidget(iRoomsPanel);
				UniTimePageLabel.getInstance().setPageName(MESSAGES.pageRooms());
				if (iRoomsTable != null && iRoomsTable.isVisible()) search(getRoom() == null ? null : getRoom().getUniqueId());
				changeUrl();
			}
			@Override
			protected void onShow() {
				iRootPanel.setWidget(iRoomDetail);
				changeUrl();
			}
			@Override
			protected void edit() {
				final Long roomId = (getRoom() == null ? null : getRoom().getUniqueId());
				if (roomId != null) {
					FilterRpcRequest rooms = iFilter.createRpcRequest();
					rooms.setCommand(FilterRpcRequest.Command.ENUMERATE);
					rooms.addOption("id", getRoom().getUniqueId().toString());
					rooms.setSessionId(iProperties.getAcademicSessionId());
					LoadingWidget.execute(rooms, new AsyncCallback<FilterRpcResponse>() {
						@Override
						public void onFailure(Throwable caught) {
							UniTimeNotifications.error(MESSAGES.failedToLoadRoomDetails(caught.getMessage()), caught);
						}
						
						@Override
						public void onSuccess(FilterRpcResponse result) {
							if (result == null || result.getResults() == null || result.getResults().isEmpty()) {
								UniTimeNotifications.error(MESSAGES.errorRoomDoesNotExist(roomId.toString()));
							} else {
								iRoomEdit.setRoom((RoomDetailInterface)result.getResults().get(0));
								iRoomEdit.show();
							}
						}
					}, MESSAGES.waitLoadingRoomDetails());
				} else {
					hide();
				}
			}
			@Override
			protected RoomDetailInterface getPrevious(Long roomId) {
				return iRoomsTable == null ? null : iRoomsTable.getPrevious(roomId);
			}
			@Override
			protected RoomDetailInterface getNext(Long roomId) {
				return iRoomsTable == null ? null : iRoomsTable.getNext(roomId);
			}
			@Override
			protected void previous(final RoomDetailInterface room) {
				setRoom(room);
				changeUrl();
			}
			@Override
			protected void next(final RoomDetailInterface room) {
				setRoom(room);
				changeUrl();
			}
			@Override
			public void hide() {
				super.hide();
			}
		};
		
		iRoomEdit = new RoomEdit(iMode) {
			@Override
			protected void onShow() {
				iRootPanel.setWidget(iRoomEdit);
				changeUrl();
			}
			@Override
			protected void onHide(RoomDetailInterface detail, boolean canShowDetail, String message) {
				if (!canShowDetail || (detail == null && getRoom().getUniqueId() == null)) {
					iRootPanel.setWidget(iRoomsPanel);
					UniTimePageLabel.getInstance().setPageName(MESSAGES.pageRooms());
					if (iRoomsTable.isVisible()) search(detail == null ? null : detail.getUniqueId());
				} else {
					if (detail != null)
						iRoomDetail.setRoom(detail);
					iRoomDetail.show(message);
				}
				changeUrl();
			}
		};
		
		iRoomDepartmentsEdit = new RoomDepartmentsEdit() {
			@Override
			protected void onShow() {
				iRootPanel.setWidget(iRoomDepartmentsEdit);
			}
			
			@Override
			protected void onHide(boolean refresh) {
				iRootPanel.setWidget(iRoomsPanel);
				UniTimePageLabel.getInstance().setPageName(MESSAGES.pageRooms());
				if (refresh && iRoomsTable.isVisible()) search(null);
			}
		};
		
		iFilter.addValueChangeHandler(new ValueChangeHandler<String>() {
			@Override
			public void onValueChange(ValueChangeEvent<String> event) {
				if (iEditRoomSharing != null) iEditRoomSharing.setEnabled(iRoomDepartmentsEdit.setDepartmentOrExamType(iFilter) && iRoomDepartmentsEdit.canEdit());
				if (iHeaderPanel != null) iHeaderPanel.setEnabled("sharing",
						iProperties != null && (iProperties.isCanEditRoomExams() || iProperties.isCanEditDepartments()) &&
						iRoomDepartmentsEdit.setDepartmentOrExamType(iFilter) && iRoomDepartmentsEdit.canEdit());
			}
		});
	}
	
	private boolean iInitialized = false;
	protected void setup(final Long sessionId, final boolean search) {
		LoadingWidget.getInstance().show(MESSAGES.waitLoadingPage());
		RPC.execute(new RoomPropertiesRequest(sessionId, iMode.name()), new AsyncCallback<RoomPropertiesInterface>() {
			@Override
			public void onFailure(Throwable caught) {
				LoadingWidget.getInstance().hide();
				iFilter.setErrorHint(MESSAGES.failedToInitialize(caught.getMessage()));
				UniTimeNotifications.error(MESSAGES.failedToInitialize(caught.getMessage()), caught);
				ToolBox.checkAccess(caught);
			}

			@Override
			public void onSuccess(RoomPropertiesInterface result) {
				LoadingWidget.getInstance().hide();
				
				iProperties = result;
				
				if (iProperties.isCanSaveFilterDefaults()) {
					iHistoryToken.setDefaultParameter("q", result.getFilterDefault("filter"));
					iFilter.setDefaultValueProvider(new TakesValue<String>() {
						@Override
						public void setValue(final String value) {
							RPC.execute(new SaveFilterDefaultRpcRequest(iMode.name() + ".filter", iFilter.getValue()),
									new AsyncCallback<GwtRpcResponse>() {
										@Override
										public void onFailure(Throwable caught) {
											UniTimeNotifications.error(MESSAGES.failedSaveAsDefault(caught.getMessage()), caught);
										}
										@Override
										public void onSuccess(GwtRpcResponse result) {
											iHistoryToken.setDefaultParameter("q", value);
										}
									});					
						}

						@Override
						public String getValue() {
							return iHistoryToken.getDefaultParameter("q", "");
						}
					});
				} else {
					if (result.hasFilterDefault("filter"))
						iHistoryToken.setDefaultParameter("q", result.getFilterDefault("filter"));
					iFilter.setDefaultValueProvider(null);
				}
				
				if (!RoomCookie.getInstance().hasOrientation())
					RoomCookie.getInstance().setOrientation(iProperties.isGridAsText(), iProperties.isHorizontal());
				
				iRoomDetail.setProperties(iProperties);
				iRoomsTable.setProperties(iProperties);
				iRoomEdit.setProperties(iProperties);
				iRoomDepartmentsEdit.setProperties(iProperties);

				if (iSession instanceof Session)
					((Session)iSession).fireChange();

				if (iNew != null) iNew.setEnabled(iProperties.isCanAddRoom() || iProperties.isCanAddNonUniversity());
				if (iHeaderPanel != null) iHeaderPanel.setEnabled("new", iProperties.isCanAddRoom() || iProperties.isCanAddNonUniversity());
				
				if (iEditRoomSharing != null) {
					iEditRoomSharing.setVisible(iProperties.isCanEditRoomExams() || iProperties.isCanEditDepartments());
					iEditRoomSharing.setEnabled(iRoomDepartmentsEdit.setDepartmentOrExamType(iFilter) && iRoomDepartmentsEdit.canEdit());
				}
				if (iHeaderPanel != null) {
					iHeaderPanel.setEnabled("sharing",
						iProperties != null && (iProperties.isCanEditRoomExams() || iProperties.isCanEditDepartments()) &&
						iRoomDepartmentsEdit.setDepartmentOrExamType(iFilter) && iRoomDepartmentsEdit.canEdit());
				}
				
				if (sessionId != null && iSession instanceof AcademicSessionSelectionBox) {
					iHistoryToken.setParameter("term", ((AcademicSessionSelectionBox)iSession).getAcademicSessionAbbreviation());
					iHistoryToken.mark();
				}
				updateFilter(search);
				iInitialized = true;
			}
		});
	}
	
	protected void updateFilter(boolean search) {
		iFilter.setValue(iHistoryToken.getParameter("q"), true);
		if (iSession instanceof AcademicSessionSelectionBox && iHistoryToken.isChanged("term", ((AcademicSessionSelectionBox)iSession).getAcademicSessionAbbreviation()) && iHistoryToken.getParameter("term") != null)
			((AcademicSessionSelectionBox)iSession).selectSession(iHistoryToken.getParameter("term"), null);
		if (iHistoryToken.hasParameter("id")) {
			if ("add".equals(iHistoryToken.getParameter("id"))) {
				iRoomEdit.setRoom(null);
				iRoomEdit.show();
			} else {
				final Long roomId = Long.valueOf(iHistoryToken.getParameter("id"));
				FilterRpcRequest rooms = iFilter.createRpcRequest();
				rooms.setCommand(FilterRpcRequest.Command.ENUMERATE);
				rooms.addOption("id", roomId.toString());
				iFilter.setValue(iHistoryToken.getParameter("q"), true);
				rooms.setSessionId(iProperties.getAcademicSessionId());
				LoadingWidget.execute(rooms, new AsyncCallback<FilterRpcResponse>() {
					@Override
					public void onFailure(Throwable caught) {
						UniTimeNotifications.error(MESSAGES.failedToLoadRoomDetails(caught.getMessage()), caught);
					}
					
					@Override
					public void onSuccess(FilterRpcResponse result) {
						if (result == null || result.getResults() == null || result.getResults().isEmpty()) {
							UniTimeNotifications.error(MESSAGES.errorRoomDoesNotExist(roomId.toString()));
						} else {
							iRoomDetail.setRoom((RoomDetailInterface)result.getResults().get(0));
							iRoomDetail.show();
						}
					}
				}, MESSAGES.waitLoadingRoomDetails());
			}
		} else if (iRoomDetail != null && iRoomDetail.equals(iRootPanel.getWidget())) {
			iRoomDetail.hide();
		} else if (iRoomEdit != null && iRoomEdit.equals(iRootPanel.getWidget())) {
			iRoomEdit.hide(iRoomEdit.getRoom(), false, null);
		} else if (iRoomDepartmentsEdit != null && iRoomDepartmentsEdit.equals(iRootPanel.getWidget())) {
			iRoomDepartmentsEdit.hide();
		} else if (search) {
			search(null);
		}
	}
	
	protected void export(String format) {
		RPC.execute(EncodeQueryRpcRequest.encode(query(format)), new AsyncCallback<EncodeQueryRpcResponse>() {
			@Override
			public void onFailure(Throwable caught) {
			}
			@Override
			public void onSuccess(EncodeQueryRpcResponse result) {
				ToolBox.open(GWT.getHostPageBaseURL() + "export?q=" + result.getQuery());
			}
		});
	}
	
	protected String query(String format) {
		RoomCookie cookie = RoomCookie.getInstance();
		int flags = (iRoomsTable == null ? cookie.getFlags(iMode) : cookie.getFlags(iMode) & iRoomsTable.getFlags());
		String query = "output=" + format + "&flags=" + flags + "&sort=" + cookie.getRoomsSortBy() +
				"&orientation=" + (cookie.isGridAsText() ? "text" : cookie.areRoomsHorizontal() ? "horizontal" : "vertical") + (cookie.hasMode() ? "&mode=" + cookie.getMode() : "") +
				"&dm=" + cookie.getDeptMode();
		if (iProperties.getAcademicSessionId() != null)
			query += "&sid=" + iProperties.getAcademicSessionId();
				
		FilterRpcRequest rooms = iFilter.getElementsRequest();
		if (rooms.hasOptions()) {
			for (Map.Entry<String, Set<String>> option: rooms.getOptions().entrySet()) {
				for (String value: option.getValue()) {
					query += "&r:" + option.getKey() + "=" + URL.encodeQueryString(value);
				}
			}
		}
		
		if (rooms.getText() != null && !rooms.getText().isEmpty())
			query += "&r:text=" + URL.encodeQueryString(rooms.getText());
				
		return query;
	}
	
	protected void hideResults() {
		if (iRoomsTable == null) return;
		if (iMore != null) iMore.setEnabled(false);
		if (iHeaderPanel != null) iHeaderPanel.setEnabled("more", false);
		iRoomsTable.clearTable(1);
	}
	
	protected void search(final Long roomId) {
		if (iRoomsTable == null) return;
		hideResults();
		LoadingWidget.execute(iFilter.getElementsRequest(), new AsyncCallback<FilterRpcResponse>() {
			@Override
			public void onFailure(Throwable caught) {
				iFilter.setErrorHint(MESSAGES.failedToLoadRooms(caught.getMessage()));
				UniTimeNotifications.error(MESSAGES.failedToLoadRooms(caught.getMessage()), caught);
			}
			@Override
			public void onSuccess(FilterRpcResponse result) {
				iFilter.clearHint();
				if (result == null || result.getResults() == null || result.getResults().isEmpty()) {
					iFilter.setErrorHint(MESSAGES.errorNoRoomsMatchingFilter());
				} else {
					Chip dept = iFilter.getChip("department");
					iRoomsTable.setDepartment(dept == null ? null : dept.getValue());
					for (FilterRpcResponse.Entity entity: result.getResults())
						iRoomsTable.addRoom((RoomDetailInterface)entity);
					iRoomsTable.sort();
				}
				if (iMore != null) iMore.setEnabled(iRoomsTable.getRowCount() > 1);
				if (iHeaderPanel != null) iHeaderPanel.setEnabled("more", iRoomsTable.getRowCount() > 1);
				iRoomsTable.scrollTo(roomId);
			}
		}, MESSAGES.waitLoadingRooms());
	}

	private class Session implements AcademicSessionProvider {
		private List<AcademicSessionChangeHandler> iHandlers = new ArrayList<AcademicSessionProvider.AcademicSessionChangeHandler>();
		
		private Session() {
		}
		
		@Override
		public void selectSession(Long sessionId, AsyncCallback<Boolean> callback) {
			callback.onSuccess(false);
		}
		
		@Override
		public String getAcademicSessionName() {
			return iProperties == null ? null : iProperties.getAcademicSessionName();
		}
		
		@Override
		public Long getAcademicSessionId() {
			return iProperties == null ? null : iProperties.getAcademicSessionId();
		}
		
		@Override
		public void addAcademicSessionChangeHandler(AcademicSessionChangeHandler handler) {
			iHandlers.add(handler);
		}
		
		@Override
		public AcademicSessionInfo getAcademicSessionInfo() {
			return null;
		}
		
		protected void fireChange() {
			AcademicSessionProvider.AcademicSessionChangeEvent event = new AcademicSessionProvider.AcademicSessionChangeEvent() {
				@Override
				public Long getNewAcademicSessionId() {
					return iProperties == null ? null : iProperties.getAcademicSessionId();
				}
				@Override
				public Long getOldAcademicSessionId() {
					return null;
				}
				@Override
				public boolean isChanged() {
					return true;
				}
			};
			for (AcademicSessionChangeHandler h: iHandlers)
				h.onAcademicSessionChange(event);
		}
	}
	
	protected void changeUrl() {
		iHistoryToken.reset(null);
		if (iSession instanceof AcademicSessionSelectionBox)
			iHistoryToken.setParameter("term", ((AcademicSessionSelectionBox)iSession).getAcademicSessionAbbreviation());
		iHistoryToken.setParameter("q", iFilter.getValue());
		if (iRoomDetail.equals(iRootPanel.getWidget())) {
			iHistoryToken.setParameter("id", iRoomDetail.getRoom().getUniqueId() == null ? "add" : iRoomDetail.getRoom().getUniqueId().toString());
		}
		if (iRoomEdit.equals(iRootPanel.getWidget())) {
			iHistoryToken.setParameter("id", iRoomEdit.getRoom().getUniqueId() == null ? "add" : iRoomEdit.getRoom().getUniqueId().toString());
		}
		iHistoryToken.mark();
		Client.fireGwtPageChanged(new Client.GwtPageChangeEvent());
	}
	
	public static class HistoryToken {
		private RoomsPageMode iMode = null;
		private Map<String, String> iParams = new HashMap<String, String>();
		private Map<String, String> iDefaults = new HashMap<String, String>();
		
		public HistoryToken(RoomsPageMode mode) {
			iMode = mode;
			
			// 1. take page type defaults --> DEFAULTS
			String query = iMode.getQuery();
			if (query != null) {
				for (String pair: query.split("\\&")) {
					int idx = pair.indexOf('=');
					if (idx >= 0) {
						String key = pair.substring(0, idx);
						if (Location.getParameter(key) == null)
							iDefaults.put(key, URL.decodeQueryString(pair.substring(idx + 1)));
					}
				}
			}
			
			// 2. take page parameters --> DEFAULTS (on top of the page type defaults)
			for (Map.Entry<String, List<String>> params: Window.Location.getParameterMap().entrySet())
				iDefaults.put(params.getKey(), params.getValue().get(0));
			
			// 3. take cookie --> PARAMS (override defaults)
			String cookie = RoomCookie.getInstance().getHash(iMode); 
			if (cookie != null) {
				for (String pair: cookie.split("\\&")) {
					int idx = pair.indexOf('=');
					if (idx >= 0) {
						String key = pair.substring(0, idx);
						if (Location.getParameter(key) == null)
							iParams.put(key, URL.decodeQueryString(pair.substring(idx + 1)));
					}
				}
			}
			
			// 4. take page token (hash) --> PARAMS (override cookie)
			parse(History.getToken());
		}
		
		public void reset(String token) {
			iParams.clear();
			parse(token);
		}
		
		public void parse(String token) {
			if (token != null && !token.isEmpty())
				for (String pair: token.split("\\&")) {
					int idx = pair.indexOf('=');
					if (idx >= 0)
						iParams.put(pair.substring(0, idx), URL.decodeQueryString(pair.substring(idx + 1)));
				}
		}
		
		public void setParameter(String key, String value) {
			if (value == null) {
				iParams.remove(key);
			} else {
				String defaultValue = iDefaults.get(key);
				if (value.equals(defaultValue))
					iParams.remove(key);
				else
					iParams.put(key, value);
			}
		}
		
		public void setParameter(String key, Long value) {
			setParameter(key, value == null ? null : value.toString());
		}
		
		@Override
		public String toString() {
			return toString(null);
		}
		
		public String toString(String skip) {
			String ret = "";
			for (String key: new TreeSet<String>(iParams.keySet())) {
				if (key.equals(skip)) continue;
				if (!ret.isEmpty()) ret += "&";
				ret += key + "=" + URL.encodeQueryString(iParams.get(key));
			}
			return ret;
		}
		
		public String getParameter(String key, String defaultValue) {
			String value = getParameter(key);
			return (value == null ? defaultValue : value);
		}
		
		public String getParameter(String key) {
			String value = iParams.get(key);
			return (value == null ? iDefaults.get(key) : value);
		}
		
		public String getDefaultParameter(String key, String defaultValue) {
			String value = iDefaults.get(key);
			return (value == null ? defaultValue : value);
		}
		
		public void setDefaultParameter(String key, String value) {
			if (value != null)
				iDefaults.put(key, value);
		}
		
		public boolean hasParameter(String key) {
			return getParameter(key) != null;
		}
		
		public boolean isChanged(String key, String value) {
			String v = getParameter(key);
			return (v == null ? value != null : !v.equals(value));
		}
		
		public boolean isChanged(String key, String defaultValue, String value) {
			String v = getParameter(key);
			return (v == null ? !defaultValue.equals(value) : !v.equals(value));
		}
		
		public void mark() {
			String token = toString();
			if (!History.getToken().equals(token))
				History.newItem(token, false);
			RoomCookie.getInstance().setHash(iMode, toString("id"));
		}
	}
}
