package various;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.servlet.http.HttpServletRequest;

/**
 * Kod klasy PhaseListenera realizujacego wzorzec POST-Redirect-GET
 * (patrz rozdzial 4.4). 
 *
 */
public class PostRedirectGetListener implements PhaseListener {

	private static final long serialVersionUID = 1L;
	private static final String PRG_DONE_ID = "PRG.Done";
	private static final String VIEW_ROOT_ID = "PRG.ViewRoot";
	private static final String UIINPUT_VALUES_ID = "PRG.UIInputValues";

	public PhaseId getPhaseId() {
		return PhaseId.RENDER_RESPONSE;
	}

	public void beforePhase(PhaseEvent event) {
		FacesContext facesContext = event.getFacesContext();
		ExternalContext externalContext = facesContext.getExternalContext();
		HttpServletRequest request = (HttpServletRequest) externalContext
				.getRequest();
		Map<String, Object> sessionMap = externalContext.getSessionMap();

		if ("POST".equals(request.getMethod())) {
			saveViewRoot(facesContext);
			saveUIInputValues(facesContext);
			redirect(facesContext);
			sessionMap.put(PRG_DONE_ID, true); // ustawia flagę na OK

		} else if (sessionMap.containsKey(PRG_DONE_ID)) {
			// Odtwarza zapisane wartości.
			restoreAll(facesContext);
			sessionMap.remove(PRG_DONE_ID);
		}
	}

	public void afterPhase(PhaseEvent event) {
	}

	/**
	 * Zapisuje UIViewRoot dla bieżącego widoku (niezbędne gdyż przy
	 * przekierowaniu żądania jest on tracony).
	 */
	private static void saveViewRoot(FacesContext facesContext) {
		UIViewRoot savedViewRoot = facesContext.getViewRoot();
		facesContext.getExternalContext().getSessionMap().put(VIEW_ROOT_ID,
				savedViewRoot);
	}

	
	/**
	 * Przygotowuje mapę sesji i wywołuje metodę zapisującą rekursywnie
	 * wszystkie wartości.
	 */
	private static void saveUIInputValues(FacesContext facesContext) {

		Map<String, Object> allUIInputValues = new HashMap<String, Object>();
		facesContext.getExternalContext().getSessionMap().put(
				UIINPUT_VALUES_ID, allUIInputValues);

		saveUIInputValues(facesContext, facesContext.getViewRoot()
				.getChildren(), allUIInputValues);
	}

	/**
	 * Rekursywnie zapisuje wszystkie komponenty.
	 */
	private static void saveUIInputValues(FacesContext facesContext,
			List<UIComponent> components, Map<String, Object> allUIInputValues) {
		for (UIComponent component : components) {
			if (component instanceof UIInput) {
				UIInput input = (UIInput) component;
				allUIInputValues.put(input.getClientId(facesContext), input
						.getValue());
			}
			saveUIInputValues(facesContext, component.getChildren(),
					allUIInputValues);
		}
	}

	/**
	 * Realizuje przekierowanie do adresu URL, zwróconego przez mechanizm
	 * nawigacji.
	 */
	private static void redirect(FacesContext facesContext) {

		String url = facesContext.getApplication().getViewHandler()
				.getActionURL(facesContext,
						facesContext.getViewRoot().getViewId());

		try {
			facesContext.getExternalContext().redirect(url);
		} catch (IOException e) {
			throw new FacesException("Cannot redirect to " + url
					+ " due to IO exception.", e);
		}
	}

	/**
	 * Odtwarza i usuwa z mapy sesji kolejno: obiekt UIViewRoot, kolejkę
	 * komunikatów i wartości komponentów.	 */
	@SuppressWarnings("unchecked")
	private static void restoreAll(FacesContext facesContext) {

		UIViewRoot savedViewRoot = (UIViewRoot) facesContext
				.getExternalContext().getSessionMap().remove(VIEW_ROOT_ID);

		facesContext.setViewRoot(savedViewRoot);

		Map<String, Object> allUIInputValues = (Map<String, Object>) facesContext
				.getExternalContext().getSessionMap().remove(UIINPUT_VALUES_ID);

		for (Entry<String, Object> entry : allUIInputValues.entrySet()) {
			UIInput input = (UIInput) facesContext.getViewRoot().findComponent(
					entry.getKey());
			input.setValue(entry.getValue());
		}
	}
}
