/*
 * Copyright (c) 2002-2009 "Neo Technology,"
 *     Network Engine for Objects in Lund AB [http://neotechnology.com]
 *
 * This file is part of Neo4j.
 * 
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.neo4j.kernel.impl.core;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.transaction.TransactionManager;

import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.TransactionFailureException;
import org.neo4j.kernel.impl.nioneo.store.RelationshipTypeData;
import org.neo4j.kernel.impl.persistence.IdGenerator;
import org.neo4j.kernel.impl.persistence.PersistenceManager;
import org.neo4j.kernel.impl.util.ArrayMap;

class RelationshipTypeHolder
{
    private ArrayMap<String,Integer> relTypes = 
        new ArrayMap<String,Integer>( 5, true, true );
    private Map<Integer,String> relTranslation = 
        new ConcurrentHashMap<Integer,String>();

    private final TransactionManager transactionManager;
    private final PersistenceManager persistenceManager;
    private final IdGenerator idGenerator;

    RelationshipTypeHolder( TransactionManager transactionManager,
        PersistenceManager persistenceManager, IdGenerator idGenerator )
    {
        this.transactionManager = transactionManager;
        this.persistenceManager = persistenceManager;
        this.idGenerator = idGenerator;
    }

    void addRawRelationshipTypes( RelationshipTypeData[] types )
    {
        for ( int i = 0; i < types.length; i++ )
        {
            relTypes.put( types[i].getName(), types[i].getId() );
            relTranslation.put( types[i].getId(), types[i].getName() );
        }
    }
    
    void addRawRelationshipType( RelationshipTypeData type )
    {
        relTypes.put( type.getName(), type.getId() );
        relTranslation.put( type.getId(), type.getName() );
    }

    public RelationshipType addValidRelationshipType( String name,
        boolean create )
    {
        if ( relTypes.get( name ) == null )
        {
            if ( !create )
            {
                return null;
            }
            int id = createRelationshipType( name );
            relTranslation.put( id, name );
        }
        else
        {
            relTranslation.put( relTypes.get( name ), name );
        }
        return new RelationshipTypeImpl( name );
    }

    boolean isValidRelationshipType( RelationshipType type )
    {
        return relTypes.get( type.name() ) != null;
    }

    private static class RelationshipTypeImpl implements RelationshipType
    {
        private String name;

        RelationshipTypeImpl( String name )
        {
            assert name != null;
            this.name = name;
        }

        public String name()
        {
            return name;
        }

        public String toString()
        {
            return name;
        }

        public boolean equals( Object o )
        {
            if ( !(o instanceof RelationshipType) )
            {
                return false;
            }
            return name.equals( ((RelationshipType) o).name() );
        }

        public int hashCode()
        {
            return name.hashCode();
        }
    }

    // TODO: this should be fixed to run in same thread
    private class RelTypeCreater extends Thread
    {
        private boolean success = false;
        private String name;
        private int id = -1;

        RelTypeCreater( String name )
        {
            super();
            this.name = name;
        }

        synchronized boolean succeded()
        {
            return success;
        }

        synchronized int getRelTypeId()
        {
            return id;
        }

        public synchronized void run()
        {
            try
            {
                transactionManager.begin();
                id = idGenerator.nextId( RelationshipType.class );
                persistenceManager.createRelationshipType( id, name );
                transactionManager.commit();
                success = true;
            }
            catch ( Throwable t )
            {
                t.printStackTrace();
                try
                {
                    transactionManager.rollback();
                }
                catch ( Throwable tt )
                {
                    tt.printStackTrace();
                }
            }
            finally
            {
                this.notify();
            }
        }
    }

    private synchronized int createRelationshipType( String name )
    {
        Integer id = relTypes.get( name );
        if ( id != null )
        {
            return id;
        }
        RelTypeCreater createrThread = new RelTypeCreater( name );
        synchronized ( createrThread )
        {
            createrThread.start();
            while ( createrThread.isAlive() )
            {
                try
                {
                    createrThread.wait( 50 );
                }
                catch ( InterruptedException e )
                { 
                    Thread.interrupted();
                }
            }
        }
        if ( createrThread.succeded() )
        {
            addRelType( name, createrThread.getRelTypeId() );
            return createrThread.getRelTypeId();
        }
        throw new TransactionFailureException( 
            "Unable to create relationship type " + name );
    }

    void addRelType( String name, Integer id )
    {
        relTypes.put( name, id );
    }

    void removeRelType( String name )
    {
        relTypes.remove( name );
    }

    void removeRelType( int id )
    {
        String name = relTranslation.remove( id );
        if ( name != null )
        {
            relTypes.remove( name );
        }
    }

    int getIdFor( RelationshipType type )
    {
        return relTypes.get( type.name() );
    }

    RelationshipType getRelationshipType( int id )
    {
        String name = relTranslation.get( id );
        if ( name != null )
        {
            return new RelationshipTypeImpl( name );
        }
        return null;
    }

    public Iterable<RelationshipType> getRelationshipTypes()
    {
        List<RelationshipType> relTypeList = new ArrayList<RelationshipType>();
        for ( String name : relTypes.keySet() )
        {
            relTypeList.add( new RelationshipTypeImpl( name ) );
        }
        return relTypeList;
    }

    void clear()
    {
        relTypes = new ArrayMap<String,Integer>();
        relTranslation = new HashMap<Integer,String>();
    }
}