/*
 * Copyright 2011 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.gwt.editor.client.impl;

import com.google.gwt.editor.client.EditorContext;
import com.google.gwt.editor.client.EditorError;
import com.google.gwt.editor.client.EditorVisitor;
import com.google.gwt.editor.client.HasEditorErrors;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * Collects and propagates EditorErrors through an Editor hierarchy.
 */
class ErrorCollector extends EditorVisitor {
  private final Stack<List<EditorError>> errorStack = new Stack<List<EditorError>>();
  private String lastAddedPath;
  private List<EditorError> lastAdded;

  public ErrorCollector(List<EditorError> errors) {
    assert errors != null;
    errorStack.push(errors);
  }

  @Override
  public <T> void endVisit(EditorContext<T> ctx) {
    AbstractEditorDelegate<?, ?> delegate = (AbstractEditorDelegate<?, ?>) ctx.getEditorDelegate();
    if (delegate == null) {
      return;
    }

    // Collect errors
    List<EditorError> errors = delegate.getErrors();
    lastAdded = new ArrayList<EditorError>(errors);
    lastAddedPath = ctx.getAbsolutePath();
    errorStack.peek().addAll(errors);
    errors.clear();

    // Filter collected errors through an error-aware editor
    HasEditorErrors<T> asErrors = ctx.asHasEditorErrors();
    if (asErrors != null) {
      // Get the enclosing error domain
      List<EditorError> tryConsume = errorStack.pop();
      int prefixLength = ctx.getAbsolutePath().length();
      for (EditorError error : tryConsume) {
        if ((prefixLength > 0) && (prefixLength < error.getAbsolutePath().length())) {
          // Remove trailing dot
          ((SimpleError) error).setPathPrefixLength(prefixLength + 1);
        } else {
          assert prefixLength == 0 || error.getAbsolutePath().equals(ctx.getAbsolutePath());
          ((SimpleError) error).setPathPrefixLength(prefixLength);
        }
      }
      /*
       * Pass collected errors to the editor. Must pass empty error collection
       * to the editor so that it can clear any existing errors when problems
       * are fixed.
       */
      asErrors.showErrors(tryConsume);

      // Short-circuit if there are no existing errors
      if (!tryConsume.isEmpty()) {
        List<EditorError> accumulator = errorStack.peek();
        for (EditorError e : tryConsume) {
          // Pass unconsumed error to enclosing domain
          if (!e.isConsumed()) {
            accumulator.add(e);
          }
        }
      }
    }
  }

  @Override
  public <Q> boolean visit(EditorContext<Q> ctx) {
    // Create a new "domain" for each error-aware editor
    HasEditorErrors<Q> asErrors = ctx.asHasEditorErrors();
    if (asErrors != null) {
      /*
       * Aliased editors (like ValueBoxEditorDecorator) will see the errors for
       * an editor at the same path that it occupies. If the editor that we're
       * currently looking at has the same path as the last thing we just saw,
       * recycle the previous errors.
       */
      if (ctx.getAbsolutePath().equals(lastAddedPath)) {
        errorStack.peek().removeAll(lastAdded);
        errorStack.push(lastAdded);
      } else {
        errorStack.push(new ArrayList<EditorError>());
      }
    }
    return true;
  }
}
