/*
 * Copyright (c) 2013-2014 Josef Hardi <josef.hardi@gmail.com>
 * 
 * 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.obidea.semantika.queryanswer.internal;

import java.net.URI;
import java.net.URISyntaxException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import com.obidea.semantika.exception.SemantikaException;
import com.obidea.semantika.queryanswer.AbstractQueryEngine;
import com.obidea.semantika.queryanswer.internal.QueryMetadata.Column;
import com.obidea.semantika.queryanswer.paging.SqlPaging;
import com.obidea.semantika.queryanswer.paging.SqlPagingStrategy;
import com.obidea.semantika.queryanswer.result.IQueryResult;
import com.obidea.semantika.queryanswer.result.IValue;
import com.obidea.semantika.queryanswer.result.Literal;
import com.obidea.semantika.queryanswer.result.QueryResult;
import com.obidea.semantika.queryanswer.result.QueryResultBuilder;
import com.obidea.semantika.queryanswer.result.Uri;
import com.obidea.semantika.queryanswer.result.ValueList;
import com.obidea.semantika.util.TemplateStringHelper;

public abstract class QueryResultLoader
{
   protected AbstractQueryEngine mQueryEngine;

   public QueryResultLoader(final AbstractQueryEngine queryEngine)
   {
      mQueryEngine = queryEngine;
   }

   protected IQueryResult evaluate(QueryParameters queryParameters) throws SQLException, SemantikaException
   {
      String sql = preprocessSql(getSqlString(), queryParameters.getQueryModifiers());
      final PreparedStatement ps =  preparedStatement(sql, queryParameters.getStatementSettings());
      final ResultSet rs = doQuery(ps);
      
      IQueryResult result = null;
      try {
         result = buildQueryResult(rs, getQueryMetadata());
      }
      finally {
         mQueryEngine.getQueryEvaluator().closeQueryStatement(ps, rs);
      }
      return result;
   }

   protected ResultSet doQuery(PreparedStatement ps) throws SQLException, SemantikaException
   {
      ResultSet rs = null;
      try {
         rs = mQueryEngine.getQueryEvaluator().getResultSet(ps);
      }
      catch (SQLException e) {
         mQueryEngine.getQueryEvaluator().closeQueryStatement(ps, rs);
         throw e;
      }
      return rs;
   }

   /**
    * Returns the produced SQL query string.
    */
   protected abstract String getSqlString();

   /**
    * Returns the produced SQL query metadata which is a list of returned columns
    * containing information about each label and data type.
    */
   protected abstract QueryMetadata getQueryMetadata();

   private String preprocessSql(String sql, QueryModifiers modifiers)
   {
      if (modifiers.isSet()) {
         int limit = modifiers.getLimit();
         int offset = modifiers.getOffset();
         final List<String> ascOrder = modifiers.getAscendingOrder();
         final List<String> descOrder = modifiers.getDescendingOrder();
         SqlPaging paging = getPaging();
         sql = paging.createPaging(sql, limit, offset, ascOrder, descOrder);
      }
      return sql;
   }

   private PreparedStatement preparedStatement(String sql, StatementSettings settings) throws SQLException, SemantikaException
   {
      PreparedStatement ps = null;
      try {
         ps = mQueryEngine.getQueryEvaluator().prepareQueryStatement(sql);
         
         /*
          * These settings come from user code in SelectQuery class and they will override the
          * global JDBC statement parameters in the configuration file, if any.
          */
         if (settings.getQueryTimeout() != null) {
            ps.setQueryTimeout(settings.getQueryTimeout().intValue());
         }
         if (settings.getFetchSize() != null) {
            ps.setFetchSize(settings.getFetchSize().intValue());
         }
         if (settings.getMaxRows() != null) {
            ps.setMaxRows(settings.getMaxRows().intValue());
         }
      }
      catch (SQLException e) {
         mQueryEngine.getQueryEvaluator().closeQueryStatement(ps, null);
         throw e;
      }
      catch (SemantikaException e) {
         mQueryEngine.getQueryEvaluator().closeQueryStatement(ps, null);
         throw e;
      }
      return ps;
   }

   private static QueryResult buildQueryResult(ResultSet rs, QueryMetadata metadata) throws SQLException
   {
      QueryResultBuilder builder = new QueryResultBuilder();
      builder.start(metadata.getSelectNames());
      while (rs.next()) {
         ValueList valueList = getValueListFromResultSet(rs, metadata);
         builder.handleResultFragment(valueList);
      }
      return builder.getQueryResult();
   }

   private static ValueList getValueListFromResultSet(ResultSet rs, QueryMetadata metadata) throws SQLException
   {
      List<String> selectLabels = new ArrayList<String>();
      List<IValue> values = getSelectValues(rs, metadata, selectLabels);
      return new ValueList(selectLabels, values);
   }

   private static List<IValue> getSelectValues(ResultSet rs, QueryMetadata metadata, List<String> selectLabels) throws SQLException
   {
      List<IValue> values = new ArrayList<IValue>();
      for (int i = 1; i <= metadata.size(); i++) {
         String label = getLabel(metadata, i);
         selectLabels.add(label);
         IValue value = getValue(rs, metadata, i);
         values.add(value);
      }
      return values;
   }

   private static String getLabel(QueryMetadata metadata, int position) throws SQLException
   {
      return metadata.getColumn(position).getLabel();
   }

   private static IValue getValue(ResultSet resultSet, QueryMetadata metadata, int position) throws SQLException
   {
      Column c = metadata.getColumn(position);
      if (c.isLiteral()) {
         String value = resultSet.getString(position);
         return new Literal(value, URI.create(c.getDatatype()));
      }
      else {
         String value = resultSet.getString(position);
         if (value != null && !validUri(value)) {
            /*
             * We assume if the URI string is invalid it means the given value is a
             * URI template construction, i.e., <template> : <value1> <value2> etc. 
             */
            value = TemplateStringHelper.buildUri(value);
         }
         return new Uri(value);
      }
   }

   private static boolean validUri(String uriString)
   {
      try {
         new URI(uriString);
      }
      catch (URISyntaxException e) {
         return false;
      }
      return true;
   }

   private SqlPaging getPaging()
   {
      return SqlPagingStrategy.buildPaging(mQueryEngine.getSettings().getDatabase().getDatabaseProduct());
   }
}

@@ -6173,24 +6173,241 @@
 eption%0A   %7B%0A
+      String value = resultSet.getString(position);%0A      /*%0A       * If the JDBC ResultSet gives null, then this method returns null as well.%0A       */%0A      if (value == null) %7B%0A         return null;%0A      %7D%0A      %0A
       Column
@@ -6472,63 +6472,8 @@
 ) %7B%0A
-         String value = resultSet.getString(position);%0A
     
@@ -6567,84 +6567,12 @@
     
-String value = resultSet.getString(position);%0A         if (value != null && 
+if (
 !val

Handle the null value.