content: /**
   * Copyright (C) 2012 the original author or authors.
   *
   * 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 co.jirm.orm.builder;
  
  import static com.google.common.base.Strings.nullToEmpty;
  import static co.jirm.core.util.JirmPrecondition.check;
  
  import com.google.common.collect.ImmutableList;
  import com.google.common.collect.ImmutableMap;
  
  
  
  public abstract class ImmutableCondition extends ImmutableParameterized<ImmutableCondition> implements Condition<ImmutableCondition> {
  	
  	protected final ConditionType conditionType;
  
  	public abstract void accept(ConditionVisitor v);
  	
  	public static ImmutableCondition where(String custom) {
  		if (nullToEmpty(custom).trim().isEmpty())
  			return where();
  		return new CustomCondition(custom);
  	}
  	
  	public static ImmutableCondition where() {
  		return new NoOp();
  	}
  	
  	
  	ImmutableCondition(ImmutableList<Object> parameters, ImmutableMap<String, Object> nameParameters,
  			ConditionType conditionType) {
  		super(parameters, nameParameters);
  		this.conditionType = conditionType;
  	}
  
  	ImmutableCondition(ImmutableCondition child, Object value) {
  		super(child, value);
  		this.conditionType = child.conditionType;
  	}
  
  	ImmutableCondition(ImmutableCondition child, String key, Object value) {
  		super(child, key, value);
  		this.conditionType = child.conditionType;
  	}
  
  	ImmutableCondition(ConditionType type) {
  		conditionType = type;
  	}
  	
  	public ImmutableCondition and(ImmutableCondition other) {
  		if (other.conditionType == ConditionType.NOOP)
  			return this;
  		if (isNoOp())
  			return other;
  		return CombinedCondition.newInstance(this, CombineType.AND, other);
  	}
  	public ImmutableCondition and(String sql) {
  		if (isNoOp())
  			return where(sql);
  		return this.and(new CustomCondition(sql));
  
  	}
      public ImmutableCondition or(ImmutableCondition other) {
  		if (other.conditionType == ConditionType.NOOP)
  			return this;
  		if (isNoOp())
  			return other;
  		return this.or(other);
      }
      public ImmutableCondition or(String sql) {
  		if (isNoOp())
  			return where(sql);
  		return CombinedCondition.newInstance(this, CombineType.OR, new CustomCondition(sql));
      }
      
      public ImmutableCondition not() {
      	if (isNoOp())
      		return this;
      	return new NotCondition(this);
      }
      
      public boolean isNoOp() {
      	return (this.conditionType == ConditionType.NOOP);
      }
      
      private final static class CombinedCondition extends ImmutableCondition {
      	
      	private final ImmutableList<ImmutableCondition> conditions;
      	
      	public static CombinedCondition newInstance(ImmutableCondition lh, CombineType operator, ImmutableCondition rh) {
      		ConditionType conditionType = operator == CombineType.AND ? ConditionType.AND : ConditionType.OR;
      		final ImmutableList<ImmutableCondition> conditions;
      		final ImmutableList<Object> parameters;
      		final ImmutableMap<String, Object> nameParameters;
      		
      		if (conditionType == lh.conditionType && conditionType == rh.conditionType) {
      			conditions = ImmutableList.<ImmutableCondition>builder()
      					.addAll(((CombinedCondition)lh).conditions)
      					.addAll(((CombinedCondition)rh).conditions).build();
      			parameters = ImmutableList.<Object>builder().addAll(lh.getParameters()).addAll(rh.getParameters()).build();
      			nameParameters = ImmutableMap.<String, Object>builder()
      					.putAll(lh.getNameParameters()).putAll(rh.getNameParameters()).build();
      		}
      		else if (conditionType == lh.conditionType && ! rh.conditionType.isCombine()) {
      			conditions = ImmutableList.<ImmutableCondition>builder()
      					.addAll(((CombinedCondition)lh).conditions).add(rh).build();
      			parameters = ImmutableList.copyOf(lh.getParameters());
      			nameParameters = ImmutableMap.copyOf(lh.getNameParameters());
      		}
      		else if (conditionType == rh.conditionType && ! lh.conditionType.isCombine()) {
      			conditions = ImmutableList.<ImmutableCondition>builder()
      					.addAll(((CombinedCondition)rh).conditions).add(lh).build();
      			parameters = ImmutableList.copyOf(rh.getParameters());
      			nameParameters = ImmutableMap.copyOf(rh.getNameParameters());
      		}
      		else {
      			conditions = ImmutableList.<ImmutableCondition>builder().add(lh).add(rh).build();
      			parameters = ImmutableList.of();
      			nameParameters = ImmutableMap.of();
      		}
      		return new CombinedCondition(parameters, nameParameters, conditionType, conditions);
      	}
      	
      	private CombinedCondition(ImmutableList<Object> parameters, ImmutableMap<String, Object> nameParameters,
  				ConditionType conditionType, ImmutableList<ImmutableCondition> conditions) {
  			super(parameters, nameParameters, conditionType);
  			this.conditions = conditions;
      	}
      	
      	@Override
      	public void accept(ConditionVisitor v) {
      		if (this.conditionType == ConditionType.AND)
      			v.visitAnd(conditions, this);
      		else
      			v.visitOr(conditions, this);
      			
      	}
      	
  		private CombinedCondition(CombinedCondition child, Object value) {
  			super(child, value);
  			this.conditions = child.conditions;
  		}
  
  		private CombinedCondition(CombinedCondition child, String key, Object value) {
  			super(child, key, value);
  			this.conditions = child.conditions;
  		}
  		
  		@Override
  		public ImmutableCondition bind(String key, Object value) {
  			return new CombinedCondition(this, key, value);
  		}
  
  		@Override
  		public ImmutableCondition with(Object ... value ) {
  			return new CombinedCondition(this, value[0]);
  		}
  
  
  		@Override
  		public int hashCode() {
  			final int prime = 31;
  			int result = super.hashCode();
  			result = prime * result + ((conditions == null) ? 0 : conditions.hashCode());
  			return result;
  		}
  
  
  		@Override
  		public boolean equals(Object obj) {
  			if (this == obj)
  				return true;
  			if (!super.equals(obj))
  				return false;
  			if (getClass() != obj.getClass())
  				return false;
  			CombinedCondition other = (CombinedCondition) obj;
  			if (conditions == null) {
  				if (other.conditions != null)
  					return false;
  			}
  			else if (!conditions.equals(other.conditions))
  				return false;
  			return true;
  		}
  		
  		
  
      }
      
      private final static class CustomCondition extends ImmutableCondition {
      	private final String sql;
  
      	@Override
      	public void accept(ConditionVisitor v) {
      		v.visitSql(sql, this);
      	}
      	
  		private CustomCondition(String sql) {
  			super(ConditionType.CUSTOM);
  			this.sql = sql;
  		}
  		
  		private CustomCondition(CustomCondition child, Object value) {
  			super(child, value);
  			this.sql = child.sql;
  		}
  
  		private CustomCondition(CustomCondition child, String key, Object value) {
  			super(child, key, value);
  			this.sql = child.sql;
  		}
  		
  		@Override
  		public ImmutableCondition bind(String key, Object value) {
  			return new CustomCondition(this, key, value);
  		}
  
  		@Override
  		public ImmutableCondition with(Object ... value) {
  			//TODO check args
  			return new CustomCondition(this, value[0]);
  		}
  
  		@Override
  		public int hashCode() {
  			final int prime = 31;
  			int result = super.hashCode();
  			result = prime * result + ((sql == null) ? 0 : sql.hashCode());
  			return result;
  		}
  
  		@Override
  		public boolean equals(Object obj) {
  			if (this == obj)
  				return true;
  			if (!super.equals(obj))
  				return false;
  			if (getClass() != obj.getClass())
  				return false;
  			CustomCondition other = (CustomCondition) obj;
  			if (sql == null) {
  				if (other.sql != null)
  					return false;
  			}
  			else if (!sql.equals(other.sql))
  				return false;
  			return true;
  		}
      }
  
      private final static class NotCondition extends ImmutableCondition {
      	private final ImmutableCondition condition;
      	
      	@Override
      	public void accept(ConditionVisitor visitor) {
      		visitor.visitNot(condition);
      	}
      	
      	public NotCondition(ImmutableCondition condition) {
      		super(ConditionType.NOT);
      		this.condition = condition;
      	}
      	
  		private NotCondition(NotCondition child, Object value) {
  			super(child, value);
  			this.condition = child.condition;
  		}
  
  		private NotCondition(NotCondition child, String key, Object value) {
  			super(child, key, value);
  			this.condition = child.condition;
  		}
  		
  		@Override
  		public NotCondition bind(String key, Object value) {
  			return new NotCondition(this, key, value);
  		}
  
  		@Override
  		public ImmutableCondition with(Object ... value) {
  			return new NotCondition(this, value[0]);
  		}
  
  		@Override
  		public int hashCode() {
  			final int prime = 31;
  			int result = super.hashCode();
  			result = prime * result + ((condition == null) ? 0 : condition.hashCode());
  			return result;
  		}
  
  		@Override
  		public boolean equals(Object obj) {
  			if (this == obj)
  				return true;
  			if (!super.equals(obj))
  				return false;
  			if (getClass() != obj.getClass())
  				return false;
  			NotCondition other = (NotCondition) obj;
  			if (condition == null) {
  				if (other.condition != null)
  					return false;
  			}
  			else if (!condition.equals(other.condition))
  				return false;
  			return true;
  		}
  		
  		
      }
      
      private final static class NoOp extends ImmutableCondition {
      	
  		private NoOp() {
  			super(ConditionType.NOOP);
  		}
  		
  		@Override
  		public void accept(ConditionVisitor v) {
  			v.visitNoOp();
  		}
  
  		private NoOp(ImmutableCondition child, Object value) {
  			super(child, value);
  			throw check.stateInvalid("Cannot set parameters on this condition.");
  		}
  
  		private NoOp(ImmutableCondition child, String key, Object value) {
  			super(child, key, value);
  			throw check.stateInvalid("Cannot set parameters on this condition.");
  		}
  
  		@Override
  		public ImmutableCondition bind(String key, Object value) {
  			throw check.stateInvalid("Cannot set parameters on this condition.");
  		}
  
  		@Override
  		public ImmutableCondition with(Object ... value) {
  			throw check.stateInvalid("Cannot set parameters on this condition.");
  		}
      	
      }
      
      public enum ConditionType {
      	CUSTOM, NOOP, AND, OR, NOT;
      	
      	public boolean isCombine() {
      		return this == AND || this == OR;
      	}
      }
  
  	@Override
  	public int hashCode() {
  		final int prime = 31;
  		int result = super.hashCode();
  		result = prime * result + ((conditionType == null) ? 0 : conditionType.hashCode());
  		return result;
  	}
  
  	@Override
  	public boolean equals(Object obj) {
  		if (this == obj)
  			return true;
  		if (!super.equals(obj))
  			return false;
  		if (getClass() != obj.getClass())
  			return false;
  		ImmutableCondition other = (ImmutableCondition) obj;
  		if (conditionType != other.conditionType)
  			return false;
  		return true;
  	}
  	
  	@Override
  	public String toString() {
  		StringBuilder sb = new StringBuilder();
  		ConditionVisitors.conditionVisitor(sb).startOn(this);
  		return sb.toString();
  	}
      
      
  }
  