/*
 * Copyright (c) 2012 Google Inc.
 *
 * 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.google.cloud.demo.model.sql;

import com.google.appengine.api.blobstore.BlobKey;
import com.google.cloud.demo.ConfigManager;
import com.google.cloud.demo.model.Photo;
import com.google.cloud.demo.model.PhotoManager;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.List;

/**
 * Photo manager for Cloud SQL.
 *
 */
public class PhotoManagerSql extends DemoEntityManagerSql<Photo> implements PhotoManager {
  private static final String SQL_DELETE_PHOTO = "DELETE FROM Photos WHERE PhotoId=?";
  private static final String SQL_ORDER_BY_POSTTIME = " ORDER BY PostTime DESC";
  private static final String SQL_SELECT_ALL_PHOTOS_PREFIX =
      "SELECT PhotoId, Title, BlobKey, PostTime, UserId,"
      + " Nickname, IsShared, IsActive FROM Photos INNER JOIN DemoUsers USING(UserId)";
  private static final String SQL_SELECT_ALL_PHOTOS = SQL_SELECT_ALL_PHOTOS_PREFIX
      + SQL_ORDER_BY_POSTTIME;
  private static final String SQL_SELECT_ALL_ACTIVE_PHOTOS = SQL_SELECT_ALL_PHOTOS_PREFIX
      + " WHERE IsActive" + SQL_ORDER_BY_POSTTIME;
  private static final String SQL_SELECT_ALL_DEACTIVATED_PHOTOS =
      SQL_SELECT_ALL_PHOTOS_PREFIX + " WHERE NOT IsActive";
  private static final String SQL_SELECT_ALL_OWNED_PHOTOS =
      SQL_SELECT_ALL_PHOTOS_PREFIX + " WHERE IsActive AND UserId=?" + SQL_ORDER_BY_POSTTIME;
  private static final String SQL_SELECT_ALL_SHARED_PHOTOS =
      SQL_SELECT_ALL_PHOTOS_PREFIX + " WHERE IsActive AND UserId != ? AND IsShared"
          + SQL_ORDER_BY_POSTTIME;
  private static final String SQL_SELECT_PHOTO =
      SQL_SELECT_ALL_PHOTOS_PREFIX + " WHERE PhotoId=? ANd UserId=?";
  private static final String SQL_UPDATE_SQL =
      "UPDATE Photos SET Title=?, BlobKey=?, UserId=?, IsShared=?, IsActive=? WHERE PhotoId=?";
  private static final String SQL_INSERT_PHOTO =
      "INSERT INTO Photos(Title, BlobKey, UserId, IsShared, IsActive) VALUES(?, ?, ?, ?, ?)";

  public PhotoManagerSql(ConfigManager configManager) {
    super(configManager);
  }

  @Override
  public Iterable<Photo> getEntities() {
    return getEntitiesInTransaction(SQL_SELECT_ALL_PHOTOS);
  }

  private Iterable<Photo> getEntitiesInTransaction(final String query) {
    return runInTransaction(new TransactionalOperation<List<Photo>>() {
      @Override
      public List<Photo> execute(Connection conn) throws SQLException {
        return queryEntities(conn, query, new PhotoSelectQueryCallback() {
          @Override
          public void prepareStatement(PreparedStatement stmt) {
            // NOP
          }
        });
      }});
  }

  @Override
  public Iterable<Photo> getDeactivedPhotos() {
    return getEntitiesInTransaction(SQL_SELECT_ALL_DEACTIVATED_PHOTOS);
  }

  @Override
  public Iterable<Photo> getActivePhotos() {
    return getEntitiesInTransaction(SQL_SELECT_ALL_ACTIVE_PHOTOS);
  }

  @Override
  public Photo deleteEntity(final Photo entity) {
    return runInTransaction(new TransactionalOperation<Photo>() {
      @Override
      public Photo execute(Connection conn) throws SQLException {
        return deleteEntity(conn, entity, SQL_DELETE_PHOTO, new DefaultUpdateQueryCallback() {
          @Override
          public void prepareStatement(PreparedStatement stmt) throws SQLException {
            stmt.setLong(1, entity.getId());
          }
        });
      }
    });
  }

  @Override
  public Photo upsertEntity(final Photo demoEntity) {
    return runInTransaction(new TransactionalOperation<Photo>() {
      @Override
      public Photo execute(Connection conn) throws SQLException {
        return upsertPhoto(conn, demoEntity);
      }
    });
  }

  private Photo upsertPhoto(Connection conn, final Photo demoEntity)
      throws SQLException {
    String query = SQL_INSERT_PHOTO;
    if (demoEntity.getId() != null) {
      query = SQL_UPDATE_SQL;
    }
    return upsertEntity(conn, demoEntity, query, new DefaultUpdateQueryCallback() {

      @Override
      public void prepareStatement(PreparedStatement stmt) throws SQLException {
        int count = 1;
        stmt.setString(count++, demoEntity.getTitle());
        stmt.setString(count++, demoEntity.getBlobKey().getKeyString());
        stmt.setString(count++, demoEntity.getOwnerId());
        stmt.setBoolean(count++, demoEntity.isShared());
        stmt.setBoolean(count++, demoEntity.isActive());
        if (demoEntity.getId() != null) {
          stmt.setLong(count, demoEntity.getId());
        }
      }

      @Override
      public void onIdGenerated(long id) {
        if (demoEntity instanceof PhotoSql && demoEntity.getId() == null) {
          PhotoSql photoSql = (PhotoSql) demoEntity;
          photoSql.setId(id);
        }
      }
    });
  }

  @Override
  public Photo getPhoto(final String userId, final long id) {
    return runInTransaction(new TransactionalOperation<Photo>() {
      @Override
      public Photo execute(Connection conn) throws SQLException {
        return getPhoto(conn, userId, id);
      }
    });
  }

  private Photo getPhoto(Connection conn, final String userId, final long id)
      throws SQLException {
    return queryEntity(conn, SQL_SELECT_PHOTO, new PhotoSelectQueryCallback() {
      @Override
      public void prepareStatement(PreparedStatement stmt) throws SQLException {
        stmt.setLong(1, id);
        stmt.setString(2, userId);
      }
    });
  }

  @Override
  public Iterable<Photo> getOwnedPhotos(final String userId) {
    return runInTransaction(new TransactionalOperation<List<Photo>>() {
      @Override
      public List<Photo> execute(Connection conn) throws SQLException {
        return queryEntities(conn, SQL_SELECT_ALL_OWNED_PHOTOS, new PhotoSelectQueryCallback() {
          @Override
          public void prepareStatement(PreparedStatement stmt) throws SQLException {
            stmt.setString(1, userId);
          }
        });
      }
    });
  }

  @Override
  public Iterable<Photo> getSharedPhotos(final String userId) {
    return runInTransaction(new TransactionalOperation<List<Photo>>() {
      @Override
      public List<Photo> execute(Connection conn) throws SQLException {
        return queryEntities(conn, SQL_SELECT_ALL_SHARED_PHOTOS, new PhotoSelectQueryCallback() {
          @Override
          public void prepareStatement(PreparedStatement stmt) throws SQLException {
            stmt.setString(1, userId);
          }
        });
      }
    });
  }

  @Override
  public Photo newPhoto(String userId) {
    return new PhotoSql(userId);
  }

  @Override
  public Photo deactivePhoto(final String userId, final long id) {
    return runInTransaction(new TransactionalOperation<Photo>() {
      @Override
      public Photo execute(Connection conn) throws SQLException {
        Photo photo = getPhoto(conn, userId, id);
        if (photo != null && photo.isActive()) {
          photo.setActive(false);
          upsertPhoto(conn, photo);
        }
        return photo;
      }});
  }

  /**
   * Helper callback classes that does result processing.
   */
  private abstract class PhotoSelectQueryCallback
      implements QueryCallback.SelectQueryCallback<Photo> {
    @Override
    public Photo fromResultSet(ResultSet rs) throws SQLException {
      int count = 1;
      Long photoId = rs.getLong(count++);
      String title = rs.getString(count++);
      String blobKey = rs.getString(count++);
      Timestamp ts = rs.getTimestamp(count++);
      String userId = rs.getString(count++);
      String nickname = rs.getString(count++);
      Boolean isShared = rs.getBoolean(count++);
      Boolean isActive = rs.getBoolean(count++);
      Photo photo = new PhotoSql(photoId, userId);
      photo.setTitle(title);
      photo.setBlobKey(new BlobKey(blobKey));
      photo.setUploadTime(ts.getTime());
      photo.setOwnerNickname(nickname);
      photo.setShared(isShared != null && isShared);
      photo.setActive(isActive == null || isActive);
      return photo;
    }
  }
}