/*******************************************************************************
 * Copyright 2012 Relay Technology Management, Inc.
 * 
 * Licensed under the Relay Technology Management, Inc. License, Version 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may request a copy of the License at
 * 
 * support@relaytm.com
 * 
 * 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.relay.winter.server;

import java.util.ArrayList;
import java.util.Date;

import com.attivio.client.SearchClient;
import com.attivio.model.AttivioException;
import com.attivio.model.query.Context;
import com.attivio.model.query.Expression;
import com.attivio.model.query.MatchAll;
import com.attivio.model.query.Operator;
import com.attivio.model.query.Query;
import com.attivio.model.query.Range;
import com.attivio.model.query.Range.Bounds;
import com.attivio.model.query.Term;
import com.isomorphic.criteria.AdvancedCriteria;
import com.isomorphic.criteria.Criterion;
import com.isomorphic.criteria.criterion.AndCriterion;
import com.isomorphic.criteria.criterion.CustomCriterion;
import com.isomorphic.criteria.criterion.NotCriterion;
import com.isomorphic.criteria.criterion.OrCriterion;
import com.isomorphic.criteria.criterion.SimpleCriterion;
import com.isomorphic.datasource.BasicDataSource;

// WARNING:
// This is all built on isomorphic classes, not smart gwt
// there are subtle difference in these classes.
//    This basic class written by DB at attivio is thus, I think, very dangerous
// should really be built on smart gwt which also has a basic like datasource
// 
// for this reason when calling AIE perhaps we can contruct a advanced criteria that is also isomorphic based??
//

public abstract class AbstractAttivioDataSource extends BasicDataSource {

  protected static final int DEFAULT_MAX_ROWS = 10000000;
  protected static final String OPERATOR_EQUALS = "equals";
  protected static final String OPERATOR_CONTAINS = "contains";
  protected static final String OPERATOR_ICONTAINS = "iContains";
  protected static final String OPERATOR_GREATER_OR_EQUAL = "greaterOrEqual";
  protected static final String OPERATOR_LESS_OR_EQUAL = "lessOrEqual";

  private SearchClient client = null;

  private static final long serialVersionUID = 1L;

  private static String HOST = "localhost";
  private static int PORT = 17001;

  static {
    HOST = System.getProperty("attivio.search.host", HOST);
    PORT = Integer.parseInt(System.getProperty("attivio.search.port", PORT + ""));
  }

  public String getHost() {
    return HOST;
  }

  public int getPort() {
    return PORT;
  }

  protected SearchClient getSearchClient() throws AttivioException {
    if (client == null)
      client = new SearchClient(getHost(), getPort());
    return client;
  }

  protected Query getExpressionFromAdvancedCriteria(AdvancedCriteria advCrit) {
    Criterion c = advCrit.asCriterion();
        
    // System.out.println(" Criteria is an instance of " + c.getClass().toString());
    if (c instanceof SimpleCriterion) {
      return getExpressionFromSimpleCriterion((SimpleCriterion)c);
    }
    if (c instanceof AndCriterion) {
      return getExpressionFromAndCriterion((AndCriterion) c);
    } else if (c instanceof OrCriterion) {
      return getExpressionFromOrCriterion((OrCriterion) c);
    } else if (c instanceof NotCriterion) {
      throw new UnsupportedOperationException("NOT criterion not supported");
    }
    return null;
  }
  
  protected Query getExpressionFromSimpleCriterion(SimpleCriterion c) {
    Expression expr = new Expression(Operator.AND);
    Query q = processCriterion(c);
    if (q != null) 
      expr.add(q);
    return expr;
  }

  protected Query getExpressionFromAndCriterion(AndCriterion crit) {
    Expression expr = new Expression(Operator.AND);
    for (Criterion c : crit.getCriteria()) {
      Query q = processCriterion(c);
      if (q != null) 
        expr.add(q);
    }
    return expr;
  }

  protected Query getExpressionFromOrCriterion(OrCriterion crit) {
    Expression expr = new Expression(Operator.OR);
    for (Criterion c : crit.getCriteria()) {
      Query q = processCriterion(c);
      if (q != null)
        expr.add(q);
    }
    return expr;
  }

  protected Query processCriterion(Criterion c) {
    if (c instanceof SimpleCriterion) {
      SimpleCriterion d = (SimpleCriterion) c;
      String fieldName = d.getFieldName();
      String operator = d.getOperatorId();
      Object value = d.getValue();
      if (fieldName == null || fieldName.equals("__gwt_ObjectId"))
        return null;
      if (operator.equals(OPERATOR_EQUALS))
        return getEqualTerm(fieldName, value);
      else if (operator.equals(OPERATOR_CONTAINS))
        return getEqualTerm(fieldName, value);
//      else if (operator.equals(OPERATOR_ICONTAINS))
//        return getIContainsTerm(fieldName, value);
      else if (operator.equals(OPERATOR_GREATER_OR_EQUAL))
        return getGreaterOrEqual(fieldName, value);
      else if (operator.equals(OPERATOR_LESS_OR_EQUAL))
        return getLessOrEqual(fieldName, value);
      else        
        throw new UnsupportedOperationException("Unknown operator " + operator);
    } else if (c instanceof AndCriterion) {
      AndCriterion d = (AndCriterion) c;
      return getExpressionFromAndCriterion(d);
    } else if (c instanceof OrCriterion) {
      OrCriterion d = (OrCriterion) c;
      return getExpressionFromOrCriterion(d);      
    } else if (c instanceof CustomCriterion) {
      // do nothing until we figure out what this is supposed to represent
      return null;
    } else {
      throw new UnsupportedOperationException("Unsupported Criterion type " + c.getClass().getName());
    }
  }

  protected Query getGreaterOrEqual(String fieldName, Object obj) {
    if (obj == null)
      return null;

    if (obj instanceof String) {
      String value = (String) obj;
      Range range = new Range(value, null, Bounds.INCLUSIVE, Bounds.INCLUSIVE);
      range.setLowerTerm(value);
      range.setUpperTerm(null);
      return new Context(fieldName, range);
    } else if (obj instanceof Date) {
      Date value = (Date) obj;
      Range range = new Range(value, null, Bounds.INCLUSIVE, Bounds.INCLUSIVE);
      range.setLowerTerm(value);
      range.setUpperTerm(null);
      return new Context(fieldName, range);
    } else {
      throw new UnsupportedOperationException("Unsupported value type in greaterOrEqual expression "
          + obj.getClass().getName());
    }
  }
  
  
  protected Query getLessOrEqual(String fieldName, Object obj) {
    if (obj == null)
      return null;

    if (obj instanceof String) {
      String value = (String) obj;
      Range range = new Range(null, value, Bounds.INCLUSIVE, Bounds.INCLUSIVE);
      range.setLowerTerm(null);
      range.setUpperTerm(value);
      return new Context(fieldName, range);
    } else if (obj instanceof Date) {
      Date value = (Date) obj;
      Range range = new Range(null, value, Bounds.INCLUSIVE, Bounds.INCLUSIVE);
      range.setLowerTerm(null);
      range.setUpperTerm(value);      
      return new Context(fieldName, range);
    } else {
      throw new UnsupportedOperationException("Unsupported value type in lessOrEqual expression "
          + obj.getClass().getName());
    }
  }
  

  protected Query getWildcardTerm(String key, Object obj) {
    if (obj == null)
      return new MatchAll();

    if (obj instanceof String) {
      String value = (String) obj;
      if ("*".equals(value))
        return new Context(key, "*");

      Expression or = new Expression(Operator.OR);
      // add exact term to invoke synonym expansion
      Term term1 = new Term(value);
      term1.setWildcard(false);
      or.add(new Context(key, term1));
      // add wild card version
      Term term2 = new Term("*" + value + "*");
      term2.setWildcard(true);
      or.add(new Context(key, term2));
      return or;
    } else if (obj instanceof Date) {
      // Date value = (Date)obj;

      return null;
    } else if (obj instanceof ArrayList) {
      // TODO: test out if this part of AbstractAttivioData is ever used, looks dead to me - ECS
      Expression and = new Expression(Operator.AND);
      /*
       * ArrayList values = (ArrayList)obj; for (Object item : values) { if (item instanceof Map) { Map mapItem =
       * (Map)item; // Expression expr = getExpressionFromMap(mapItem); // if (expr != null) and.add(expr); } else {
       * 
       * } }
       */
      return and;
    } else if (obj instanceof Long) {
      return null;
    } else {
      throw new UnsupportedOperationException("Unsupported value type in wildcard expression "
          + obj.getClass().getName());
    }
  }

  // for example
  // Query: AND(PHRASE("colorectal cancer", "query"="colorectal cancer", "wildcard"=false), PHRASE("*Vari*", "query"="*Vari*", "wildcard"=true))  
  protected Query getIContainsTerm(String key, Object obj) {
    if (obj == null)
      return new MatchAll();

    if (obj instanceof String) {
      String value = (String) obj;
      return new Context(key, value);
    }
    
    
    if (obj instanceof String) {
      String value = (String) obj;
      if ("*".equals(value))
        return new Context(key, "*");

      Expression or = new Expression(Operator.OR);
      // add exact term to invoke synonym expansion
      Term term1 = new Term(value);
      term1.setWildcard(false);
      or.add(new Context(key, term1));
      // add wild card version
      Term term2 = new Term("*" + value + "*");
      term2.setWildcard(true);
      or.add(new Context(key, term2));
      return or;
    } else if (obj instanceof Date) {
      // Date value = (Date)obj;

      return null;
    } else if (obj instanceof ArrayList) {
      // TODO: test out if this part of AbstractAttivioData is ever used, looks dead to me - ECS
      Expression and = new Expression(Operator.AND);
      /*
       * ArrayList values = (ArrayList)obj; for (Object item : values) { if (item instanceof Map) { Map mapItem =
       * (Map)item; // Expression expr = getExpressionFromMap(mapItem); // if (expr != null) and.add(expr); } else {
       * 
       * } }
       */
      return and;
    } else if (obj instanceof Long) {
      return null;
    } else {
      throw new UnsupportedOperationException("Unsupported value type in wildcard expression "
          + obj.getClass().getName());
    }
  }

  
  
protected Query getEqualTerm(String key, Object obj) {
    if (obj == null)
      return new MatchAll();

    if (obj instanceof String) {
      String value = (String) obj;
      return new Context(key, value);
      /*
      if ("*".equals(value))
        return new Context(key, "*");

      // add exact term to invoke synonym expansion
      Term term1 = new Term(value);
      term1.setWildcard(false);

      return term1;
      */
    } else if (obj instanceof Date) {
      // Date value = (Date)obj;

      return null;
    } else if (obj instanceof ArrayList) {
      // TODO: test out if this part of AbstractAttivioData is ever used, looks dead to me - ECS
      Expression and = new Expression(Operator.AND);
      /*
       * ArrayList values = (ArrayList)obj; for (Object item : values) { if (item instanceof Map) { Map mapItem =
       * (Map)item; // Expression expr = getExpressionFromMap(mapItem); // if (expr != null) and.add(expr); } else {
       * 
       * } }
       */
      return and;
    } else if (obj instanceof Long) {
      return null;
    } else {
      throw new UnsupportedOperationException("Unsupported value type in wildcard expression "
          + obj.getClass().getName());
    }
  }

  
}
