/*
 * Copyright 2010 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.web.bindery.requestfactory.gwt.client.impl;

import com.google.gwt.editor.client.Editor;
import com.google.gwt.editor.client.EditorVisitor;
import com.google.gwt.editor.client.impl.AbstractEditorDelegate;
import com.google.gwt.editor.client.impl.BaseEditorDriver;
import com.google.gwt.editor.client.impl.DelegateMap;
import com.google.gwt.editor.client.impl.DelegateMap.KeyMethod;
import com.google.gwt.editor.client.impl.SimpleViolation;
import com.google.web.bindery.autobean.shared.AutoBean;
import com.google.web.bindery.autobean.shared.AutoBeanUtils;
import com.google.web.bindery.event.shared.EventBus;
import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryEditorDriver;
import com.google.web.bindery.requestfactory.shared.EntityProxy;
import com.google.web.bindery.requestfactory.shared.RequestContext;
import com.google.web.bindery.requestfactory.shared.RequestFactory;
import com.google.web.bindery.requestfactory.shared.ValueProxy;
import com.google.web.bindery.requestfactory.shared.impl.Constants;

import java.util.Iterator;
import java.util.List;

/**
 * Contains utility methods for top-level driver implementations.
 * 
 * @param <R> the type being edited
 * @param <E> the type of Editor
 */
public abstract class AbstractRequestFactoryEditorDriver<R, E extends Editor<R>> extends
    BaseEditorDriver<R, E> implements RequestFactoryEditorDriver<R, E> {

  /**
   * Adapts a RequestFactory Violation object to the SimpleViolation interface.
   */
  @SuppressWarnings("deprecation")
  static class SimpleViolationAdapter extends SimpleViolation {
    private final com.google.web.bindery.requestfactory.shared.Violation v;

    /**
     * @param v
     */
    private SimpleViolationAdapter(com.google.web.bindery.requestfactory.shared.Violation v) {
      this.v = v;
    }

    @Override
    public Object getKey() {
      return v.getOriginalProxy();
    }

    @Override
    public String getMessage() {
      return v.getMessage();
    }

    @Override
    public String getPath() {
      return v.getPath();
    }

    @Override
    public Object getUserDataObject() {
      return v;
    }
  }
  /**
   * Provides a source of SimpleViolation objects based on RequestFactory's
   * simplified Violation interface.
   */
  @SuppressWarnings("deprecation")
  static class ViolationIterable implements Iterable<SimpleViolation> {

    private final Iterable<com.google.web.bindery.requestfactory.shared.Violation> violations;

    public ViolationIterable(
        Iterable<com.google.web.bindery.requestfactory.shared.Violation> violations) {
      this.violations = violations;
    }

    public Iterator<SimpleViolation> iterator() {
      final Iterator<com.google.web.bindery.requestfactory.shared.Violation> source =
          violations.iterator();
      return new Iterator<SimpleViolation>() {
        public boolean hasNext() {
          return source.hasNext();
        }

        public SimpleViolation next() {
          return new SimpleViolationAdapter(source.next());
        }

        public void remove() {
          source.remove();
        }
      };
    }
  }

  /**
   * Since the ValueProxy is being mutated in-place, we need a way to stabilize
   * its hashcode for future equality checks.
   */
  private static class ValueProxyHolder {
    private final ValueProxy proxy;

    public ValueProxyHolder(ValueProxy proxy) {
      this.proxy = proxy;
    }

    @Override
    public boolean equals(Object o) {
      return proxy.equals(((ValueProxyHolder) o).proxy);
    }

    @Override
    public int hashCode() {
      return proxy.getClass().hashCode();
    }
  }

  private static final DelegateMap.KeyMethod PROXY_ID_KEY = new DelegateMap.KeyMethod() {
    public Object key(Object object) {
      if (object instanceof EntityProxy) {
        return ((EntityProxy) object).stableId();
      } else if (object instanceof ValueProxy) {
        AutoBean<?> bean = AutoBeanUtils.getAutoBean(object);
        // Possibly replace an editable ValueProxy with its immutable base
        AutoBean<?> parent = bean.getTag(Constants.PARENT_OBJECT);
        if (parent != null) {
          object = parent.as();
        }
        return new ValueProxyHolder((ValueProxy) object);
      }
      return null;
    }
  };

  private EventBus eventBus;
  private List<String> paths;
  private RequestFactory factory;
  private RequestContext saveRequest;

  public void display(R object) {
    edit(object, null);
  }

  public void edit(R object, RequestContext saveRequest) {
    this.saveRequest = saveRequest;
    /*
     * Provide the delegate with the RequestContext so ensureMutable works as
     * expected Editor will be provided the delegate by the Initializer visitor.
     */
    ((RequestFactoryEditorDelegate<R, E>) getDelegate()).setRequestContext(saveRequest);
    doEdit(object);
  }

  public RequestContext flush() {
    checkSaveRequest();
    doFlush();
    return saveRequest;
  }

  public String[] getPaths() {
    return paths.toArray(new String[paths.size()]);
  }

  public void initialize(E editor) {
    doInitialize(null, null, editor);
  }

  public void initialize(EventBus eventBus, RequestFactory requestFactory, E editor) {
    assert eventBus != null : "eventBus must not be null";
    assert requestFactory != null : "requestFactory must not be null";
    doInitialize(eventBus, requestFactory, editor);
  }

  public void initialize(RequestFactory requestFactory, E editor) {
    initialize(requestFactory.getEventBus(), requestFactory, editor);
  }

  @SuppressWarnings("deprecation")
  public boolean setViolations(
      Iterable<com.google.web.bindery.requestfactory.shared.Violation> violations) {
    return doSetViolations(violations == null ? null : new ViolationIterable(violations));
  }

  protected void checkSaveRequest() {
    if (saveRequest == null) {
      throw new IllegalStateException("edit() was called with a null Request");
    }
  }

  @Override
  protected void configureDelegate(AbstractEditorDelegate<R, E> rootDelegate) {
    ((RequestFactoryEditorDelegate<R, E>) rootDelegate).initialize(eventBus, factory, "",
        getEditor());
  }

  @Override
  protected EditorVisitor createInitializerVisitor() {
    return new Initializer(saveRequest);
  }

  protected void doInitialize(EventBus eventBus, RequestFactory requestFactory, E editor) {
    this.eventBus = eventBus;
    this.factory = requestFactory;
    super.doInitialize(editor);
    PathCollector c = new PathCollector();
    accept(c);
    this.paths = c.getPaths();
  }

  @Override
  protected KeyMethod getViolationKeyMethod() {
    return PROXY_ID_KEY;
  }
}
