/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.mahout.cf.taste.impl.model.jdbc;

import com.google.common.base.Preconditions;
import org.apache.mahout.cf.taste.common.TasteException;
import org.apache.mahout.common.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
 * <p>
 * See also {@link org.apache.mahout.cf.taste.impl.model.jdbc.PostgreSQLJDBCDataModel} --
 * same except deals with a table without preference info:
 * </p>
 *
 * <p>
 *
 * <pre>
 * CREATE TABLE taste_preferences (
 *   user_id BIGINT NOT NULL,
 *   item_id BIGINT NOT NULL,
 *   PRIMARY KEY (user_id, item_id)
 * );
 * CREATE INDEX taste_preferences_user_id_index ON taste_preferences (user_id);
 * CREATE INDEX taste_preferences_item_id_index ON taste_preferences (item_id);
 * </pre>
 *
 * </p>
 *
 * @see PostgreSQLJDBCDataModel
 */
public class PostgreSQLBooleanPrefJDBCDataModel extends SQL92BooleanPrefJDBCDataModel {

  private static final Logger log = LoggerFactory.getLogger(PostgreSQLBooleanPrefJDBCDataModel.class);

  private static final String POSTGRESQL_DUPLICATE_KEY_STATE = "23505"; // this is brittle...

  /**
   * <p>
   * Creates a  using the default {@link javax.sql.DataSource} (named
   * {@link #DEFAULT_DATASOURCE_NAME} and default table/column names.
   * </p>
   *
   * @throws org.apache.mahout.cf.taste.common.TasteException
   *          if {@link javax.sql.DataSource} can't be found
   */
  public PostgreSQLBooleanPrefJDBCDataModel() throws TasteException {
  }

  /**
   * <p>
   * Creates a  using the default {@link javax.sql.DataSource} found
   * under the given name, and using default table/column names.
   * </p>
   *
   * @param dataSourceName name of {@link javax.sql.DataSource} to look up
   * @throws org.apache.mahout.cf.taste.common.TasteException
   *          if {@link javax.sql.DataSource} can't be found
   */
  public PostgreSQLBooleanPrefJDBCDataModel(String dataSourceName) throws TasteException {
    super(dataSourceName);
  }

  /**
   * <p>
   * Creates a  using the given {@link javax.sql.DataSource} and default
   * table/column names.
   * </p>
   *
   * @param dataSource {@link javax.sql.DataSource} to use
   */
  public PostgreSQLBooleanPrefJDBCDataModel(DataSource dataSource) {
    super(dataSource);
  }

  /**
   * <p>
   * Creates a  using the given {@link javax.sql.DataSource} and default
   * table/column names.
   * </p>
   *
   * @param dataSource      {@link javax.sql.DataSource} to use
   * @param preferenceTable name of table containing preference data
   * @param userIDColumn    user ID column name
   * @param itemIDColumn    item ID column name
   * @param timestampColumn timestamp column name (may be null)
   */
  public PostgreSQLBooleanPrefJDBCDataModel(DataSource dataSource,
                                            String preferenceTable,
                                            String userIDColumn,
                                            String itemIDColumn,
                                            String timestampColumn) {
      super(dataSource, preferenceTable, userIDColumn, itemIDColumn, timestampColumn);
  }

  /**
   * Override since PostgreSQL doesn't have the same non-standard capability that MySQL has, to optionally
   * ignore an insert that fails since the row exists already.
   */
  @Override
  public void setPreference(long userID, long itemID, float value) throws TasteException {
    Preconditions.checkArgument(!Float.isNaN(value), "NaN value");
    log.debug("Setting preference for user {}, item {}", userID, itemID);

    String setPreferenceSQL = getSetPreferenceSQL();
    Connection conn = null;
    PreparedStatement stmt = null;
    try {
      conn = getDataSource().getConnection();
      stmt = conn.prepareStatement(setPreferenceSQL);
      setLongParameter(stmt, 1, userID);
      setLongParameter(stmt, 2, itemID);
      log.debug("Executing SQL update: {}", setPreferenceSQL);
      stmt.executeUpdate();
    } catch (SQLException sqle) {
      if (!POSTGRESQL_DUPLICATE_KEY_STATE.equals(sqle.getSQLState())) {
        log.warn("Exception while setting preference", sqle);
        throw new TasteException(sqle);
      }
    } finally {
      IOUtils.quietClose(null, stmt, conn);
    }
  }

}
