package ru.yandex.webmaster3.worker;

import com.google.common.collect.Maps;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import ru.yandex.webmaster3.core.worker.task.PeriodicTaskType;
import ru.yandex.webmaster3.core.worker.task.WorkerTaskData;
import ru.yandex.webmaster3.core.worker.task.WorkerTaskType;

import javax.annotation.PostConstruct;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.EnumMap;
import java.util.Map;

/**
 * Created by Oleg Bazdyrev on 2019-08-06.
 */
@Component("taskRegistry")
public class TaskRegistry {

    private final ApplicationContext applicationContext;
    private Map<WorkerTaskType, Task> taskRegistryMap;
    private Map<PeriodicTaskType, PeriodicTask> periodicTaskRegistryMap;

    @Autowired
    public TaskRegistry(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;

    }

    @PostConstruct
    public void init() {
        EnumMap<WorkerTaskType, Task> taskMap = new EnumMap<>(WorkerTaskType.class);
        for (String beanName : applicationContext.getBeanNamesForType(Task.class)) {
            // страшная магия, пытаемся инстанцировать DataClass
            Task bean = (Task) applicationContext.getBean(beanName);
            Class<? extends WorkerTaskData> dataClass = bean.getDataClass();
            WorkerTaskType taskType = findTaskType(dataClass);
            if (taskType == null) {
                throw new IllegalStateException("Could not find taskType for bean " + beanName);
            }
            taskMap.put(taskType, bean);
        }
        taskRegistryMap = Maps.immutableEnumMap(taskMap);

        EnumMap<PeriodicTaskType, PeriodicTask> periodicTaskMap = new EnumMap<>(PeriodicTaskType.class);
        for (String beanName : applicationContext.getBeanNamesForType(PeriodicTask.class)) {
            PeriodicTask bean = (PeriodicTask) applicationContext.getBean(beanName);
            periodicTaskMap.put(bean.getType(), bean);
        }
        periodicTaskRegistryMap = Maps.immutableEnumMap(periodicTaskMap);
    }

    private WorkerTaskType findTaskType(Class<? extends WorkerTaskData> dataClass) {
        // try to instantiate all public constructor
        for (Constructor<?> constructor : dataClass.getDeclaredConstructors()) {
            if ((constructor.getModifiers() & Modifier.PUBLIC) > 0) {
                Class<?>[] parameterTypes = constructor.getParameterTypes();
                Object[] parameters = new Object[parameterTypes.length];
                for (int i = 0; i < parameterTypes.length; i++) {
                    Class<?> parameterType = parameterTypes[i];
                    parameters[i] = instantiate(parameterType);
                }
                try {
                    WorkerTaskData taskData = (WorkerTaskData) constructor.newInstance(parameters);
                    return taskData.getTaskType();
                } catch (Exception ignored) {
                }
            }
        }
        return null;
    }

    public static Object instantiate(Class<?> clazz) {
        if (!clazz.isPrimitive()) {
            return null;
        }
        switch (clazz.getName()) {
            case "boolean":
                return false;
            case "byte":
                return (byte) 0;
            case "short":
                return (short) 0;
            case "int":
                return (int) 0;
            case "long":
                return (long) 0;
            case "float":
                return (float) 0;
            case "double":
                return (double) 0;
            case "char":
                return (char) 0;
            case "void":
                return null;
        }
        throw new IllegalStateException();
    }

    public Map<WorkerTaskType, Task> getTaskRegistryMap() {
        return taskRegistryMap;
    }

    public Map<PeriodicTaskType, PeriodicTask> getPeriodicTaskRegistryMap() {
        return periodicTaskRegistryMap;
    }
}
