/**
 * Copyright (C) 2016 Premium Minds.
 *
 * This file is part of pm-wicket-utils.
 *
 * pm-wicket-utils is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 *
 * pm-wicket-utils 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 Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with pm-wicket-utils. If not, see <http://www.gnu.org/licenses/>.
 */
package com.premiumminds.webapp.wicket;

import java.util.List;

import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.request.resource.JavaScriptResourceReference;
import org.apache.wicket.request.resource.ResourceReference;

public abstract class InfiniteScrollListView<T> extends WebMarkupContainer {
	private static final long serialVersionUID = 3906374470444741117L;
	
	private static final int MAX_ELEMENTS = 100;
	private static final int SCROLL_SIZE = 20;
	private static final ResourceReference javascriptReference = new JavaScriptResourceReference(InfiniteScrollListView.class, "InfiniteScrollListView.js");
	
	private WebMarkupContainer listContainer;
	private WebMarkupContainer upLoading;
	private WebMarkupContainer downLoading;
	
	private ListView<T> listView;
	
	private AbstractDefaultAjaxBehavior upBehavior;
	private AbstractDefaultAjaxBehavior downBehavior;

	public InfiniteScrollListView(String id, PageableListModel<? extends List<? extends T>> model) {
		super(id, model);
	}
	
	@SuppressWarnings("serial")
	@Override
	protected void onInitialize() {
		super.onInitialize();
		
		setOutputMarkupPlaceholderTag(true);
		
		add(listContainer = new WebMarkupContainer("list"));
		listContainer.setOutputMarkupPlaceholderTag(true);
		
		listContainer.add(upLoading = new WebMarkupContainer("upLoading"));
		upLoading.setOutputMarkupPlaceholderTag(true);
		upLoading.setVisible(false);
		
		add(upBehavior = new AbstractDefaultAjaxBehavior() {
			private static final long serialVersionUID = -1895111598850513557L;

			@Override
			protected void respond(AjaxRequestTarget target) {
				target.appendJavaScript("InfiniteScroll.getFromContainer('"+InfiniteScrollListView.this.getMarkupId()+"').scrollDownTo('"+getItemMarkupId(InfiniteScrollListView.this.getModel().getObject().get(0))+"')");
				
				setStartIndex(InfiniteScrollListView.this.getModel().getStartIndex()-SCROLL_SIZE);
				
				refreshLoadings();

				target.add(listContainer);
			}
		}); 
		
		listView = new ListView<T>("list", new LoadableDetachableModel<List<T>>(){
				@Override
				protected List<T> load() {
					return getModel().getObject();
				}
			})
		{
			private static final long serialVersionUID = -1218380580005295126L;

			@Override
			protected void populateItem(ListItem<T> item) {
				InfiniteScrollListView.this.populateItem(item);
				item.setMarkupId(getItemMarkupId(item.getModelObject()));
			}
			
			@Override
			public void renderHead(IHeaderResponse response) {
				String containerId = InfiniteScrollListView.this.getMarkupId();
				
				response.render(JavaScriptHeaderItem.forReference(javascriptReference, "infinite-scroll-list-view-js"));

				response.render(OnDomReadyHeaderItem.forScript("InfiniteScroll.getFromContainer('"+containerId+"').changeUp("+Boolean.toString(isShowUpLoading())+")"));
				response.render(OnDomReadyHeaderItem.forScript("InfiniteScroll.getFromContainer('"+containerId+"').changeDown("+Boolean.toString(isShowDownLoading())+")"));
				
				super.renderHead(response);
			}
		};
		listContainer.add(listView);
		
		listContainer.add(downLoading = new WebMarkupContainer("downLoading"));
		downLoading.setOutputMarkupPlaceholderTag(true);
		downLoading.setVisible(getModel().getSize()>MAX_ELEMENTS);
		
		add(downBehavior = new AbstractDefaultAjaxBehavior() {
			private static final long serialVersionUID = -1895111598850513557L;

			@Override
			protected void respond(AjaxRequestTarget target) {
				target.appendJavaScript("InfiniteScroll.getFromContainer('"+InfiniteScrollListView.this.getMarkupId()+"')" +
						".scrollUpTo('"+getItemMarkupId(InfiniteScrollListView.this.getModel().getObject().get(
								Math.min(InfiniteScrollListView.this.getModel().getViewSize()-1, InfiniteScrollListView.this.getModel().getObject().size()-1)
						  ))+"')");
				setStartIndex(getModel().getStartIndex()+SCROLL_SIZE);

				refreshLoadings();
				
				target.add(listContainer);
			}
		});
		
		add(getScrollBehaviour());
	}
	
	protected abstract void populateItem(final ListItem<T> item);
	
	protected abstract String getItemMarkupId(final T item);
	
	private Behavior getScrollBehaviour(){
		return new AttributeModifier("scroll", Model.of(this.getMarkupId())){
			private static final long serialVersionUID = 3523727356782417598L;

			@Override
			public void renderHead(Component component, IHeaderResponse response) {
				super.renderHead(component, response);

				response.render(OnDomReadyHeaderItem.forScript("InfiniteScroll.getFromContainer('"+getMarkupId()+"').setUrls('"+upBehavior.getCallbackUrl()+"', '"+downBehavior.getCallbackUrl()+"')"));
			}
			
			@Override
			protected String newValue(String currentValue, String replacementValue) {
				return "InfiniteScroll.handleScroll('"+InfiniteScrollListView.this.getMarkupId()+"')"; 
			}
		};
	}
	
	@Override
	protected void onBeforeRender() {
		setStartIndex(0);

		refreshLoadings();
		
		super.onBeforeRender();
	}
	
	private boolean isShowUpLoading(){
		return getModel().getStartIndex()>0;
	}
	
	private boolean isShowDownLoading(){
		return getModel().getSize()-getModel().getStartIndex()>MAX_ELEMENTS;
	}
	
	private void setStartIndex(int index){
		getModel().setStartIndex(index, MAX_ELEMENTS);
	}
	
	@SuppressWarnings("unchecked")
	public PageableListModel<List<T>> getModel(){
		return (PageableListModel<List<T>>) getDefaultModel();
	}

	public void setModel(PageableListModel<? extends List<? extends T>> model){
		setDefaultModel(model);
	}
	
	private void refreshLoadings(){
		upLoading.setVisible(isShowUpLoading());
		downLoading.setVisible(isShowDownLoading());
	}
}