#
# Copyright (C) 2006-2010  Nexedi SA
#
# 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; either version 2
# of the License, or (at your option) any later version.
#
# 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.

from neo import util
from neo.exception import DatabaseFailure

class DatabaseManager(object):
    """This class only describes an interface for database managers."""

    def __init__(self):
        """
            Initialize the object.
        """
        self._under_transaction = False

    def isUnderTransaction(self):
        return self._under_transaction

    def begin(self):
        """
            Begin a transaction
        """
        if self._under_transaction:
            raise DatabaseFailure('A transaction has already begun')
        self._begin()
        self._under_transaction = True

    def commit(self):
        """
            Commit the current transaction
        """
        if not self._under_transaction:
            raise DatabaseFailure('The transaction has not begun')
        self._commit()
        self._under_transaction = False

    def rollback(self):
        """
            Rollback the current transaction
        """
        self._rollback()
        self._under_transaction = False

    def setup(self, reset = 0):
        """Set up a database. If reset is true, existing data must be
        discarded."""
        raise NotImplementedError

    def _begin(self):
        raise NotImplementedError

    def _commit(self):
        raise NotImplementedError

    def _rollback(self):
        raise NotImplementedError

    def getConfiguration(self, key):
        """
            Return a configuration value, returns None if not found or not set
        """
        raise NotImplementedError

    def setConfiguration(self, key, value):
        """
            Set a configuration value
        """
        if self._under_transaction:
            self._setConfiguration(key, value)
        else:
            self.begin()
            try:
                self._setConfiguration(key, value)
            except:
                self.rollback()
                raise
            self.commit()

    def _setConfiguration(self, key, value):
        raise NotImplementedError

    def getUUID(self):
        """
            Load an UUID from a database.
        """
        return util.bin(self.getConfiguration('uuid'))

    def setUUID(self, uuid):
        """
            Store an UUID into a database.
        """
        self.setConfiguration('uuid', util.dump(uuid))

    def getNumPartitions(self):
        """
            Load the number of partitions from a database.
        """
        n = self.getConfiguration('partitions')
        if n is not None:
            return int(n)

    def setNumPartitions(self, num_partitions):
        """
            Store the number of partitions into a database.
        """
        self.setConfiguration('partitions', num_partitions)

    def getNumReplicas(self):
        """
            Load the number of replicas from a database.
        """
        n = self.getConfiguration('replicas')
        if n is not None:
            return int(n)

    def setNumReplicas(self, num_replicas):
        """
            Store the number of replicas into a database.
        """
        self.setConfiguration('replicas', num_replicas)

    def getName(self):
        """
            Load a name from a database.
        """
        return self.getConfiguration('name')

    def setName(self, name):
        """
            Store a name into a database.
        """
        self.setConfiguration('name', name)

    def getPTID(self):
        """
            Load a Partition Table ID from a database.
        """
        return util.bin(self.getConfiguration('ptid'))

    def setPTID(self, ptid):
        """
            Store a Partition Table ID into a database.
        """
        self.setConfiguration('ptid', util.dump(ptid))

    def getLastOID(self):
        """
            Returns the last OID used
        """
        return util.bin(self.getConfiguration('loid'))

    def setLastOID(self, loid):
        """
            Set the last OID used
        """
        self.setConfiguration('loid', util.dump(loid))

    def getPartitionTable(self):
        """Return a whole partition table as a tuple of rows. Each row
        is again a tuple of an offset (row ID), an UUID of a storage
        node, and a cell state."""
        raise NotImplementedError

    def getLastTID(self, all = True):
        """Return the last TID in a database. If all is true,
        unfinished transactions must be taken account into. If there
        is no TID in the database, return None."""
        raise NotImplementedError

    def getUnfinishedTIDList(self):
        """Return a list of unfinished transaction's IDs."""
        raise NotImplementedError

    def objectPresent(self, oid, tid, all = True):
        """Return true iff an object specified by a given pair of an
        object ID and a transaction ID is present in a database.
        Otherwise, return false. If all is true, the object must be
        searched from unfinished transactions as well."""
        raise NotImplementedError

    def getObject(self, oid, tid = None, before_tid = None):
        """Return a tuple of a serial, next serial, a compression
        specification, a checksum, and object data, if a given object
        ID is present. Otherwise, return None. If tid is None and
        before_tid is None, the latest revision is taken. If tid is
        specified, the given revision is taken. If tid is not specified,
        but before_tid is specified, the latest revision before the
        given revision is taken. The next serial is a serial right after
        before_tid, if specified. Otherwise, it is None."""
        raise NotImplementedError

    def changePartitionTable(self, ptid, cell_list):
        """Change a part of a partition table. The list of cells is
        a tuple of tuples, each of which consists of an offset (row ID),
        an UUID of a storage node, and a cell state. The Partition
        Table ID must be stored as well."""
        raise NotImplementedError

    def setPartitionTable(self, ptid, cell_list):
        """Set a whole partition table. The semantics is the same as
        changePartitionTable, except that existing data must be
        thrown away."""
        raise NotImplementedError

    def dropPartitions(self, num_partitions, offset_list):
        """ Drop any data of non-assigned partitions for a given UUID """
        raise NotImplementedError('this method must be overriden')

    def dropUnfinishedData(self):
        """Drop any unfinished data from a database."""
        raise NotImplementedError

    def storeTransaction(self, tid, object_list, transaction, temporary = True):
        """Store a transaction temporarily, if temporary is true. Note
        that this transaction is not finished yet. The list of objects
        contains tuples, each of which consists of an object ID,
        a compression specification, a checksum and object data.
        The transaction is either None or a tuple of the list of OIDs,
        user information, a description, extension information and transaction
        pack state (True for packed)."""
        raise NotImplementedError

    def getTransactionUndoData(self, tid, undone_tid,
            getObjectFromTransaction):
        """Undo transaction with "undone_tid" tid. "tid" is the tid of the
        transaction in which the undo happens.
        getObjectFromTransaction is a callback allowing to find object data
        stored to this storage in the same transaction (it is useful for
        example when undoing twice in the same transaction).
        """
        raise NotImplementedError

    def finishTransaction(self, tid):
        """Finish a transaction specified by a given ID, by moving
        temporarily data to a finished area."""
        raise NotImplementedError

    def deleteTransaction(self, tid, all = False):
        """Delete a transaction specified by a given ID from a temporarily
        area. If all is true, it must be deleted even from a finished
        area."""
        raise NotImplementedError

    def getTransaction(self, tid, all = False):
        """Return a tuple of the list of OIDs, user information,
        a description, and extension information, for a given transaction
        ID. If there is no such transaction ID in a database, return None.
        If all is true, the transaction must be searched from a temporary
        area as well."""
        raise NotImplementedError

    def getOIDList(self, offset, length, num_partitions, partition_list):
        """Return a list of OIDs in descending order from an offset,
        at most the specified length. The list of partitions are passed
        to filter out non-applicable TIDs."""
        raise NotImplementedError

    def getObjectHistory(self, oid, offset = 0, length = 1):
        """Return a list of serials and sizes for a given object ID.
        The length specifies the maximum size of such a list. Result starts
        with latest serial, and the list must be sorted in descending order.
        If there is no such object ID in a database, return None."""
        raise NotImplementedError

    def getTIDList(self, offset, length, num_partitions, partition_list):
        """Return a list of TIDs in ascending order from an offset,
        at most the specified length. The list of partitions are passed
        to filter out non-applicable TIDs."""
        raise NotImplementedError

    def getReplicationTIDList(self, offset, length, num_partitions,
        partition_list):
        """Return a list of TIDs in descending order from an offset,
        at most the specified length. The list of partitions are passed
        to filter out non-applicable TIDs."""
        raise NotImplementedError

    def getTIDListPresent(self, tid_list):
        """Return a list of TIDs which are present in a database among
        the given list."""
        raise NotImplementedError

    def getSerialListPresent(self, oid, serial_list):
        """Return a list of serials which are present in a database among
        the given list."""
        raise NotImplementedError