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.writer;
  
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.List;
  import java.util.Map;
  import java.util.Map.Entry;
  
  import org.apache.commons.lang3.text.StrLookup;
  import org.apache.commons.lang3.text.StrSubstitutor;
  
  import static co.jirm.core.util.JirmPrecondition.check;
  import co.jirm.mapper.definition.SqlObjectDefinition;
  import co.jirm.mapper.definition.SqlParameterDefinition;
  import co.jirm.mapper.definition.SqlParameterObjectDefinition;
  
  import com.google.common.base.Function;
  import com.google.common.base.Joiner;
  import com.google.common.base.Optional;
  import com.google.common.collect.Collections2;
  import com.google.common.collect.Lists;
  
  
  public class SqlWriterStrategy {
  	protected final Joiner commaJoiner;
  	protected final String clauseSpaceSeparator;
  	
  	
  	protected SqlWriterStrategy(Joiner commaJoiner, String clauseSpaceSeparator) {
  		super();
  		this.commaJoiner = commaJoiner;
  		this.clauseSpaceSeparator = clauseSpaceSeparator;
  	}
  
  	public static SqlWriterStrategy newInstance(String sep) {
  		return new SqlWriterStrategy(Joiner.on("," + sep),sep);
  	}
  
  	public StringBuilder insertStatement(StringBuilder qb, final SqlObjectDefinition<?> definition, Map<String, Object> m) {
  		qb.append("INSERT INTO ").append(definition.getSqlName()).append(" (");
  		Joiner.on(", ").appendTo(qb, insertColumns(definition, m));
  		qb.append(") VALUES (");
  		Joiner.on(", ").appendTo(qb, valueMarks(m.values()));
  		qb.append(")");
  		return qb;
  	}
  	
  	public StringBuilder selectStatementBeforeWhere(StringBuilder sb, final SqlObjectDefinition<?> definition) {
  		//sb.append(clauseSpaceSeparator);
  		sb.append("SELECT ");
  		selectParameters(sb, definition);
  		sb.append(clauseSpaceSeparator);
  		sb.append("FROM ").append(definition.getSqlName());
  		innerJoin(sb, definition);
  		return sb;
  	}
  	
  	private Collection<String> sqlParameterNames(final SqlObjectDefinition<?> definition, final String tablePrefix, final String as) {
  		Collection<String> it = Collections2.transform(definition.getSimpleParameters().values(), new Function<SqlParameterDefinition, String>() {
  
  			@Override
  			public String apply(SqlParameterDefinition input) {
  				return input.sqlName(tablePrefix) + ((as == null) ? "" : " AS \"" + as + input.getParameterName() + "\"");
  			}
  			
  		});
  		return it;
  	}
  	
  	private void sqlSelectParameters(
  			List<String> parts, String prefix, 
  			String as, SqlParameterDefinition parameter, 
  			SqlParameterObjectDefinition od, int depth) {
  		parts.addAll(sqlParameterNames(od.getObjectDefintion(),prefix, as));
  		if (depth >= od.getMaximumLoadDepth()) return;
  		for (Entry<String, SqlParameterDefinition> defs : od.getObjectDefintion().getManyToOneParameters().entrySet()) {
  			String childAlias = prefix+"_"+ defs.getValue().getParameterName();
  			String newAs = as + defs.getValue().getParameterName() + ".";
  			sqlSelectParameters(parts, childAlias, newAs, defs.getValue(), defs.getValue().getObjectDefinition().get(), depth + 1);
  		}
  	}
  	
  	public void selectParameters(StringBuilder b, SqlObjectDefinition<?> definition) {
  		
  		List<String> params = Lists.newArrayList(sqlParameterNames(definition, definition.getSqlName(), null));
  		for (Entry<String, SqlParameterDefinition> defs : definition.getManyToOneParameters().entrySet()) {
  			String as = defs.getValue().getParameterName() + ".";
  			sqlSelectParameters(params, "_" + defs.getValue().getParameterName(), as, defs.getValue(), defs.getValue().getObjectDefinition().get(), 1);
  		}
  		commaJoiner.appendTo(b, params);
  	}
  	
  	private void innerJoin(StringBuilder b, String parent, String prefix, SqlParameterDefinition parameter, SqlParameterObjectDefinition od, int depth) {
  		SqlParameterDefinition pd = od.getObjectDefintion().idParameter().get();
  		b.append(clauseSpaceSeparator).append("INNER JOIN ").append(od.getObjectDefintion().getSqlName()).append(" ").append(prefix)
  		.append(" ON ")
  		.append(pd.sqlName(prefix))
  		.append(" = ")
  		.append(parameter.sqlName(parent));
  		if (depth >= od.getMaximumLoadDepth()) return;
  		for (Entry<String, SqlParameterDefinition> defs : od.getObjectDefintion().getManyToOneParameters().entrySet()) {
  			String childAlias = prefix+"_"+defs.getValue().getParameterName();
  			SqlParameterDefinition childParameter = defs.getValue();
  			SqlParameterObjectDefinition childObjectDef = defs.getValue().getObjectDefinition().get();
  			
  			innerJoin(b, prefix, childAlias, childParameter, childObjectDef, depth + 1);
  		}
  	}
  	
  	public void innerJoin(StringBuilder b, SqlObjectDefinition<?> definition) {
  		for (Entry<String, SqlParameterDefinition> defs : definition.getManyToOneParameters().entrySet()) {
  			innerJoin(b, definition.getSqlName(), "_" + defs.getValue().getParameterName(), 
  					defs.getValue(), defs.getValue().getObjectDefinition().get(), 1);
  		}
  	}
  	
  	public StringBuilder deleteStatementBeforeWhere(StringBuilder sb, final SqlObjectDefinition<?> definition) {
  		sb.append("DELETE FROM ").append(definition.getSqlName());
  		return sb;
  	}
  	
  	public StringBuilder updateStatementBeforeSet(StringBuilder sb, final SqlObjectDefinition<?> definition) {
  		sb.append("UPDATE ").append(definition.getSqlName());
  		return sb;
  	}
  	
  	protected List<String> insertColumns(SqlObjectDefinition<?> definition, Map<String, Object> m) {
  		List<String> equalsKeys = new ArrayList<String>();
  		for (Entry<String, Object> e : m.entrySet()) {
  			Optional<String> sqlName = definition.parameterNameToSql(e.getKey());
  			check.state(sqlName.isPresent(), 
  					"Property: {} not found in object.", e.getKey());
  			equalsKeys.add(sqlName.get());
  		}
  		return equalsKeys;
  	}
  	
  	public void appendValues(List<Object> values, final SqlObjectDefinition<?> definition, Map<String, Object> keysAndValues) {
  		for (Entry<String, Object> e : keysAndValues.entrySet()) {
  			SqlParameterDefinition d = definition.getParameters().get(e.getKey());
  			if (d == null) {
  				values.add(e.getValue());	
  			}
  			else {
  				values.add(d.convertToSql(e.getValue()));
  			}
  		}
  	}
  	
  	public List<Object> fillValues(final SqlObjectDefinition<?> definition, Map<String, Object> keysAndValues) {
  		List<Object> values = Lists.newArrayListWithCapacity(keysAndValues.size());
  		appendValues(values, definition, keysAndValues);
  		return values;
  	}
  	
  	protected List<String> valueMarks(Iterable<Object> values) {
  		List<String> marks = new ArrayList<String>();
  		for (@SuppressWarnings("unused") Object v : values) {
  			marks.add("?");
  		}
  		return marks;
  	}
  	
  	public String replacePropertyPaths(final SqlObjectDefinition<?> definition, final String sql) {
  		StrLookup<String> lookup = new StrLookup<String>() {
  			@Override
  			public String lookup(String key) {
  				Optional<String> p = parameterPathToSql(definition,key);
  				check.state(p.isPresent(),
  						"Invalid object path: {}", key);
  				return p.get();
  			}
  		};
  		StrSubstitutor s = new StrSubstitutor(lookup, "{{", "}}", '$');
  		String result = s.replace(sql);
  		return result;
  	}
  	
  	public String replaceProperties(final SqlObjectDefinition<?> definition, final String sql) {
  		StrLookup<String> lookup = new StrLookup<String>() {
  			@Override
  			public String lookup(String key) {
  				Optional<String> sqlName = definition.parameterNameToSql(key);
  				check.state(sqlName.isPresent(), "Property: {} not found in object.", key);
  				return sqlName.get();
  			}
  		};
  		StrSubstitutor s = new StrSubstitutor(lookup, "{{", "}}", '$');
  		String result = s.replace(sql);
  		return result;
  	}
  	
  	public Optional<String> parameterPathToSql(final SqlObjectDefinition<?> definition, String parameterDotPath) {
  		List<SqlParameterDefinition> parts = definition.resolveParameterPath(parameterDotPath);
  		if (parts.isEmpty()) return Optional.absent();
  		StringBuilder sb = new StringBuilder();
  		boolean first = true;
  		for (SqlParameterDefinition p : parts) {
  			if (first) {
  				first = false;
  				if ( ! p.isComplex() )
  					sb.append(definition.getSqlName());
  			}
  			if (p.isComplex()) {
  				sb.append("_").append(p.getParameterName());
  			}
  			else {
  				sb.append(".").append(p.sqlName());
  			}
  			
  		}
  		return Optional.of(sb.toString());
  
  	}
  	
  	
  //	public String selectConjuntionQuery() {
  //		StringBuilder qb = new StringBuilder();
  //		qb.append("SELECT ");
  //		definition.selectParameters(qb);
  //		qb.append(" FROM ").append(definition.getSqlName());
  //		definition.innerJoin(qb);
  //		qb.append(" WHERE ").append(this.andClause());
  //		if (limit.isPresent()) {
  //			qb.append(" ");
  //			qb.append("LIMIT ?");	
  //		}
  //		if (offset.isPresent()) {
  //			qb.append(" ");
  //			qb.append("OFFSET ?");
  //		}
  //		String q = qb.toString();
  //		return q;
  //	}
  //	
  
  }
  