package org.apache.archiva.metadata.repository.cassandra;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import static org.apache.archiva.metadata.repository.cassandra.model.ColumnNames.*;

import me.prettyprint.cassandra.model.BasicColumnDefinition;
import me.prettyprint.cassandra.model.ConfigurableConsistencyLevel;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.cassandra.service.CassandraHostConfigurator;
import me.prettyprint.cassandra.service.ThriftKsDef;
import me.prettyprint.hector.api.Cluster;
import me.prettyprint.hector.api.HConsistencyLevel;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.ddl.ColumnFamilyDefinition;
import me.prettyprint.hector.api.ddl.ColumnIndexType;
import me.prettyprint.hector.api.ddl.ComparatorType;
import me.prettyprint.hector.api.factory.HFactory;
import org.apache.archiva.metadata.repository.RepositorySessionFactoryBean;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;

/**
 * FIXME make all configuration not hardcoded :-)
 *
 * @author Olivier Lamy
 * @since 2.0.0
 */
@Service("archivaEntityManagerFactory#cassandra")
public class DefaultCassandraArchivaManager
    implements CassandraArchivaManager
{

    private Logger logger = LoggerFactory.getLogger( getClass() );

    @Inject
    private ApplicationContext applicationContext;

    private static final String CLUSTER_NAME = "archiva";

    private static final String KEYSPACE_NAME = "ArchivaKeySpace";

    private boolean started;

    private Cluster cluster;

    private Keyspace keyspace;

    // configurable???
    private String repositoryFamilyName = "repository";

    private String namespaceFamilyName = "namespace";

    private String projectFamilyName = PROJECT.toString();

    private String projectVersionMetadataFamilyName = "projectversionmetadata";

    private String artifactMetadataFamilyName = "artifactmetadata";

    private String metadataFacetFamilyName = "metadatafacet";

    private String mailingListFamilyName = "mailinglist";

    private String licenseFamilyName = "license";

    private String dependencyFamilyName = "dependency";

    @Value("${cassandra.host}")
    private String cassandraHost;

    @Value("${cassandra.port}")
    private String cassandraPort;

    @Value("${cassandra.maxActive}")
    private int maxActive;

    @Value("${cassandra.readConsistencyLevel}")
    private String readConsistencyLevel;

    @Value("${cassandra.writeConsistencyLevel}")
    private String writeConsistencyLevel;

    @Value("${cassandra.replicationFactor}")
    private int replicationFactor;

    @Value("${cassandra.keyspace.name}")
    private String keyspaceName;

    @Value("${cassandra.cluster.name}")
    private String clusterName;

    @Inject
    private RepositorySessionFactoryBean repositorySessionFactoryBean;

    @PostConstruct
    public void initialize()
    {
        // skip initialisation if not cassandra
        if ( !StringUtils.equals( repositorySessionFactoryBean.getId(), "cassandra" ) )
        {
            return;
        }
        final CassandraHostConfigurator configurator =
            new CassandraHostConfigurator( cassandraHost + ":" + cassandraPort );
        configurator.setMaxActive( maxActive );
        //configurator.setCassandraThriftSocketTimeout(  );

        cluster = HFactory.getOrCreateCluster( clusterName, configurator );

        final ConfigurableConsistencyLevel consistencyLevelPolicy = new ConfigurableConsistencyLevel();
        consistencyLevelPolicy.setDefaultReadConsistencyLevel( HConsistencyLevel.valueOf( readConsistencyLevel ) );
        consistencyLevelPolicy.setDefaultWriteConsistencyLevel( HConsistencyLevel.valueOf( writeConsistencyLevel ) );
        keyspace = HFactory.createKeyspace( keyspaceName, cluster, consistencyLevelPolicy );

        List<ColumnFamilyDefinition> cfds = new ArrayList<>();

        // namespace table
        {

            final ColumnFamilyDefinition namespace =
                HFactory.createColumnFamilyDefinition( keyspace.getKeyspaceName(), //
                                                       getNamespaceFamilyName(), //
                                                       ComparatorType.UTF8TYPE );
            cfds.add( namespace );

            // creating indexes for cql query

            BasicColumnDefinition nameColumn = new BasicColumnDefinition();
            nameColumn.setName( StringSerializer.get().toByteBuffer( NAME.toString() ) );
            nameColumn.setIndexName( NAME.toString() );
            nameColumn.setIndexType( ColumnIndexType.KEYS );
            nameColumn.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            namespace.addColumnDefinition( nameColumn );

            BasicColumnDefinition repositoryIdColumn = new BasicColumnDefinition();
            repositoryIdColumn.setName( StringSerializer.get().toByteBuffer( REPOSITORY_NAME.toString() ) );
            repositoryIdColumn.setIndexName( REPOSITORY_NAME.toString() );
            repositoryIdColumn.setIndexType( ColumnIndexType.KEYS );
            repositoryIdColumn.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            namespace.addColumnDefinition( repositoryIdColumn );
        }

        // repository table
        {
            final ColumnFamilyDefinition repository =
                HFactory.createColumnFamilyDefinition( keyspace.getKeyspaceName(), //
                                                       getRepositoryFamilyName(), //
                                                       ComparatorType.UTF8TYPE );

            cfds.add( repository );

            BasicColumnDefinition nameColumn = new BasicColumnDefinition();
            nameColumn.setName( StringSerializer.get().toByteBuffer( REPOSITORY_NAME.toString() ) );
            nameColumn.setIndexName( REPOSITORY_NAME.toString() );
            nameColumn.setIndexType( ColumnIndexType.KEYS );
            nameColumn.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            repository.addColumnDefinition( nameColumn );
        }

        // project table
        {

            final ColumnFamilyDefinition project = HFactory.createColumnFamilyDefinition( keyspace.getKeyspaceName(), //
                                                                                          getProjectFamilyName(), //
                                                                                          ComparatorType.UTF8TYPE );
            cfds.add( project );

            // creating indexes for cql query

            BasicColumnDefinition projectIdColumn = new BasicColumnDefinition();
            projectIdColumn.setName( StringSerializer.get().toByteBuffer( PROJECT_ID.toString() ) );
            projectIdColumn.setIndexName( PROJECT_ID.toString() );
            projectIdColumn.setIndexType( ColumnIndexType.KEYS );
            projectIdColumn.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            project.addColumnDefinition( projectIdColumn );

            BasicColumnDefinition repositoryIdColumn = new BasicColumnDefinition();
            repositoryIdColumn.setName( StringSerializer.get().toByteBuffer( REPOSITORY_NAME.toString() ) );
            repositoryIdColumn.setIndexName( REPOSITORY_NAME.toString() );
            repositoryIdColumn.setIndexType( ColumnIndexType.KEYS );
            repositoryIdColumn.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            project.addColumnDefinition( repositoryIdColumn );

            BasicColumnDefinition namespaceIdColumn = new BasicColumnDefinition();
            namespaceIdColumn.setName( StringSerializer.get().toByteBuffer( NAMESPACE_ID.toString() ) );
            namespaceIdColumn.setIndexName( NAMESPACE_ID.toString() );
            namespaceIdColumn.setIndexType( ColumnIndexType.KEYS );
            namespaceIdColumn.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            project.addColumnDefinition( namespaceIdColumn );
        }

        //projectversionmetadatamodel
        {

            final ColumnFamilyDefinition projectVersionMetadataModel =
                HFactory.createColumnFamilyDefinition( keyspace.getKeyspaceName(), //
                                                       getProjectVersionMetadataFamilyName(), //
                                                       ComparatorType.UTF8TYPE );
            cfds.add( projectVersionMetadataModel );

            // creating indexes for cql query

            BasicColumnDefinition namespaceIdColumn = new BasicColumnDefinition();
            namespaceIdColumn.setName( StringSerializer.get().toByteBuffer( NAMESPACE_ID.toString() ) );
            namespaceIdColumn.setIndexName( NAMESPACE_ID.toString() );
            namespaceIdColumn.setIndexType( ColumnIndexType.KEYS );
            namespaceIdColumn.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            projectVersionMetadataModel.addColumnDefinition( namespaceIdColumn );

            BasicColumnDefinition repositoryNameColumn = new BasicColumnDefinition();
            repositoryNameColumn.setName( StringSerializer.get().toByteBuffer( REPOSITORY_NAME.toString() ) );
            repositoryNameColumn.setIndexName( REPOSITORY_NAME.toString() );
            repositoryNameColumn.setIndexType( ColumnIndexType.KEYS );
            repositoryNameColumn.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            projectVersionMetadataModel.addColumnDefinition( repositoryNameColumn );

            BasicColumnDefinition idColumn = new BasicColumnDefinition();
            idColumn.setName( StringSerializer.get().toByteBuffer( ID.toString() ) );
            idColumn.setIndexName( ID.toString() );
            idColumn.setIndexType( ColumnIndexType.KEYS );
            idColumn.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            projectVersionMetadataModel.addColumnDefinition( idColumn );

            BasicColumnDefinition projectIdColumn = new BasicColumnDefinition();
            projectIdColumn.setName( StringSerializer.get().toByteBuffer( PROJECT_ID.toString() ) );
            projectIdColumn.setIndexName( PROJECT_ID.toString() );
            projectIdColumn.setIndexType( ColumnIndexType.KEYS );
            projectIdColumn.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            projectVersionMetadataModel.addColumnDefinition( projectIdColumn );

        }

        // artifactmetadatamodel table
        {

            final ColumnFamilyDefinition artifactMetadataModel =
                HFactory.createColumnFamilyDefinition( keyspace.getKeyspaceName(), //
                                                       getArtifactMetadataFamilyName(), //
                                                       ComparatorType.UTF8TYPE );
            cfds.add( artifactMetadataModel );

            // creating indexes for cql query

            BasicColumnDefinition idColumn = new BasicColumnDefinition();
            idColumn.setName( StringSerializer.get().toByteBuffer( ID.toString() ) );
            idColumn.setIndexName( ID.toString() );
            idColumn.setIndexType( ColumnIndexType.KEYS );
            idColumn.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            artifactMetadataModel.addColumnDefinition( idColumn );

            BasicColumnDefinition repositoryNameColumn = new BasicColumnDefinition();
            repositoryNameColumn.setName( StringSerializer.get().toByteBuffer( REPOSITORY_NAME.toString() ) );
            repositoryNameColumn.setIndexName( REPOSITORY_NAME.toString() );
            repositoryNameColumn.setIndexType( ColumnIndexType.KEYS );
            repositoryNameColumn.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            artifactMetadataModel.addColumnDefinition( repositoryNameColumn );

            BasicColumnDefinition namespaceIdColumn = new BasicColumnDefinition();
            namespaceIdColumn.setName( StringSerializer.get().toByteBuffer( NAMESPACE_ID.toString() ) );
            namespaceIdColumn.setIndexName( NAMESPACE_ID.toString() );
            namespaceIdColumn.setIndexType( ColumnIndexType.KEYS );
            namespaceIdColumn.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            artifactMetadataModel.addColumnDefinition( namespaceIdColumn );

            BasicColumnDefinition projectColumn = new BasicColumnDefinition();
            projectColumn.setName( StringSerializer.get().toByteBuffer( PROJECT.toString() ) );
            projectColumn.setIndexName( PROJECT.toString() );
            projectColumn.setIndexType( ColumnIndexType.KEYS );
            projectColumn.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            artifactMetadataModel.addColumnDefinition( projectColumn );

            BasicColumnDefinition projectVersionColumn = new BasicColumnDefinition();
            projectVersionColumn.setName( StringSerializer.get().toByteBuffer( PROJECT_VERSION.toString() ) );
            projectVersionColumn.setIndexName( PROJECT_VERSION.toString() );
            projectVersionColumn.setIndexType( ColumnIndexType.KEYS );
            projectVersionColumn.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            artifactMetadataModel.addColumnDefinition( projectVersionColumn );

            BasicColumnDefinition versionColumn = new BasicColumnDefinition();
            versionColumn.setName( StringSerializer.get().toByteBuffer( VERSION.toString() ) );
            versionColumn.setIndexName( VERSION.toString() );
            versionColumn.setIndexType( ColumnIndexType.KEYS );
            versionColumn.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            artifactMetadataModel.addColumnDefinition( versionColumn );

            BasicColumnDefinition whenGatheredColumn = new BasicColumnDefinition();
            whenGatheredColumn.setName( StringSerializer.get().toByteBuffer( WHEN_GATHERED.toString() ) );
            whenGatheredColumn.setIndexName( WHEN_GATHERED.toString() );
            whenGatheredColumn.setIndexType( ColumnIndexType.KEYS );
            whenGatheredColumn.setValidationClass( ComparatorType.LONGTYPE.getClassName() );
            artifactMetadataModel.addColumnDefinition( whenGatheredColumn );

            BasicColumnDefinition sha1Column = new BasicColumnDefinition();
            sha1Column.setName( StringSerializer.get().toByteBuffer( SHA1.toString() ) );
            sha1Column.setIndexName( SHA1.toString() );
            sha1Column.setIndexType( ColumnIndexType.KEYS );
            sha1Column.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            artifactMetadataModel.addColumnDefinition( sha1Column );

            BasicColumnDefinition md5Column = new BasicColumnDefinition();
            md5Column.setName( StringSerializer.get().toByteBuffer( MD5.toString() ) );
            md5Column.setIndexName( MD5.toString() );
            md5Column.setIndexType( ColumnIndexType.KEYS );
            md5Column.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            artifactMetadataModel.addColumnDefinition( md5Column );


        }

        // metadatafacetmodel table
        {
            final ColumnFamilyDefinition metadataFacetModel =
                HFactory.createColumnFamilyDefinition( keyspace.getKeyspaceName(), //
                                                       getMetadataFacetFamilyName(), //
                                                       ComparatorType.UTF8TYPE );
            cfds.add( metadataFacetModel );

            // creating indexes for cql query

            BasicColumnDefinition facetIdColumn = new BasicColumnDefinition();
            facetIdColumn.setName( StringSerializer.get().toByteBuffer( FACET_ID.toString() ) );
            facetIdColumn.setIndexName( FACET_ID.toString() );
            facetIdColumn.setIndexType( ColumnIndexType.KEYS );
            facetIdColumn.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            metadataFacetModel.addColumnDefinition( facetIdColumn );

            BasicColumnDefinition repositoryNameColumn = new BasicColumnDefinition();
            repositoryNameColumn.setName( StringSerializer.get().toByteBuffer( REPOSITORY_NAME.toString() ) );
            repositoryNameColumn.setIndexName( REPOSITORY_NAME.toString() );
            repositoryNameColumn.setIndexType( ColumnIndexType.KEYS );
            repositoryNameColumn.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            metadataFacetModel.addColumnDefinition( repositoryNameColumn );

            BasicColumnDefinition nameColumn = new BasicColumnDefinition();
            nameColumn.setName( StringSerializer.get().toByteBuffer( NAME.toString() ) );
            nameColumn.setIndexName( NAME.toString() );
            nameColumn.setIndexType( ColumnIndexType.KEYS );
            nameColumn.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            metadataFacetModel.addColumnDefinition( nameColumn );

            BasicColumnDefinition namespaceColumn = new BasicColumnDefinition();
            namespaceColumn.setName( StringSerializer.get().toByteBuffer( NAMESPACE_ID.toString() ) );
            namespaceColumn.setIndexName( NAMESPACE_ID.toString() );
            namespaceColumn.setIndexType( ColumnIndexType.KEYS );
            namespaceColumn.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            metadataFacetModel.addColumnDefinition( namespaceColumn );

            BasicColumnDefinition projectIdColumn = new BasicColumnDefinition();
            projectIdColumn.setName( StringSerializer.get().toByteBuffer( PROJECT_ID.toString() ) );
            projectIdColumn.setIndexName( PROJECT_ID.toString() );
            projectIdColumn.setIndexType( ColumnIndexType.KEYS );
            projectIdColumn.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            metadataFacetModel.addColumnDefinition( projectIdColumn );

            BasicColumnDefinition projectVersionColumn = new BasicColumnDefinition();
            projectVersionColumn.setName( StringSerializer.get().toByteBuffer( PROJECT_VERSION.toString() ) );
            projectVersionColumn.setIndexName( PROJECT_VERSION.toString() );
            projectVersionColumn.setIndexType( ColumnIndexType.KEYS );
            projectVersionColumn.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            metadataFacetModel.addColumnDefinition( projectVersionColumn );

        }

        // mailinglist table
        {
            final ColumnFamilyDefinition mailingListCf =
                HFactory.createColumnFamilyDefinition( keyspace.getKeyspaceName(), //
                                                       getMailingListFamilyName(), //
                                                       ComparatorType.UTF8TYPE );

            BasicColumnDefinition projectVersionMetadataModel_key = new BasicColumnDefinition();
            projectVersionMetadataModel_key.setName( StringSerializer.get().toByteBuffer( "projectVersionMetadataModel.key" ) );
            projectVersionMetadataModel_key.setIndexName( "projectVersionMetadataModel_key" );
            projectVersionMetadataModel_key.setIndexType( ColumnIndexType.KEYS );
            projectVersionMetadataModel_key.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            mailingListCf.addColumnDefinition( projectVersionMetadataModel_key );

            cfds.add( mailingListCf );

            // creating indexes for cql query

        }

        // license table
        {
            final ColumnFamilyDefinition licenseCf =
                HFactory.createColumnFamilyDefinition( keyspace.getKeyspaceName(), //
                                                       getLicenseFamilyName(), //
                                                       ComparatorType.UTF8TYPE );

            BasicColumnDefinition projectVersionMetadataModel_key = new BasicColumnDefinition();
            projectVersionMetadataModel_key.setName( StringSerializer.get().toByteBuffer( "projectVersionMetadataModel.key" ) );
            projectVersionMetadataModel_key.setIndexName( "projectVersionMetadataModel_key" );
            projectVersionMetadataModel_key.setIndexType( ColumnIndexType.KEYS );
            projectVersionMetadataModel_key.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            licenseCf.addColumnDefinition( projectVersionMetadataModel_key );

            cfds.add( licenseCf );

            // creating indexes for cql query

        }

        // dependency table
        {
            final ColumnFamilyDefinition dependencyCf =
                HFactory.createColumnFamilyDefinition( keyspace.getKeyspaceName(), //
                                                       getDependencyFamilyName(), //
                                                       ComparatorType.UTF8TYPE );
            cfds.add( dependencyCf );

            // creating indexes for cql query

            BasicColumnDefinition groupIdColumn = new BasicColumnDefinition();
            groupIdColumn.setName( StringSerializer.get().toByteBuffer( GROUP_ID.toString() ) );
            groupIdColumn.setIndexName( "groupIdIdx" );
            groupIdColumn.setIndexType( ColumnIndexType.KEYS );
            groupIdColumn.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            dependencyCf.addColumnDefinition( groupIdColumn );

            BasicColumnDefinition projectVersionMetadataModel_key = new BasicColumnDefinition();
            projectVersionMetadataModel_key.setName( StringSerializer.get().toByteBuffer( "projectVersionMetadataModel.key" ) );
            projectVersionMetadataModel_key.setIndexName( "projectVersionMetadataModel_key" );
            projectVersionMetadataModel_key.setIndexType( ColumnIndexType.KEYS );
            projectVersionMetadataModel_key.setValidationClass( ComparatorType.UTF8TYPE.getClassName() );
            dependencyCf.addColumnDefinition( projectVersionMetadataModel_key );

        }

        // TODO take care of update new table!!
        { // ensure keyspace exists, here if the keyspace doesn't exist we suppose nothing exist
            if ( cluster.describeKeyspace( keyspaceName ) == null )
            {
                logger.info( "Creating Archiva Cassandra '" + keyspaceName + "' keyspace." );
                cluster.addKeyspace( HFactory.createKeyspaceDefinition( keyspaceName, //
                                                                        ThriftKsDef.DEF_STRATEGY_CLASS, //
                                                                        replicationFactor, //
                                                                        cfds )
                );
            }
        }

    }

    @Override
    public void start()
    {
    }

    @PreDestroy
    @Override
    public void shutdown()
    {
    }


    @Override
    public boolean started()
    {
        return started;
    }


    @Override
    public Keyspace getKeyspace()
    {
        return keyspace;
    }

    @Override
    public Cluster getCluster()
    {
        return cluster;
    }

    @Override
    public String getRepositoryFamilyName()
    {
        return repositoryFamilyName;
    }

    @Override
    public String getNamespaceFamilyName()
    {
        return namespaceFamilyName;
    }

    @Override
    public String getProjectFamilyName()
    {
        return projectFamilyName;
    }

    @Override
    public String getProjectVersionMetadataFamilyName()
    {
        return projectVersionMetadataFamilyName;
    }

    @Override
    public String getArtifactMetadataFamilyName()
    {
        return artifactMetadataFamilyName;
    }

    @Override
    public String getMetadataFacetFamilyName()
    {
        return metadataFacetFamilyName;
    }

    @Override
    public String getMailingListFamilyName()
    {
        return mailingListFamilyName;
    }

    @Override
    public String getLicenseFamilyName()
    {
        return licenseFamilyName;
    }

    @Override
    public String getDependencyFamilyName()
    {
        return dependencyFamilyName;
    }
}
