/**
 * 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.
 */
/**
 *
 */
package org.apache.cxf.aegis.type.basic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

class InterfaceInvocationHandler implements InvocationHandler {
    private Map<Object, Object> attributes = new HashMap<>();

    InterfaceInvocationHandler() {
    }


    public void writeProperty(Object key, Object value) {
        attributes.put(key, value);
    }

    public Object readProperty(Object key) {
        return attributes.get(key);
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.getMethod("hashCode", (Class[]) null).equals(method)) {
            return proxyHashCode(proxy);
        } else if (Object.class.getMethod("equals", new Class[] {Object.class}).equals(method)) {
            return proxyEquals(proxy, args[0]);
        } else if (Object.class.getMethod("toString", (Class[]) null).equals(method)) {
            return proxyToString(proxy);
        } else {
            if (isGetterMethod(method, args)) {
                return doGetter(method, args);
            } else if (isSetterMethod(method, args)) {
                return doSetter(method, args);
            } else {
                throw new IllegalAccessError(method.getName() + " is not delegated.");
            }
        }
    }

    protected boolean isGetterMethod(Method method, Object[] args) {
        if (args != null && args.length > 0) {
            return false;
        }

        String methodName = method.getName();

        if (methodName.startsWith("get") && methodName.length() > 3) {
            return true;
        }

        return methodName.length() > 2 && methodName.startsWith("is");
        /*
         * // should "hasXXX()" be considered a getter method? else if
         * (methodName.startsWith("has") && methodName.length() > 3) { return
         * true; }
         */
    }

    protected boolean isSetterMethod(Method method, Object[] args) {
        if (args == null || args.length != 1) {
            return false;
        }

        if (!"void".equals(method.getReturnType().getName())) {
            return false;
        }

        String methodName = method.getName();

        return methodName.startsWith("set") && methodName.length() > 3;
    }

    protected Object doGetter(Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        final String attrName;

        if (methodName.startsWith("get")) {
            attrName = convertMethodName(methodName, 3);
        } else if (methodName.startsWith("is")) {
            attrName = convertMethodName(methodName, 2);
        } else {
            throw new IllegalAccessError(methodName + " is not a valid getter method.");
        }

        Object prop = readProperty(attrName);
        if (prop == null && method.getReturnType().isPrimitive()) {
            if (method.getReturnType() == int.class) {
                prop = Integer.valueOf(0);
            } else if (method.getReturnType() == boolean.class) {
                prop = Boolean.FALSE;
            } else if (method.getReturnType() == long.class) {
                prop = Long.valueOf(0);
            } else if (method.getReturnType() == double.class) {
                prop = Double.valueOf(0);
            } else if (method.getReturnType() == short.class) {
                prop = Short.valueOf((short)0);
            } else if (method.getReturnType() == byte.class) {
                prop = Byte.valueOf((byte)0);
            } else if (method.getReturnType() == char.class) {
                prop = Character.valueOf((char)0);
            }
        }
        return prop;
    }

    protected Object doSetter(Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        final String attrName;

        if (methodName.startsWith("set")) {
            attrName = convertMethodName(methodName, 3);
        } else {
            throw new IllegalAccessError(methodName + " is not a valid setter method.");
        }

        writeProperty(attrName, args[0]);

        return null;
    }

    private String convertMethodName(String methodName, int firstCharacter) {
        if (methodName.length() >= firstCharacter + 2) {
            if (!Character.isUpperCase(methodName.charAt(firstCharacter + 1))) {
                return Character.toLowerCase(methodName.charAt(firstCharacter))
                       + methodName.substring(firstCharacter + 1);
            }
            return methodName.substring(3);
        }
        return Character.toLowerCase(methodName.charAt(firstCharacter))
               + methodName.substring(firstCharacter + 1);
    }

    protected Integer proxyHashCode(Object proxy) {
        return Integer.valueOf(System.identityHashCode(proxy));
    }

    protected Boolean proxyEquals(Object proxy, Object other) {
        return proxy == other ? Boolean.TRUE : Boolean.FALSE;
    }

    protected String proxyToString(Object proxy) {
        return proxy.getClass().getName() + '@' + proxy.hashCode();
    }
}
