// Copyright (c) 2009 MatchCraft, Inc.  All rights reserved.
package test;

import java.util.*;

import com.google.gwt.core.client.Duration;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.user.client.Window;
import com.smartgwt.client.core.DataClass;
import com.smartgwt.client.data.AdvancedCriteria;
import com.smartgwt.client.data.Criteria;
import com.smartgwt.client.data.DataSource;
import com.smartgwt.client.data.fields.DataSourceTextField;
import com.smartgwt.client.types.OperatorId;
import com.smartgwt.client.widgets.IButton;
import com.smartgwt.client.widgets.events.ClickEvent;
import com.smartgwt.client.widgets.events.ClickHandler;
import com.smartgwt.client.widgets.layout.HLayout;
import com.smartgwt.client.widgets.layout.VLayout;

public class AdvancedCriteriaTest extends HLayout {
  private static final int NUM_RECORDS = 30000;

  private Criteria criteria;
  private AdvancedCriteria advancedCriteria;

  public AdvancedCriteriaTest() {
    setWidth100();
    setHeight100();
    setMembersMargin(10);
    setUpCriteria();
    testFilterUsingCriteria();
    testFilterUsingAdvancedCriteria();
    testFilterUsingAdvancedCriteriaSansOverhead();
  }

  private FilteredDataSource createDataSource() {
    FilteredDataSource ds = new FilteredDataSource();
    ds.setFields(new DataSourceTextField("name", "Name"),
                 new DataSourceTextField("value", "Value"));
    ds.setClientOnly(true);

    DataClass[] data = new DataClass[NUM_RECORDS];
    for (int i = 0; i < NUM_RECORDS; ++i) {
      DataClass record = new DataClass();
      record.setAttribute("name", "Name " + i);
      record.setAttribute("value", "Value" + i);
      data[i] = record;
    }
    ds.setTestData(data);

    return ds;
  }

  private void setUpCriteria() {
    this.criteria = new Criteria();
    criteria.setAttribute("name", "Name " + (NUM_RECORDS - 1));
    criteria.setAttribute("value", "Value" + (NUM_RECORDS - 1));

    this.advancedCriteria
      = new AdvancedCriteria(OperatorId.AND,
                             new AdvancedCriteria[] { new AdvancedCriteria("name", OperatorId.EQUALS, "Name " + (NUM_RECORDS - 1)),
                                                      new AdvancedCriteria("value", OperatorId.EQUALS, "Value" + (NUM_RECORDS - 1)) });
  }

  private void testFilterUsingCriteria() {
    IButton b = new IButton("Filter Using Criteria");
    b.setAutoFit(true);
    b.addClickHandler(new ClickHandler() {
      public void onClick(ClickEvent arg0) {
        FilteredDataSource ds = createDataSource();
        Duration d = new Duration();
        ds.filter(criteria.getJsObj());
        Window.alert("Filter Using Criteria: " + d.elapsedMillis() + "ms");
      }
    });
    addMember(b);
  }

  private void testFilterUsingAdvancedCriteria() {
    IButton b = new IButton("Filter Using Advanced Criteria");
    b.setAutoFit(true);
    b.addClickHandler(new ClickHandler() {
      public void onClick(ClickEvent arg0) {
        FilteredDataSource ds = createDataSource();
        Duration d = new Duration();
        ds.filter(advancedCriteria.getJsObj());
        Window.alert("Filter Using Advanced Criteria: " + d.elapsedMillis() + "ms");
      }
    });
    addMember(b);
  }

  private void testFilterUsingAdvancedCriteriaSansOverhead() {
    IButton b = new IButton("Filter Using Advanced Criteria Sans Overhead");
    b.setAutoFit(true);
    b.addClickHandler(new ClickHandler() {
      public void onClick(ClickEvent arg0) {
        FilteredDataSource ds = createDataSource();
        ds.removeOverhead();
        Duration d = new Duration();
        ds.filter(advancedCriteria.getJsObj());
        Window.alert("Filter Using Advanced Criteria Sans Overhead: " + d.elapsedMillis() + "ms");
      }
    });
    addMember(b);
  }

  // ------------------------------------------------------------

  private static class FilteredDataSource extends DataSource {
    public native void filter(JavaScriptObject criteria) /*-{
      var self = this.@com.smartgwt.client.core.BaseClass::getOrCreateJsObj()();
      self.applyFilter(self.testData, criteria, {});
    }-*/;

    public native void removeOverhead() /*-{
      var self = this.@com.smartgwt.client.core.BaseClass::getOrCreateJsObj()();
      var fastOperatorLookup = {};

      var origGetSearchOperator = self.getSearchOperator;
      self.getSearchOperator = function(operatorId) {
        // it's faster to use dictionary to look up operator by ID
        // instead of searching for it from isc.DataSource._operators array
        // O(1) instead of O(n)
        // return isc.DataSource._operators.find("ID", operatorId);

        var op = fastOperatorLookup[operatorId];
        if (!op) {
          op = origGetSearchOperator(operatorId);
          // caching the operator so we can look it up next time
          fastOperatorLookup[operatorId] = op;
        }

        return op;
      };

      self.evaluateCriterion = function(record, criterion) {
        if (criterion.requiresServer == true) return true;

        var op = self.getSearchOperator(criterion.operator);
        if (op == null) {
            $wnd.isc.logWarn("Attempted to use unknown operator " + criterion.operator);
            return false;
        }

        // remove overhead of looking up fieldOperators for validation
        // Perhaps, we can do the validation only once outside of evaluationCriterion?
        //
        // if (criterion.fieldName) {
        //   var validOps = this.getFieldOperators(criterion.fieldName);
        //   if (!validOps.contains(op.ID)) {
        //     this.logWarn("Operator " + op.ID + " is not valid for field " + criterion.fieldName +
        //                  ". Continuing anyway.");
        //   }
        // }

        return op.condition(criterion.value, record, criterion.fieldName, criterion, op, this);
      };
    }-*/;
  }
}
