/*
 * Copyright (c) 2013 - 2015 xxyy (Philipp Nowak; devnull@nowak-at.net). All rights reserved.
 *
 * Any usage, including, but not limited to, compiling, running, redistributing, printing,
 *  copying and reverse-engineering is strictly prohibited without explicit written permission
 *  from the original author and may result in legal steps being taken.
 *
 * See the included LICENSE file (core/src/main/resources) or email xxyy98+xyclicense@gmail.com for details.
 */

package li.l1t.lanatus.sql.account;

import com.google.common.base.Preconditions;
import com.google.common.base.Verify;
import li.l1t.common.sql.sane.AbstractSqlConnected;
import li.l1t.common.sql.sane.SaneSql;
import li.l1t.lanatus.api.account.AccountSnapshot;
import li.l1t.lanatus.api.account.MutableAccount;
import li.l1t.lanatus.api.exception.AccountConflictException;

import java.time.Instant;
import java.util.UUID;

/**
 * Writes the state of a mutable account to database, attempting basic conflict resolution if the
 * state changed in database.
 *
 * @author <a href="http://xxyy.github.io/">xxyy</a>
 * @since 2016-10-07
 */
class JdbcAccountWriter extends AbstractSqlConnected {
    private final JdbcAccountFetcher<AccountSnapshot> snapshotFetcher;

    JdbcAccountWriter(SaneSql sql, JdbcAccountFetcher<AccountSnapshot> snapshotFetcher) {
        super(sql);
        this.snapshotFetcher = snapshotFetcher;
    }

    public void write(MutableAccount account) throws AccountConflictException {
        Preconditions.checkNotNull(account, "account");
        AccountSnapshot currentSnapshot = snapshotFetcher.fetchSingle(account.getPlayerId());
        attemptWrite(account, currentSnapshot);
    }

    private void attemptWrite(MutableAccount account, AccountSnapshot currentSnapshot) throws AccountConflictException {
        if (currentSnapshot.isDefault()) {
            createNewAccount(account);
        } else if (localCopyIsDifferentFromRemote(account, currentSnapshot)) {
            attemptUpdate(account, currentSnapshot);
        } //no changes otherwise
    }

    private void createNewAccount(MutableAccount account) {
        int rowsAffected = insertRaw(
                account.getPlayerId(), account.getMelonsCount(), account.getLastRank(),
                findMelonDifference(account)
        );
        Verify.verify(rowsAffected == 1, "expected insert to affect single row, was: ", account, rowsAffected);
    }

    private int insertRaw(UUID playerId, int melonsCount, String lastRank, int melonsDiff) {
        return sql().updateRaw("INSERT INTO " + SqlAccountRepository.TABLE_NAME + " " +
                        "SET player_uuid=?, created=?, melons=?, lastrank=? " +
                        "ON DUPLICATE KEY UPDATE melons=melons+?, lastrank=?",
                playerId.toString(), Instant.now(), melonsCount, lastRank, melonsDiff, lastRank
        );
    }

    private boolean localCopyIsDifferentFromRemote(MutableAccount account, AccountSnapshot currentSnapshot) {
        return !currentSnapshot.equals(account);
    }

    private void attemptUpdate(MutableAccount account, AccountSnapshot currentSnapshot) throws AccountConflictException {
        if (lastRankChangedConcurrentlyInDatabase(account, currentSnapshot)) {
            throw new AccountConflictException(currentSnapshot, account);
        } else {
            updateUsingMelonsDifference(account);
        }
    }

    private boolean lastRankChangedConcurrentlyInDatabase(MutableAccount account, AccountSnapshot currentSnapshot) {
        return !currentSnapshot.getLastRank().equals(account.getInitialState().getLastRank());
    }

    private void updateUsingMelonsDifference(MutableAccount account) {
        int rowsAffected = updateRaw(
                account.getPlayerId(), account.getLastRank(),
                findMelonDifference(account)
        );
        Verify.verify(rowsAffected == 1, "did not update single row when writing account", account, rowsAffected);
    }

    private int updateRaw(UUID playerId, String lastRank, int melonsDiff) {
        return sql().updateRaw("UPDATE " + SqlAccountRepository.TABLE_NAME + " " +
                        "SET melons=melons+?, lastrank=?" +
                        "WHERE player_uuid=?",
                melonsDiff, lastRank, playerId.toString(), lastRank
        );
    }

    private int findMelonDifference(MutableAccount account) {
        return account.getMelonsCount() - account.getInitialState().getMelonsCount();
    }
}

@@ -27,9 +27,9 @@
  201
-5
+6
  xxy
@@ -50,28 +50,18 @@
 ak; 
-devnull@nowak-at.net
+xyc@l1t.li
 ). A
@@ -406,45 +406,8 @@
 ces)
- or email xxyy98+xyclicense@gmail.com
  for
@@ -2199,54 +2199,8 @@
 nk()
-,%0A                findMelonDifference(account)
 %0A   
@@ -2399,32 +2399,16 @@
 lastRank
-, int melonsDiff
 ) %7B%0A    
@@ -2559,89 +2559,8 @@
 s=?,
- lastrank=? %22 +%0A                        %22ON DUPLICATE KEY UPDATE melons=melons+?,
  las
@@ -2637,30 +2637,8 @@
 unt,
- lastRank, melonsDiff,
  las

Remove ON DUPLICATE KEY UPDATE from JdbcAccountWriter