/*
 * Copyright 2014 NAVER Corp.
 *
 * 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 com.navercorp.pinpoint.common.util;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

/**
 * @author emeroad
 */
public class StaticFieldLookUp<T> {

    public static interface Filter<T>  {
        boolean FILTERED = true;
        boolean INCLUDE = false;
        boolean filter(T serviceType);
    }

    public static class BypassFilter<T> implements Filter<T> {
        @Override
        public boolean filter(T type) {
            return INCLUDE;
        }
    };

    public static class ExcludeFilter<T> implements Filter<T> {
        private final T[] excludeTypeList;

        public ExcludeFilter(T[] excludeTypeList) {
            if (excludeTypeList == null) {
                throw new NullPointerException("excludeTypeList must not be null");
            }
             this.excludeTypeList = excludeTypeList;
        }

        @Override
        public boolean filter(T type) {
            for (T excludeType : excludeTypeList) {
                if (excludeType == type) {
                    return FILTERED;
                }
            }
            return Filter.INCLUDE;
        }
    };

    private final Class<?> targetClazz;
    private final Class<T> findClazz;

    public StaticFieldLookUp(Class<?> targetClazz, Class<T> findClazz) {
        if (targetClazz == null) {
            throw new NullPointerException("targetClazz must not be null");
        }
        if (findClazz == null) {
            throw new NullPointerException("findClazz must not be null");
        }
        this.targetClazz = targetClazz;
        this.findClazz = findClazz;
    }

    public List<T> lookup(Filter<T> filter) {
        if (filter == null) {
            throw new NullPointerException("filter must not be null");
        }
        final List<T> lookup = new ArrayList<T>();

        Field[] declaredFields = targetClazz.getDeclaredFields();
        for (Field field : declaredFields) {
            if (!Modifier.isStatic(field.getModifiers())) {
                continue;
            }
            if (!Modifier.isPublic(field.getModifiers())) {
                continue;
            }
            if (!field.getType().equals(findClazz)) {
                continue;
            }
            final Object filedObject = getObject(field);

            if (findClazz.isInstance(filedObject)) {
                T type = findClazz.cast(filedObject);
                if (filter.filter(type) == Filter.FILTERED) {
                    continue;
                }

                lookup.add(type);
            }
        }
        return lookup;
    }

    public List<T> lookup() {
        return lookup(new BypassFilter<T>());
    }

    private Object getObject(Field field) {
        try {
            return field.get(findClazz);
        } catch (IllegalAccessException ex) {
            throw new RuntimeException("service access fail. Caused by:" + ex.getMessage(), ex);
        }
    }



}
