/*
 * Copyright 2009 Google Inc.
 * 
 * Licensed 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 com.google.appengine.demos.taskengine.client;

import com.google.appengine.demos.taskengine.client.ControlBar.Controls;
import com.google.appengine.demos.taskengine.client.DomUtils.EventRemover;
import com.google.appengine.demos.taskengine.client.Tasks.Controller;
import com.google.appengine.demos.taskengine.shared.Label;
import com.google.appengine.demos.taskengine.shared.Task;
import com.google.gwt.dom.client.AnchorElement;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.EventListener;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * The main UI for our Application. This is the list of tasks.
 */
public class TaskList extends Page {

  /**
   * Styles for this Widget. CssResource styles are compiled, minified and
   * injected into the compiled output for this application. Fewer round trips
   * since everything is included in the JavaScript :)!
   */
  public interface Css extends CssResource {
    String checkBoxContainer();

    String checked();

    String garbage();

    String plus();

    String taskRow();

    String taskRowPersisted();

    String title();

    String unChecked();

    String user();
  }

  /**
   * Resources for this Widget.
   * 
   */
  public interface Resources extends ControlBar.Resources {
    @Source("resources/checkBox.png")
    ImageResource checkBox();

    @Source("resources/check.png")
    ImageResource checkMark();

    @Source("resources/garbage.png")
    ImageResource garbage();

    @Source("resources/plus.png")
    ImageResource plus();

    @Source("resources/TaskList.css")
    TaskList.Css taskListCss();
  }

  /**
   * This class wraps TaskData and its associated DOM elements. This class
   * controls the rendering of Task Data to the UI.
   */
  public class TaskRow extends Widget {
    private final DivElement checkMark;
    private final Task data;
    private List<EventRemover> removers = new ArrayList<EventRemover>();

    private final DivElement titleElem;

    public TaskRow(Element parentElem, Task data) {
      super(parentElem);
      this.data = data;
      Element myElem = getElement();
      TaskList.Css css = resources.taskListCss();
      myElem.setClassName(css.taskRow());
      titleElem = Document.get().createDivElement();
      titleElem.setClassName(css.title());
      DivElement rightMask = Document.get().createDivElement();
      rightMask.setClassName(css.checkBoxContainer());
      checkMark = Document.get().createDivElement();
      rightMask.appendChild(checkMark);

      myElem.appendChild(titleElem);
      myElem.appendChild(rightMask);

      renderTask();

      hookEventListeners();
    }

    public Task getTaskData() {
      return data;
    }

    public void removeFromList() {
      // unhook event handlers
      for (int i = 0, n = removers.size(); i < n; i++) {
        removers.get(i).remove();
      }
      removers.clear();

      taskRowMap.remove(data.getId());
      getTaskListContainerElement(data.getLabelPriority()).removeChild(
          getElement());
    }

    public void renderTask() {
      titleElem.getStyle().setProperty("borderColor",
          Label.chooseColor(data.getLabelPriority()));
      titleElem.setInnerText(data.getTitle());
      if (data.isFinished()) {
        checkMark.setClassName(resources.taskListCss().checked());
      } else {
        checkMark.setClassName(resources.taskListCss().unChecked());
      }
    }

    public void setRowAsNotPersisted() {
      getElement().setClassName(resources.taskListCss().taskRow());
    }

    public void setRowAsPersisted(String id) {
      getElement().setClassName(
          resources.taskListCss().taskRow() + " "
              + resources.taskListCss().taskRowPersisted());
      taskRowMap.remove(data.getId());
      data.setId(id);
      taskRowMap.put(id, this);
    }

    private void hookEventListeners() {
      // This is what happens when we click on the task label
      DomUtils.addEventListener("click", titleElem, new EventListener() {

        public void onBrowserEvent(Event event) {
          controller.loadTask(data);
          controller.goToTaskDetails();
        }

      });

      // This is what happens when we click the checkbox
      DomUtils.addEventListener("click", checkMark, new EventListener() {

        public void onBrowserEvent(Event event) {
          if (data.isFinished()) {
            data.setFinished(false);
            completedTasks.remove(TaskRow.this);
          } else {
            data.setFinished(true);
            completedTasks.add(TaskRow.this);
          }
          renderTask();
          controller.persistTask(TaskRow.this);
          event.stopPropagation();
        }

      });
    }
  }

  /**
   * Creates the controls to be added to a TaskList.
   */
  public static Controls createControls(final Controller controller,
      TaskList.Resources resources) {
    TaskList.Css css = resources.taskListCss();

    // Setup the controls that will be added to the top bar the TaskList screen
    Controls controls = new Controls(resources);
    controls.addControl(css.plus(), new EventListener() {

      public void onBrowserEvent(Event event) {
        controller.loadTask(null);
        controller.goToTaskDetails();
      }

    });

    controls.addControl(css.garbage(), new EventListener() {

      public void onBrowserEvent(Event event) {
        controller.deleteCompletedTasks();
      }

    });

    return controls;
  }

  private final List<TaskRow> completedTasks = new ArrayList<TaskRow>();
  private final Controller controller;
  private boolean isLoggedIn = false;
  private final AnchorElement logoutLink;
  private final DivElement notUrgentImportantTasks;
  private final DivElement notUrgentNotImportantTasks;
  private final TaskList.Resources resources;
  private final HashMap<String, TaskRow> taskRowMap =
    new HashMap<String, TaskRow>();
  private final List<TaskRow> tasksPendingDeleteConfirmation =
    new ArrayList<TaskRow>();
  private final DivElement urgentImportantTasks;
  private final DivElement urgentNotImportantTasks;

  private final DivElement userEmail;

  protected TaskList(PageTransitionPanel parent, Controls controls,
      Controller controller, TaskList.Resources resources) {
    super(parent, controls, resources);
    this.controller = controller;
    this.resources = resources;

    urgentNotImportantTasks = Document.get().createDivElement();
    urgentImportantTasks = Document.get().createDivElement();
    notUrgentNotImportantTasks = Document.get().createDivElement();
    notUrgentImportantTasks = Document.get().createDivElement();
    Element container = getContentContainer();
    container.appendChild(urgentImportantTasks);
    container.appendChild(notUrgentImportantTasks);
    container.appendChild(urgentNotImportantTasks);
    container.appendChild(notUrgentNotImportantTasks);

    // Login stuff
    userEmail = Document.get().createDivElement();
    userEmail.getStyle().setProperty("display", "inline-block");
    userEmail.setInnerText("Loading...");
    logoutLink = Document.get().createAnchorElement();
    DivElement userInfoContainer = Document.get().createDivElement();
    userInfoContainer.appendChild(userEmail);
    userInfoContainer.appendChild(logoutLink);
    userInfoContainer.setClassName(resources.taskListCss().user());
    container.appendChild(userInfoContainer);
  }

  /**
   * Adds a task to our TaskList UI.
   * 
   * @param task the task to be added
   * @return returns the {@link TaskRow} that was attached to the UI
   */
  public TaskRow addTaskToUi(Task task) {
    Element container = getTaskListContainerElement(task.getLabelPriority());
    TaskRow row = new TaskRow(container, task);

    // For tasks that we have read from storage that are completed.
    if (row.getTaskData().isFinished()) {
      completedTasks.add(row);
    }
    return row;
  }

  /**
   * Removes the tasks pending deletion.
   */
  public void confirmDeletion() {
    for (int i = 0, n = tasksPendingDeleteConfirmation.size(); i < n; i++) {
      TaskRow row = tasksPendingDeleteConfirmation.get(i);
      row.removeFromList();
    }
    tasksPendingDeleteConfirmation.clear();
  }

  /**
   * Gets the tasks currently marked for completion and moves them over to
   * pending delete confirmation.
   * 
   * @return the tasks currently marked for completion
   */
  public String[] getCompletedTaskIdsAndMoveToPending() {
    String[] tasks = new String[completedTasks.size()];
    int i = 0;
    while (!completedTasks.isEmpty()) {
      TaskRow row = completedTasks.remove(0);
      row.setRowAsNotPersisted();
      tasks[i] = row.getTaskData().getId();
      tasksPendingDeleteConfirmation.add(row);
      i++;
    }
    return tasks;
  }

  public boolean isLoggedIn() {
    return isLoggedIn;
  }

  /**
   * Moves tasks that were pending deletion back over to pending. This is called
   * in the case where we fail to complete a delete due to
   * network/authentication issues.
   */
  public void movePendingTasksBackToCompleted() {
    int i = 0;
    while (!tasksPendingDeleteConfirmation.isEmpty()) {
      TaskRow row = tasksPendingDeleteConfirmation.remove(0);
      completedTasks.add(row);
      i++;
    }
  }

  /**
   * Method invoked if our RPC times out, returns an error, or if we are not
   * logged in.
   * 
   * @param loginUrl server generated sign in url, or <code>null</code> if we
   *          time out or have an RPC error.
   */
  public void notifyNotLoggedIn(String loginUrl) {
    getControlBar().disableControls();
    if (loginUrl != null) {
      // We are definitely not signed in
      userEmail.setInnerText("Please ");
      logoutLink.setHref(loginUrl);
      logoutLink.setInnerText(" signin.");
    } else {
      // We had an RPC error or a timeout
      userEmail.setInnerText("Network slow :(, please wait or");
      logoutLink.setHref("javascript:location.reload(true);");
      logoutLink.setInnerText(" Try Refresh");
    }
  }

  /**
   * Sets the logout link and displays the currently signed in user.
   * 
   * @param userEmailStr the email address for the currently signed in user.
   * @param logoutUrl the logout url.
   */
  public void setUserLoggedIn(String userEmailStr, String logoutUrl) {
    isLoggedIn = true;
    userEmail.setInnerText(userEmailStr + " | ");
    logoutLink.setHref(logoutUrl);
    logoutLink.setInnerText(" logout");

    // In case we get a login response after already disabling controls
    getControlBar().enableControls();
  }

  /**
   * Updates the UI to reflect that the underlying task has been updated.
   * 
   * @param task the {@link Task} that has been updated.
   * @param oldPriority the old priority level of the task.
   * @return returns the updated {@link TaskRow}.
   */
  public TaskRow updateTask(Task task, int oldPriority) {
    TaskRow row = taskRowMap.get(task.getId());
    assert (row != null);
    if (oldPriority != task.getLabelPriority()) {
      Element container = getTaskListContainerElement(oldPriority);
      container.removeChild(row.getElement());
      container = getTaskListContainerElement(task.getLabelPriority());
      container.appendChild(row.getElement());
    }
    row.setRowAsNotPersisted();
    row.renderTask();
    return row;
  }

  private Element getTaskListContainerElement(int priorityLevel) {
    switch (priorityLevel) {
      case Label.NOT_URGENT_IMPORTANT:
        return notUrgentImportantTasks;
      case Label.NOT_URGENT_NOT_IMPORTANT:
        return notUrgentNotImportantTasks;
      case Label.URGENT_IMPORTANT:
        return urgentImportantTasks;
      case Label.URGENT_NOT_IMPORTANT:
        return urgentNotImportantTasks;
      default:
        return urgentImportantTasks;
    }
  }
}
