// Copyright 2006, 2007, 2008, 2011 The Apache Software Foundation
//
// 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.apache.tapestry5.ioc.internal.services;

import org.apache.tapestry5.ioc.services.ExceptionTracker;
import org.apache.tapestry5.plastic.MethodInvocation;
import org.slf4j.Logger;

import java.util.Iterator;

import static java.lang.String.format;

/**
 * Used by {@link org.apache.tapestry5.ioc.internal.services.LoggingDecoratorImpl} to delegate out logging behavior to a
 * separate object.
 */
public final class MethodLogger
{
    private static final int BUFFER_SIZE = 200;

    private static final String ENTER = "ENTER";

    private static final String EXIT = " EXIT";

    private static final String FAIL = " FAIL";

    private final Logger logger;

    private final ExceptionTracker exceptionTracker;

    public MethodLogger(Logger logger, ExceptionTracker exceptionTracker)
    {
        this.logger = logger;
        this.exceptionTracker = exceptionTracker;
    }

    public boolean isDebugEnabled()
    {
        return logger.isDebugEnabled();
    }

    /**
     * Invoked when a method is first entered
     *
     * @param invocation identifies method invoked as well as parameters passed to method
     */
    public void entry(MethodInvocation invocation)
    {
        StringBuilder buffer = new StringBuilder(BUFFER_SIZE);

        buffer.append(format("[%s] %s(", ENTER, invocation.getMethod().getName()));

        for (int i = 0; i < invocation.getMethod().getParameterTypes().length; i++)
        {
            if (i > 0) buffer.append(", ");

            convert(buffer, invocation.getParameter(i));
        }

        buffer.append(')');

        logger.debug(buffer.toString());
    }

    private void convert(StringBuilder buffer, Object object)
    {
        if (object == null)
        {
            buffer.append("null");
            return;
        }

        // Minimal, alas: Doesn't handle embedded quotes and other
        // characters. Really want to convert the string back to what it
        // would look like as source code.

        if (object instanceof String)
        {
            buffer.append("\"");
            buffer.append(object.toString());
            buffer.append("\"");
            return;
        }

        if (object instanceof Object[])
        {
            Object[] values = (Object[]) object;
            buffer.append('{');

            for (int i = 0; i < values.length; i++)
            {
                if (i > 0) buffer.append(", ");

                convert(buffer, values[i]);
            }

            buffer.append('}');
            return;
        }

        if (object instanceof Iterable)
        {
            Iterable itr = (Iterable) object;
            boolean first = true;

            buffer.append('[');
            Iterator i = itr.iterator();
            while (i.hasNext())
            {
                if (!first) buffer.append(", ");

                convert(buffer, i.next());
                first = false;
            }
            buffer.append(']');
            return;
        }

        // Might need to add a few more, for things like character values ...

        buffer.append(object.toString());
    }

    /**
     * Invoked when a method exits (possibly returning a value).
     *
     * @param invocation identifies method invocation and  result value
     */
    public void exit(MethodInvocation invocation)
    {
        StringBuilder buffer = new StringBuilder(BUFFER_SIZE);

        buffer.append(format("[%s] %s", EXIT, invocation.getMethod().getName()));

        if (invocation.getMethod().getReturnType() != void.class)
        {
            buffer.append(" [");
            convert(buffer, invocation.getReturnValue());
            buffer.append(']');
        }

        logger.debug(buffer.toString());
    }

    /**
     * Invoked when method invocation instead throws an exception.
     *
     * @param invocation identifies method invocation which failed
     * @param t          exception throws by method invocation
     */
    public void fail(MethodInvocation invocation, Throwable t)
    {
        logger.debug(
                format("[%s] %s -- %s", FAIL,
                        invocation.getMethod().getName(),
                        t.getClass().getName()),
                exceptionTracker.exceptionLogged(t) ? null : t);
    }
}
