/*
   Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
 */

package com.mysql.clusterj.tie;

import java.util.ArrayList;
import java.util.List;

import com.mysql.clusterj.ClusterJUserException;
import com.mysql.clusterj.core.util.I18NHelper;
import com.mysql.clusterj.core.util.Logger;
import com.mysql.clusterj.core.util.LoggerFactoryService;

import com.mysql.ndbjtie.ndbapi.NdbErrorConst;
import com.mysql.ndbjtie.ndbapi.NdbOperationConst;
import com.mysql.ndbjtie.ndbapi.NdbScanOperation;
import com.mysql.ndbjtie.ndbapi.NdbOperationConst.LockMode;

/**
 *
 */
class NdbRecordScanResultDataImpl extends NdbRecordResultDataImpl {

    /** My message translator */
    static final I18NHelper local = I18NHelper
            .getInstance(NdbRecordScanResultDataImpl.class);

    /** My logger */
    static final Logger logger = LoggerFactoryService.getFactory()
            .getInstance(NdbRecordScanResultDataImpl.class);

    /** Flags for iterating a scan */
    protected final int RESULT_READY = 0;
    protected final int SCAN_FINISHED = 1;
    protected final int CACHE_EMPTY = 2;

    /** The ClusterTransaction only for executing the lock takeover if any */
    private final ClusterTransactionImpl clusterTransaction;

    /** The NdbOperation that defines the result */
    private final NdbRecordScanOperationImpl scanOperation;

    /** The NdbScanOperation */
    private final NdbScanOperation ndbScanOperation;

    /** The number to skip */
    protected final long skip;

    /** The limit */
    protected final long limit;

    /** The record counter during the scan */
    protected long recordCounter = 0;

    /** True if records are locked during this scan */
    private final boolean lockRecordsDuringScan;

    /** True if any records have been locked while scanning the cache */
    private List<NdbOperationConst> recordsLocked = new ArrayList<NdbOperationConst>();

    /** Construct the ResultDataImpl based on an NdbRecordOperationImpl.
     * When used with the compatibility operations, delegate to the NdbRecordOperation
     * to copy data.
     * @param clusterTransaction the cluster transaction used to take over locks
     * @param operation the NdbRecordOperationImpl
     * @param skip the number of rows to skip
     * @param limit the last row number
     */
    public NdbRecordScanResultDataImpl(ClusterTransactionImpl clusterTransaction,
            NdbRecordScanOperationImpl scanOperation, long skip, long limit) {
        super(scanOperation);
        this.clusterTransaction = clusterTransaction;
        this.scanOperation = scanOperation;
        this.ndbScanOperation = (NdbScanOperation)scanOperation.ndbOperation;
        this.skip = skip;
        this.limit = limit;
        this.lockRecordsDuringScan = ndbScanOperation.getLockMode() != LockMode.LM_CommittedRead;
    }

    /** If any locks were taken over, execute the takeover operations
     */
    private void executeIfRecordsLocked() {
        if (recordsLocked.size() != 0) {
            clusterTransaction.executeNoCommit(false, true);
            for (NdbOperationConst ndbOperation: recordsLocked) {
                NdbErrorConst ndbError = ndbOperation.getNdbError();
                if (ndbError.code() == 0) {
                    continue;
                } else {
                    String detail = clusterTransaction.db.getNdbErrorDetail(ndbError);
                    Utility.throwError(null, ndbError, detail);
                }
            }
            recordsLocked.clear();
        }
    }

    @Override
    public boolean next() {
        if (!clusterTransaction.isEnlisted()) {
            throw new ClusterJUserException(local.message("ERR_Db_Is_Closing"));
        }
        if (recordCounter >= limit) {
            // the next record is past the limit; we have delivered all the rows
            executeIfRecordsLocked();
            ndbScanOperation.close(true, true);
            return false;
        }
        // NdbScanOperation may have many results.
        boolean done = false;
        boolean fetch = false;
        boolean force = true; // always true for scans
        while (!done) {
            int result = scanOperation.nextResultCopyOut(fetch, force);
            switch (result) {
                case RESULT_READY:
                    if (++recordCounter > skip) {
                        // this record is past the skip
                        // if scanning with locks, grab the lock for the current transaction
                        if (lockRecordsDuringScan) {
                            NdbOperationConst lockedRecord = scanOperation.lockCurrentTuple();
                            if (lockedRecord != null) {
                                recordsLocked.add(lockedRecord);
                            }
                        }
                        // check the NdbRecord buffer guard
                        // load blob data into the operation
                        scanOperation.loadBlobValues();
                        return true;
                    } else {
                        // skip this record
                        scanOperation.returnValueBuffer();
                        break;
                    }
                case SCAN_FINISHED:
                    scanOperation.returnValueBuffer();
                    executeIfRecordsLocked();
                    scanOperation.close();
                    return false;
                case CACHE_EMPTY:
                    scanOperation.returnValueBuffer();
                    executeIfRecordsLocked();
                    fetch = true;
                    break;
                default:
                    Utility.throwError(result, ndbScanOperation.getNdbError());
            }
        }
        return true; // this statement is needed to make the compiler happy but it's never executed
    }

}
