/*
 * Copyright 2011 Paul Merlin.
 *
 * Licensed  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.
 */
package org.qi4j.library.uowfile.internal;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.qi4j.api.common.Optional;
import org.qi4j.api.injection.scope.Service;
import org.qi4j.api.injection.scope.Structure;
import org.qi4j.api.injection.scope.This;
import org.qi4j.api.mixin.Mixins;
import org.qi4j.api.service.ServiceActivation;
import org.qi4j.api.service.ServiceComposite;
import org.qi4j.api.structure.Application;
import org.qi4j.api.structure.Module;
import org.qi4j.api.unitofwork.UnitOfWork;
import org.qi4j.api.unitofwork.UnitOfWorkCallback;
import org.qi4j.api.unitofwork.UnitOfWorkCompletionException;
import org.qi4j.library.fileconfig.FileConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Mixins( UoWFileFactory.Mixin.class )
public interface UoWFileFactory
    extends ServiceActivation
{

    UoWFile createCurrentUoWFile( File file );

    class Mixin
        implements UoWFileFactory
    {

        private static class UoWFilesMetaInfo
            extends HashMap<String, UoWFile>
        {
        }

        private static final Logger LOGGER = LoggerFactory.getLogger( "org.qi4j.library.uowfile" );

        @Structure
        private Application app;

        @Structure
        private Module module;

        @This
        private ServiceComposite me;

        @Optional
        @Service
        private FileConfiguration fileConfig;

        private File workDir;

        @Override
        public void activateService()
            throws IOException
        {
            File tmp;
            if( fileConfig == null )
            {
                tmp = new File( "qi4j", app.name() + "-" + app.version() );
            }
            else
            {
                tmp = fileConfig.temporaryDirectory();
            }
            workDir = new File( tmp, "uowfile-" + me.identity().get() );
            if( !workDir.exists() && !workDir.mkdirs() )
            {
                throw new IOException( "Unable to create temporary directory: " + workDir );
            }
        }

        @Override
        public void passivateService()
            throws Exception
        {
        }

        @Override
        public UoWFile createCurrentUoWFile( File file )
        {
            return createUoWFile( module.currentUnitOfWork(), file, workDir );
        }

        private static synchronized UoWFile createUoWFile( UnitOfWork uow, File file, File workDir )
        {
            UoWFilesMetaInfo uowMeta = ensureUoWMeta( uow );
            String absolutePath = file.getAbsolutePath();
            UoWFile uowFile = uowMeta.get( absolutePath );
            if( uowFile == null )
            {
                uowFile = new UoWFile( file, workDir );
                uowFile.copyOriginalToCurrent();
                uowMeta.put( absolutePath, uowFile );
                LOGGER.trace( "Registered {} in UoW", uowFile );
            }
            return uowFile;
        }

        /**
         * Ensure UoW meta info tracking UoWFiles is present and UoW callback is registered.
         */
        private static UoWFilesMetaInfo ensureUoWMeta( final UnitOfWork uow )
        {
            UoWFilesMetaInfo uowMeta = uow.metaInfo( UoWFilesMetaInfo.class );
            if( uowMeta != null )
            {
                return uowMeta;
            }

            uowMeta = new UoWFilesMetaInfo();
            uow.setMetaInfo( uowMeta );

            uow.addUnitOfWorkCallback( new UnitOfWorkCallback()
            {
                @Override
                public void beforeCompletion()
                    throws UnitOfWorkCompletionException
                {
                    UoWFilesMetaInfo uowMeta = uow.metaInfo( UoWFilesMetaInfo.class );
                    if( uowMeta != null && !uowMeta.isEmpty() )
                    {
                        List<UoWFile> concurrentlyModified = new ArrayList<>();
                        for( UoWFile eachUoWFile : uowMeta.values() )
                        {
                            try
                            {
                                eachUoWFile.apply();
                            }
                            catch( ConcurrentUoWFileStateModificationException ex )
                            {
                                concurrentlyModified.add( ex.getUoWFile() );
                            }
                        }
                        if( !concurrentlyModified.isEmpty() )
                        {
                            throw new ConcurrentUoWFileModificationException( concurrentlyModified );
                        }
                    }
                }

                @Override
                public void afterCompletion( UnitOfWorkStatus status )
                {
                    UoWFilesMetaInfo uowMeta = uow.metaInfo( UoWFilesMetaInfo.class );
                    if( uowMeta != null && !uowMeta.isEmpty() )
                    {
                        for( UoWFile eachUoWFile : uowMeta.values() )
                        {
                            if( status == UnitOfWorkStatus.DISCARDED )
                            {
                                eachUoWFile.rollback();
                            }
                            eachUoWFile.cleanup();
                        }
                        uow.metaInfo( UoWFilesMetaInfo.class ).clear();
                    }
                }
            } );
            return uowMeta;
        }
    }

}
