// This software is released into the Public Domain.  See copying.txt for details.
package org.openstreetmap.osmosis.apidb.v0_6.impl;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Set;

import org.openstreetmap.osmosis.apidb.common.DatabaseContext;
import org.openstreetmap.osmosis.core.OsmosisRuntimeException;
import org.openstreetmap.osmosis.core.database.ReleasableStatementContainer;
import org.openstreetmap.osmosis.core.domain.v0_6.OsmUser;
import org.openstreetmap.osmosis.core.lifecycle.Closeable;


/**
 * Creates or loads the details of the Osmosis user in the database.
 * 
 * @author Brett Henderson
 */
public class UserManager implements Closeable {

    private static final String SELECT_SQL_USER_EXISTS = "SELECT Count(id) AS userCount FROM users WHERE id = ?";

    private static final String INSERT_SQL_USER = "INSERT INTO users (id, email, pass_crypt,"
            + " creation_time, display_name, data_public, description, home_lat, home_lon, home_zoom,"
            + " pass_salt) VALUES (?, ?, '00000000000000000000000000000000', NOW(), ?, ?,"
            + " ?, 0, 0, 3, '00000000')";

    private static final String UPDATE_SQL_USER = "UPDATE users SET display_name = ? WHERE id = ?";

    private final DatabaseContext dbCtx;

    private final Set<Integer> updatedUsers;

    private final ReleasableStatementContainer statementContainer;

    private PreparedStatement statementInsert;

    private PreparedStatement statementExists;

    private PreparedStatement statementUpdate;

    /**
     * Creates a new instance.
     * 
     * @param dbCtx The database context to use for all database access.
     */
    public UserManager(DatabaseContext dbCtx) {
        this.dbCtx = dbCtx;

        updatedUsers = new HashSet<Integer>();
        statementContainer = new ReleasableStatementContainer();
    }

    /**
     * Checks if the specified user exists in the database.
     * 
     * @param user The user to check for.
     * @return True if the user exists, false otherwise.
     */
    private boolean doesUserExistInDb(OsmUser user) {
        if (statementExists == null) {
            statementExists = statementContainer.add(dbCtx.prepareStatementForStreaming(SELECT_SQL_USER_EXISTS));
        }

        try {
	        statementExists.setInt(1, user.getId());
	        
	        try (ResultSet resultSet = statementExists.executeQuery()) {
	        	resultSet.next();
	        	
	        	return resultSet.getInt("userCount") != 0;
	        }
        } catch (SQLException e) {
            throw new OsmosisRuntimeException("Unable to check if user with id " + user.getId()
                    + " exists in the database.", e);
        }
    }

    /**
     * Inserts the specified user into the database.
     * 
     * @param user The user to be inserted.
     */
    private void insertUser(OsmUser user) {
        int prmIndex;
        String userName;
        boolean dataPublic;

        if (statementInsert == null) {
            statementInsert = statementContainer.add(dbCtx.prepareStatement(INSERT_SQL_USER));
        }

        if (OsmUser.NONE.equals(user)) {
            userName = "Osmosis Anonymous";
            dataPublic = false;
        } else {
            userName = user.getName();
            dataPublic = true;
        }

        try {
            prmIndex = 1;
            statementInsert.setInt(prmIndex++, user.getId());
            statementInsert.setString(prmIndex++, "osmosis_user_" + user.getId() + "@example.com");
            statementInsert.setString(prmIndex++, userName);
            statementInsert.setBoolean(prmIndex++, dataPublic);
            statementInsert.setString(prmIndex++, userName);

            statementInsert.executeUpdate();

        } catch (SQLException e) {
            throw new OsmosisRuntimeException("Unable to insert user with id " + user.getId() + " into the database.",
                    e);
        }
    }

    /**
     * Updates the specified user in the database.
     * 
     * @param user The user to be updated.
     */
    private void updateUser(OsmUser user) {
        int prmIndex;

        if (statementUpdate == null) {
            statementUpdate = statementContainer.add(dbCtx.prepareStatement(UPDATE_SQL_USER));
        }

        try {
            String userName;

            if (OsmUser.NONE.equals(user)) {
                userName = "Osmosis Anonymous";
            } else {
                userName = user.getName();
            }

            prmIndex = 1;
            statementUpdate.setString(prmIndex++, userName);
            statementUpdate.setInt(prmIndex++, user.getId());

            statementUpdate.executeUpdate();

        } catch (SQLException e) {
            throw new OsmosisRuntimeException("Unable to update user with id " + user.getId() + " in the database.", e);
        }
    }

    /**
     * Adds the user to the database or updates the name of the existing database entry if one
     * already exists with the same id.
     * 
     * @param user The user to be created or updated.
     */
    public void addOrUpdateUser(OsmUser user) {
        if (!updatedUsers.contains(user.getId())) {
            if (doesUserExistInDb(user)) {
                updateUser(user);
            } else {
                insertUser(user);
            }

            updatedUsers.add(user.getId());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void close() {
        statementContainer.close();
    }
}
