/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.ejb.plugins.cmp.jdbc2.schema;

import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCEntityMetaData;
import org.jboss.ejb.plugins.cmp.jdbc2.bridge.JDBCEntityBridge2;
import org.jboss.ejb.plugins.cmp.jdbc2.bridge.JDBCCMRFieldBridge2;
import org.jboss.deployment.DeploymentException;
import org.jboss.tm.TransactionLocal;

import javax.ejb.EJBException;
import javax.transaction.Synchronization;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.Status;
import javax.transaction.Transaction;
import java.sql.SQLException;

/**
 * @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a>
 * @version <tt>$Revision$</tt>
 */
public class Schema
{
   private EntityTable[] entityTables;
   private RelationTable[] relationTables;

   private TransactionLocal localViews = new TransactionLocal()
   {
      protected Object initialValue()
      {
         Transaction tx = getTransaction();

         if(tx == null)
         {
            throw new IllegalStateException("An operation requires an active transaction!");
         }

         Views views = new Views(tx);
         Synchronization sync = new SchemaSynchronization(views);

         try
         {
            tx.registerSynchronization(sync);
         }
         catch(RollbackException e)
         {
            throw new EJBException("Transaction already marked to roll back: " + e.getMessage(), e);
         }
         catch(SystemException e)
         {
            e.printStackTrace();
            throw new IllegalStateException("Failed to register transaction synchronization: " + e.getMessage());
         }

         return views;
      }
   };

   public EntityTable createEntityTable(JDBCEntityMetaData metadata, JDBCEntityBridge2 entity)
      throws DeploymentException
   {
      if(entityTables == null)
      {
         entityTables = new EntityTable[1];
      }
      else
      {
         EntityTable[] tmp = entityTables;
         entityTables = new EntityTable[tmp.length + 1];
         System.arraycopy(tmp, 0, entityTables, 0, tmp.length);
      }

      EntityTable table = new EntityTable(metadata, entity, this, entityTables.length - 1);
      entityTables[entityTables.length - 1] = table;
      return table;
   }

   public RelationTable createRelationTable(JDBCCMRFieldBridge2 leftField, JDBCCMRFieldBridge2 rightField)
      throws DeploymentException
   {
      if(relationTables == null)
      {
         relationTables = new RelationTable[1];
      }
      else
      {
         RelationTable[] tmp = relationTables;
         relationTables = new RelationTable[tmp.length + 1];
         System.arraycopy(tmp, 0, relationTables, 0, tmp.length);
      }

      RelationTable table = new RelationTable(leftField, rightField, this, relationTables.length - 1);
      relationTables[relationTables.length - 1] = table;
      return table;
   }

   public Table.View getView(EntityTable table)
   {
      Views views = (Views) localViews.get();
      Table.View view = views.entityViews[table.getTableId()];
      if(view == null)
      {
         view = table.createView(views.tx);
         views.entityViews[table.getTableId()] = view;
      }
      return view;
   }

   public Table.View getView(RelationTable table)
   {
      Views views = (Views) localViews.get();
      Table.View view = views.relationViews[table.getTableId()];
      if(view == null)
      {
         view = table.createView(views.tx);
         views.relationViews[table.getTableId()] = view;
      }
      return view;
   }

   public void flush()
   {
      Views views = (Views) localViews.get();

      Table.View[] relationViews = views.relationViews;
      if(relationViews != null)
      {
         for(int i = 0; i < relationViews.length; ++i)
         {
            final Table.View view = relationViews[i];
            if(view != null)
            {
               try
               {
                  view.flushDeleted(views);
               }
               catch(SQLException e)
               {
                  throw new EJBException("Failed to delete many-to-many relationships: " + e.getMessage(), e);
               }
            }
         }
      }

      final Table.View[] entityViews = views.entityViews;
      for(int i = 0; i < entityViews.length; ++i)
      {
         Table.View view = entityViews[i];
         if(view != null)
         {
            try
            {
               view.flushDeleted(views);
            }
            catch(SQLException e)
            {
               throw new EJBException("Failed to delete instances: " + e.getMessage(), e);
            }
         }
      }

      for(int i = 0; i < entityViews.length; ++i)
      {
         Table.View view = entityViews[i];
         if(view != null)
         {
            try
            {
               view.flushCreated(views);
            }
            catch(SQLException e)
            {
               throw new EJBException("Failed to create instances: " + e.getMessage(), e);
            }
         }
      }

      for(int i = 0; i < entityViews.length; ++i)
      {
         Table.View view = entityViews[i];
         if(view != null)
         {
            try
            {
               view.flushUpdated();
            }
            catch(SQLException e)
            {
               throw new EJBException("Failed to update instances: " + e.getMessage(), e);
            }
         }
      }

      if(relationViews != null)
      {
         for(int i = 0; i < relationViews.length; ++i)
         {
            final Table.View view = relationViews[i];
            if(view != null)
            {
               try
               {
                  view.flushCreated(views);
               }
               catch(SQLException e)
               {
                  throw new EJBException("Failed to create many-to-many relationships: " + e.getMessage(), e);
               }
            }
         }
      }
   }

   // Inner

   public class Views
   {
      public final Transaction tx;
      public final Table.View[] entityViews;
      public final Table.View[] relationViews;

      public Views(Transaction tx)
      {
         this.tx = tx;
         this.entityViews = new Table.View[entityTables.length];
         this.relationViews = relationTables == null ? null : new Table.View[relationTables.length];
      }
   }

   private class SchemaSynchronization implements Synchronization
   {
      private final Views views;

      public SchemaSynchronization(Views views)
      {
         this.views = views;
      }

      public void beforeCompletion()
      {
         flush();

         for(int i = 0; i < views.entityViews.length; ++i)
         {
            Table.View view = views.entityViews[i];
            if(view != null)
            {
               view.beforeCompletion();
            }
         }
      }

      public void afterCompletion(int status)
      {
         if(status == Status.STATUS_MARKED_ROLLBACK ||
            status == Status.STATUS_ROLLEDBACK ||
            status == Status.STATUS_ROLLING_BACK)
         {
            for(int i = 0; i < views.entityViews.length; ++i)
            {
               Table.View view = views.entityViews[i];
               if(view != null)
               {
                  view.rolledback();
               }
            }
         }
         else
         {
            for(int i = 0; i < views.entityViews.length; ++i)
            {
               Table.View view = views.entityViews[i];
               if(view != null)
               {
                  view.committed();
               }
            }
         }
      }
   }
}
