/*******************************************************************************
 * Copyright (c) 2015 IBH SYSTEMS GmbH.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBH SYSTEMS GmbH - initial API and implementation
 *******************************************************************************/
package org.eclipse.packagedrone.utils.profiler;

import java.io.File;
import java.lang.reflect.Proxy;
import java.time.Duration;
import java.time.Instant;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;

import org.eclipse.packagedrone.utils.profiler.internal.ProfilerInvocationHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class Profile
{
    private final static Logger logger = LoggerFactory.getLogger ( Profile.class );

    private Profile ()
    {
    }

    public static interface Handle extends AutoCloseable
    {
        @Override
        public void close ();

        public Handle createChild ( String operation );

        /**
         * Mark the beginning of a new task
         *
         * @param taskName
         *            the name of the task
         */
        public void task ( String taskName );
    }

    private static Handle NOP = new Handle () {

        @Override
        public void close ()
        {
        }

        @Override
        public Handle createChild ( final String operation )
        {
            return NOP;
        }

        @Override
        public void task ( final String operation )
        {
        }
    };

    public static class DurationEntry
    {
        private final String operation;

        private final Duration duration;

        private List<DurationEntry> entries;

        public DurationEntry ( final String operation, final Duration duration )
        {
            this.operation = operation;
            this.duration = duration;
        }

        public DurationEntry ( final String operation, final Duration duration, final List<DurationEntry> entries )
        {
            this.operation = operation;
            this.duration = duration;
            this.entries = entries;
        }

        public String getOperation ()
        {
            return this.operation;
        }

        public Duration getDuration ()
        {
            return this.duration;
        }

        public List<DurationEntry> getEntries ()
        {
            return this.entries;
        }
    }

    public static class HandleImpl implements Handle
    {
        private final HandleImpl parent;

        private final Instant start;

        private final String operation;

        private final LinkedList<DurationEntry> entries = new LinkedList<> ();

        private Instant taskStart;

        private String taskName;

        public HandleImpl ( final String operation )
        {
            this ( operation, null );
        }

        public HandleImpl ( final String operation, final HandleImpl parent )
        {
            this.parent = parent;
            this.operation = operation;

            activeProfilers.set ( this );

            this.start = Instant.now ();
        }

        @Override
        public Handle createChild ( final String operation )
        {
            return new HandleImpl ( operation, this );
        }

        @Override
        public String toString ()
        {
            return "[" + this.operation + "]";
        }

        @Override
        public void task ( final String taskName )
        {
            debug ( "task : %s", taskName );

            final Instant now = Instant.now ();

            closeTask ( now );

            this.taskStart = now;
            this.taskName = taskName;
        }

        private void closeTask ( final Instant now )
        {
            if ( this.taskStart != null )
            {
                final Duration duration = Duration.between ( this.taskStart, now );
                final DurationEntry entry = new DurationEntry ( this.taskName, duration, Collections.emptyList () );
                this.entries.add ( entry );
            }
        }

        @Override
        public void close ()
        {
            final Instant now = Instant.now ();

            closeTask ( now );

            final Duration duration = Duration.between ( this.start, now );
            final DurationEntry entry = new DurationEntry ( this.operation, duration, this.entries );

            activeProfilers.set ( this.parent );

            if ( this.parent != null )
            {
                debug ( "close : %s : %s", this.parent, this.operation );
                this.parent.entries.add ( entry );
            }
            else
            {
                debug ( "dump : %s", this.operation );

                final ProfileDataHandler handler = getHandler ();
                if ( handler != null )
                {
                    handler.handle ( entry );
                }
            }
        }

    }

    private static ThreadLocal<Handle> activeProfilers = new ThreadLocal<> ();

    private static ProfileDataHandler HANDLER;

    static
    {
        final String value = System.getProperty ( "drone.profile" );
        if ( value != null )
        {
            if ( "true".equalsIgnoreCase ( value ) )
            {
                HANDLER = DumpProfileDataHandler.INSTANCE;
            }
            else if ( value.startsWith ( "xml:" ) )
            {
                final String[] toks = value.split ( ":", 2 );
                if ( toks.length == 2 )
                {
                    try
                    {
                        HANDLER = new XmlProfileDataHandler ( new File ( toks[1] ).toPath () );
                    }
                    catch ( final Throwable e )
                    {
                        logger.error ( "Failed to initialize XML profile data handler", e );
                    }
                }
            }

            if ( HANDLER == null )
            {
                debug ( "Failed to initialize profile data handler. Activated as: '%s'", value );
            }
        }
    }

    private static boolean isActive ()
    {
        return HANDLER != null;
    }

    public static Handle start ( final Object service, final String methodName )
    {
        return start ( makeOperation ( service, methodName ) );
    }

    @SuppressWarnings ( "resource" )
    public static Handle start ( final String operation )
    {
        if ( !isActive () )
        {
            return NOP;
        }
        else
        {
            final Handle handle = activeProfilers.get ();
            if ( handle == null )
            {
                debug ( "create : %s", operation );
                return new HandleImpl ( operation );
            }
            else
            {
                debug ( "child : %s : %s", handle, operation );
                return handle.createChild ( operation );
            }
        }
    }

    private static String makeOperation ( final Object service, final String methodName )
    {
        return String.format ( "%s.%s", service.getClass ().getName (), methodName );
    }

    public static <T> T call ( final Object service, final String methodName, final Callable<T> call )
    {
        return call ( makeOperation ( service, methodName ), call );
    }

    public static <T> T call ( final String operation, final Callable<T> call )
    {
        try ( Handle handle = start ( operation ) )
        {
            return call.call ();
        }
        catch ( final Exception e )
        {
            throw new RuntimeException ( e );
        }
    }

    public static void run ( final Object service, final String methodName, final Runnable run )
    {
        run ( makeOperation ( service, methodName ), run );
    }

    public static void run ( final String operation, final Runnable run )
    {
        call ( operation, () -> {
            run.run ();
            return null;
        } );
    }

    public static <T> T proxy ( final T service, final Class<T> iface )
    {
        if ( service == null )
        {
            return null;
        }

        if ( isActive () )
        {
            return iface.cast ( Proxy.newProxyInstance ( service.getClass ().getClassLoader (), new Class<?>[] { iface }, new ProfilerInvocationHandler ( service ) ) );
        }
        else
        {
            return service;
        }
    }

    private static void debug ( final String format, final Object... args )
    {
        if ( Boolean.getBoolean ( "drone.profile.debug" ) )
        {
            System.out.print ( Thread.currentThread ().getName () );
            System.out.print ( ": " );
            System.out.format ( format, args );
            System.out.println ();
        }
    }

    private static ProfileDataHandler getHandler ()
    {
        return HANDLER;
    }
}

@@ -95,16 +95,22 @@
 (c) 2015
+, 2016
  IBH SYS
@@ -6607,45 +6607,8 @@
  %7D%0A%0A
-    @SuppressWarnings ( %22resource%22 )%0A
     

clear warning