/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF 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.apache.wicket.examples.ajax.builtin;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxFallbackLink;
import org.apache.wicket.ajax.markup.html.form.AjaxButton;
import org.apache.wicket.ajax.markup.html.form.AjaxCheckBox;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.util.io.IClusterable;

/**
 * Ajax todo list without having to write any JavaScript yourself.
 * 
 * @author Martijn Dashorst
 */
public class TodoList extends BasePage
{
    /**
     * The todo object.
     */
    public static class TodoItem implements IClusterable
    {
        private static final long serialVersionUID = 1L;

        /** Is the item done? */
        private boolean checked;

        /** Description of the item. */
        private String text;

        /** Constructor. */
        public TodoItem()
        {
        }

        /**
         * Copy constructor.
         * 
         * @param item
         *            the item to copy the values from.
         */
        public TodoItem(TodoItem item)
        {
            text = item.text;
        }

        /**
         * @return Returns the checked property.
         */
        public boolean isChecked()
        {
            return checked;
        }

        /**
         * Sets the checked property.
         * 
         * @param checked
         *            The checked property to set.
         */
        public void setChecked(boolean checked)
        {
            this.checked = checked;
        }

        /**
         * Gets the description of the item.
         * 
         * @return Returns the text.
         */
        public String getText()
        {
            return text;
        }

        /**
         * Sets the description of the item.
         * 
         * @param text
         *            The text to set.
         */
        public void setText(String text)
        {
            this.text = text;
        }
    }

    /**
     * Container for displaying the todo items in a list.
     */
    public class TodoItemsContainer extends WebMarkupContainer
    {
        /**
         * Constructor.
         * 
         * @param id
         *            the component identifier.
         */
        public TodoItemsContainer(String id)
        {
            super(id);

            // let wicket generate a markup-id so the contents can be
            // updated through an AJAX call.
            setOutputMarkupId(true);

            // add the listview to the container
            add(new ListView<TodoItem>("item", items)
            {
                @Override
                protected void populateItem(ListItem<TodoItem> item)
                {
                    // add an AJAX checkbox to the item
                    item.add(new AjaxCheckBox("check", new PropertyModel<>(
                        item.getModel(), "checked"))
                    {
                        @Override
                        protected void onUpdate(AjaxRequestTarget target)
                        {
                            // no need to do anything, the model is updated by
                            // itself, and we don't have to re-render a
                            // component (the client already has the correct
                            // state).
                        }
                    });
                    // display the text of the todo item
                    item.add(new Label("text", new PropertyModel<String>(item.getModel(), "text")));
                }
            });
        }
    }

    /**
     * Container for showing either the add link, or the addition form.
     */
    private class AddItemsContainer extends WebMarkupContainer
    {
        /** Visibility toggle so that either the link or the form is visible. */
        private boolean linkVisible = true;

        /** Link for displaying the AddTodo form. */
        private final class AddTodoLink extends AjaxFallbackLink<Void>
        {
            /** Constructor. */
            private AddTodoLink(String id)
            {
                super(id);
            }

            /**
             * onclick handler.
             * 
             * @param targetOptional
             *            the request target.
             */
            @Override
            public void onClick(Optional<AjaxRequestTarget> targetOptional)
            {
                onShowForm(targetOptional);
            }

            /**
             * Toggles the visibility with the add form.
             * 
             * @return <code>true</code> when the add links is visible and the form isn't.
             */
            @Override
            public boolean isVisible()
            {
                return linkVisible;
            }
        }

        /**
         * Link for removing all completed todos from the list, this link follows the same
         * visibility rules as the add link.
         */
        private final class RemoveCompletedTodosLink extends AjaxFallbackLink<Void>
        {
            /**
             * Constructor.
             * 
             * @param id
             *            component id
             */
            public RemoveCompletedTodosLink(String id)
            {
                super(id);
            }

            @Override
            public void onClick(Optional<AjaxRequestTarget> targetOptional)
            {
                onRemoveCompletedTodos(targetOptional);
            }

            /**
             * Toggles the visibility with the add form.
             * 
             * @return <code>true</code> when the add links is visible and the form isn't.
             */
            @Override
            public boolean isVisible()
            {
                return linkVisible;
            }
        }

        /**
         * Displays a form which offers an edit field and two buttons: one for adding the todo item,
         * and one for canceling the addition. The visibility of this component is mutual exclusive
         * with the visibility of the add-link.
         */
        private final class AddTodoForm extends Form<TodoItem>
        {
            /**
             * Constructor.
             * 
             * @param id
             *            the component id.
             */
            public AddTodoForm(String id)
            {
                super(id, new CompoundPropertyModel<>(new TodoItem()));
                setOutputMarkupId(true);
                add(new TextField<>("text"));
                add(new AjaxButton("add", this)
                {
                    @Override
                    protected void onSubmit(AjaxRequestTarget target)
                    {
                        // retrieve the todo item
                        TodoItem item = (TodoItem)getParent().getDefaultModelObject();

                        // add the item
                        onAdd(item, target);
                    }

                    @Override
                    protected void onError(AjaxRequestTarget target)
                    {
                    }
                });

                add(new AjaxButton("cancel", this)
                {
                    @Override
                    public void onSubmit(AjaxRequestTarget target)
                    {
                        onCancelTodo(target);
                    }

                    @Override
                    protected void onError(AjaxRequestTarget target)
                    {
                    }
                });
            }

            /**
             * Toggles the visibility with the add link. When the link is visible, the form isn't.
             * 
             * @return true when the form is visible and the link isn't.
             */
            @Override
            public boolean isVisible()
            {
                return !linkVisible;
            }
        }

        /**
         * Constructor.
         * 
         * @param id
         *            the component id.
         */
        public AddItemsContainer(String id)
        {
            super(id);
            // let wicket generate a markup-id so the contents can be
            // updated through an AJAX call.
            setOutputMarkupId(true);
            add(new AddTodoLink("link"));
            add(new RemoveCompletedTodosLink("remove"));
            add(new AddTodoForm("form"));
        }

        /**
         * Called then the add link was clicked, shows the form, and hides the link.
         * 
         * @param targetOptional
         *            the request target.
         */
        void onShowForm(Optional<AjaxRequestTarget> targetOptional)
        {
            // toggle the visibility
            linkVisible = false;

            // redraw the add container.
            targetOptional.ifPresent(target -> target.add(this));
        }

        void onRemoveCompletedTodos(Optional<AjaxRequestTarget> targetOptional)
        {
            List<TodoItem> ready = new ArrayList<>();
            for (TodoItem todo : items)
            {
                if (todo.isChecked())
                {
                    ready.add(todo);
                }
            }
            items.removeAll(ready);

            targetOptional.ifPresent(target -> {
                // repaint our panel
                target.add(this);

                // repaint the listview as there was a new item added.
                target.add(showItems);
            });
        }

        /**
         * Called when the form is submitted through the add button, stores the todo item, hides the
         * form, displays the add link and updates the listview.
         * 
         * @param target
         *            the request target
         */
        void onAdd(TodoItem item, AjaxRequestTarget target)
        {
            // add the item
            items.add(new TodoItem(item));

            // reset the model
            item.setChecked(false);
            item.setText("");

            // toggle the visibility
            linkVisible = true;

            // repaint our panel
            target.add(this);

            // repaint the listview as there was a new item added.
            target.add(showItems);
        }

        /**
         * Called when adding a new todo item was canceled. Hides the add form and displays the add
         * link.
         * 
         * @param target
         *            the request target.
         */
        void onCancelTodo(AjaxRequestTarget target)
        {
            // toggle the visibility
            linkVisible = true;

            // repaint the panel.
            target.add(this);
        }
    }

    /**
     * Container for redrawing the todo items list with an AJAX call.
     */
    private final WebMarkupContainer showItems;

    /**
     * The list of todo items.
     */
    final List<TodoItem> items = new ArrayList<>();

    /**
     * Constructor.
     */
    public TodoList()
    {
        // add the listview container for the todo items.
        showItems = new TodoItemsContainer("showItems");
        add(showItems);

        add(new AjaxFallbackLink<Void>("ajaxback")
        {
            @Override
            public void onClick(Optional<AjaxRequestTarget> targetOptional)
            {
                setResponsePage(getPage());
            }
        });
        // add the add container for the todo items.
        add(new AddItemsContainer("addItems"));
    }
}