package com.dbxml.db.common.btree;

/*
 * dbXML - Native XML Database
 * Copyright (C) 1999-2004  The dbXML Group, L.L.C.
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Id: BTreeFiler.java,v 1.6 2004/07/20 20:19:33 bradford Exp $
 */

import com.dbxml.db.core.Collection;
import com.dbxml.db.core.DBException;
import com.dbxml.db.core.FaultCodes;
import com.dbxml.db.core.data.Key;
import com.dbxml.db.core.data.Record;
import com.dbxml.db.core.data.RecordMetaData;
import com.dbxml.db.core.data.RecordSet;
import com.dbxml.db.core.data.Value;
import com.dbxml.db.core.filer.Filer;
import com.dbxml.db.core.filer.FilerException;
import com.dbxml.db.core.transaction.Transaction;
import com.dbxml.util.Configuration;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

/**
 * BTreeFiler is a Filer implementation based on the BTree class.
 */

public final class BTreeFiler extends BTree implements Filer {
   protected static final byte RECORD = 20;

   private static final String PAGESIZE = "pagesize";

   private Collection collection;
   private BTreeFilerHeader fileHeader;

   public BTreeFiler(boolean transactionSupported) {
      super(false);
      setTransactionSupported(transactionSupported);
      fileHeader = (BTreeFilerHeader)getFileHeader();
   }

   public BTreeFiler(File f, boolean transactionSupport) {
      this(transactionSupport);
      setFile(f);
   }

   public BTreeFiler(File f) {
      this(f, true);
   }

   public BTreeFiler() {
      this(true);
   }

   public void setLocation(String location) {
      setFile(new File(collection.getCollectionRoot(), location + ".tbl"));
   }

   public String getName() {
      return "BTreeFiler";
   }

   public boolean create() throws DBException {
      Configuration cfg = getConfig();
      if ( cfg != null )
         fileHeader.setPageSize(cfg.getIntAttribute(PAGESIZE, fileHeader.getPageSize()));
      return super.create();
   }

   public void setCollection(Collection collection) {
      this.collection = collection;
      setLocation(collection.getName());
   }

   private RecordMetaData buildMetaData(Page p) {
      BTreeFilerPageHeader ph = (BTreeFilerPageHeader)p.getPageHeader();
      HashMap meta = new HashMap(2);
      meta.put(RecordMetaData.CREATED, new Long(ph.getCreated()));
      meta.put(RecordMetaData.MODIFIED, new Long(ph.getModified()));
      return new RecordMetaData(meta);
   }

   public RecordMetaData getRecordMetaData(Transaction tx, Key key) throws DBException {
      checkOpened();
      try {
         long pos = findValue(tx, key);
         Page page = getPage(tx, pos);
         return buildMetaData(page);
      }
      catch ( BTreeNotFoundException b ) {
      }
      catch ( BTreeException b ) {
         throw b;
      }
      catch ( Exception e ) {
         e.printStackTrace(System.err);
      }
      return null;
   }

   public Record readRecord(Transaction tx, Key key) throws DBException {
      checkOpened();
      try {
         long pos = findValue(tx, key);
         Page page = getPage(tx, pos);
         Value v = readValue(tx, page);
         RecordMetaData md = buildMetaData(page);
         return new Record(key, v, md);
      }
      catch ( BTreeNotFoundException b ) {
      }
      catch ( BTreeException b ) {
         throw b;
      }
      catch ( Exception e ) {
         e.printStackTrace(System.err);
      }
      return null;
   }

   public boolean writeRecord(Transaction tx, Key key, Value value) throws DBException {
      checkOpened();
      try {
         Page p;
         try {
            long pos = findValue(tx, key);
            p = getPage(tx, pos);
         }
         catch ( BTreeNotFoundException b ) {
            p = getFreePage(tx);
            addValue(tx, key, p.getPageNum());
            fileHeader.incRecordCount();
         }
         BTreeFilerPageHeader ph = (BTreeFilerPageHeader)p.getPageHeader();

         long t = System.currentTimeMillis();
         if ( ph.getStatus() == UNUSED )
            ph.setCreated(t);

         ph.setModified(t);
         ph.setStatus(RECORD);

         writeValue(tx, p, value);
      }
      catch ( DBException d ) {
         throw d;
      }
      catch ( Exception e ) {
         e.printStackTrace(System.err);
      }
      return true;
   }

   public boolean deleteRecord(Transaction tx, Key key) throws DBException {
      checkOpened();
      try {
         long pos = findValue(tx, key);
         Page p = getPage(tx, pos);

         removeValue(tx, key);
         unlinkPages(tx, p.getPageNum());

         fileHeader.decRecordCount();
         return true;
      }
      catch ( BTreeNotFoundException b ) {
      }
      catch ( BTreeException b ) {
         throw b;
      }
      catch ( Exception e ) {
         e.printStackTrace(System.err);
      }
      return false;
   }

   public long getRecordCount(Transaction tx) throws DBException {
      checkOpened();
      return fileHeader.getRecordCount();
   }

   public RecordSet getRecordSet(Transaction tx) throws DBException {
      checkOpened();
      return new BTreeFilerRecordSet(tx);
   }


   /**
    * BTreeFilerRecordSet
    */

   private class BTreeFilerRecordSet implements RecordSet, BTreeCallback {
      private Transaction tx;
      private List keys;
      private Iterator enum;

      public BTreeFilerRecordSet(Transaction tx) throws DBException {
         this.tx = tx;
         try {
            keys = new ArrayList((int)fileHeader.getRecordCount());
            query(tx, null, this);
            enum = keys.iterator();
         }
         catch ( IOException e ) {
            throw new FilerException(FaultCodes.GEN_CRITICAL_ERROR, "Error generating RecordSet", e);
         }
      }

      public synchronized void indexInfo(Value value, Value extra) {
         keys.add(new Key(value));
      }

      public synchronized Key getNextKey() {
         return (Key)enum.next();
      }

      public synchronized Record getNextRecord() throws DBException {
         return readRecord(tx, (Key)enum.next());
      }

      public synchronized Value getNextValue() throws DBException {
         return getNextRecord().getValue();
      }

      public synchronized boolean hasMoreRecords() {
         return enum.hasNext();
      }
   }

   ////////////////////////////////////////////////////////////////////

   public FileHeader createFileHeader() {
      return new BTreeFilerHeader();
   }

   public FileHeader createFileHeader(boolean read) throws IOException {
      return new BTreeFilerHeader(read);
   }

   public FileHeader createFileHeader(long pageCount) {
      return new BTreeFilerHeader(pageCount);
   }

   public FileHeader createFileHeader(long pageCount, int pageSize) {
      return new BTreeFilerHeader(pageCount, pageSize);
   }

   public PageHeader createPageHeader() {
      return new BTreeFilerPageHeader();
   }


   /**
    * BTreeFilerHeader
    */

   private final class BTreeFilerHeader extends BTreeFileHeader {
      private long totalBytes = 0;

      public BTreeFilerHeader() {
      }

      public BTreeFilerHeader(long pageCount) {
         super(pageCount);
      }

      public BTreeFilerHeader(long pageCount, int pageSize) {
         super(pageCount, pageSize);
      }

      public BTreeFilerHeader(boolean read) throws IOException {
         super(read);
      }

      public synchronized void read(RandomAccessFile raf) throws IOException {
         super.read(raf);
         totalBytes = raf.readLong();
      }

      public synchronized void write(RandomAccessFile raf) throws IOException {
         super.write(raf);
         raf.writeLong(totalBytes);
      }

      /** The total number of bytes in use by the file */
      public synchronized void setTotalBytes(long totalBytes) {
         this.totalBytes = totalBytes;
         setDirty();
      }

      /** The total number of bytes in use by the file */
      public synchronized long getTotalBytes() {
         return totalBytes;
      }
   }

   /**
    * BTreeFilerPageHeader
    */

   private final class BTreeFilerPageHeader extends BTreePageHeader {
      private long created = 0;
      private long modified = 0;

      public BTreeFilerPageHeader() {
      }

      public BTreeFilerPageHeader(ByteBuffer buf) throws IOException {
         super(buf);
      }

      public synchronized void read(ByteBuffer buf) throws IOException {
         super.read(buf);

         if ( getStatus() == UNUSED )
            return;

         created = buf.getLong();
         modified = buf.getLong();
      }

      public synchronized void write(ByteBuffer buf) throws IOException {
         super.write(buf);
         buf.putLong(created);
         buf.putLong(modified);
      }

      public synchronized void setRecordLen(int recordLen) {
         fileHeader.setTotalBytes((fileHeader.totalBytes - getRecordLen()) + recordLen);
         super.setRecordLen(recordLen);
      }

      /** UNIX-time when this record was created */
      public synchronized void setCreated(long created) {
         this.created = created;
         setDirty();
      }

      /** UNIX-time when this record was created */
      public synchronized long getCreated() {
         return created;
      }

      /** UNIX-time when this record was last modified */
      public synchronized void setModified(long modified) {
         this.modified = modified;
         setDirty();
      }

      /** UNIX-time when this record was last modified */
      public synchronized long getModified() {
         return modified;
      }
   }
}


