/*
 * Copyright � 2002-2011 the original author or authors.
 *
 * 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.
 */

using System;
using System.Reflection;
using AopAlliance.Intercept;
using NHibernate.Engine;
using NHibernate.Proxy.Poco;
using NHibernate.Type;
using Spring.Aop;
using Spring.Reflection.Dynamic;

namespace Spring.Data.NHibernate.Bytecode
{
    /// <summary>
    /// 
    /// </summary>
    /// <author>Fabio Maulo</author>
    [Serializable]
    public class LazyInitializer : BasicLazyInitializer, IMethodInterceptor, ITargetSource
    {
        private static readonly MethodInfo exceptionInternalPreserveStackTrace;

        static LazyInitializer()
        {
            exceptionInternalPreserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic);
        }

        ///<summary>
        ///</summary>
        ///<param name="entityName"></param>
        ///<param name="persistentClass"></param>
        ///<param name="id"></param>
        ///<param name="getIdentifierMethod"></param>
        ///<param name="setIdentifierMethod"></param>
        ///<param name="componentIdType"></param>
        ///<param name="session"></param>
        public LazyInitializer(string entityName, Type persistentClass, object id, MethodInfo getIdentifierMethod,
            MethodInfo setIdentifierMethod, IAbstractComponentType componentIdType,
            ISessionImplementor session)
            : base(
                entityName,
                persistentClass,
                id,
                getIdentifierMethod,
                setIdentifierMethod,
                componentIdType,
                session,
                overridesEquals: false
            )
        {
        }

        /// <summary>
        /// Implement this method to perform extra treatments before and after
        /// the call to the supplied <paramref name="invocation"/>.
        /// </summary>
        /// <remarks>
        /// <p>
        /// Polite implementations would certainly like to invoke
        /// <see cref="AopAlliance.Intercept.IJoinpoint.Proceed"/>. 
        /// </p>
        /// </remarks>
        /// <param name="invocation">
        /// The method invocation that is being intercepted.
        /// </param>
        /// <returns>
        /// The result of the call to the
        /// <see cref="AopAlliance.Intercept.IJoinpoint.Proceed"/> method of
        /// the supplied <paramref name="invocation"/>; this return value may
        /// well have been intercepted by the interceptor.
        /// </returns>
        /// <exception cref="System.Exception">
        /// If any of the interceptors in the chain or the target object itself
        /// throws an exception.
        /// </exception>
        public object Invoke(IMethodInvocation invocation)
        {
            try
            {
                MethodInfo methodInfo = invocation.Method;
                object returnValue = base.Invoke(methodInfo, invocation.Arguments, invocation.Proxy);

                if (returnValue != InvokeImplementation)
                {
                    return returnValue;
                }

                SafeMethod method = new SafeMethod(methodInfo);
                return method.Invoke(GetImplementation(), invocation.Arguments);
            }
            catch (TargetInvocationException ex)
            {
                exceptionInternalPreserveStackTrace.Invoke(ex.InnerException, new Object[] { });
                throw ex.InnerException;
            }
        }

        object ITargetSource.GetTarget() => Target;

        void ITargetSource.ReleaseTarget(object target) { }

        Type ITargetSource.TargetType => PersistentClass;

        bool ITargetSource.IsStatic => false;
    }
}
