package com.github.masahitojp.botan;

import com.github.masahitojp.botan.adapter.BotanAdapter;
import com.github.masahitojp.botan.adapter.ComandLineAdapter;
import com.github.masahitojp.botan.brain.BotanBrain;
import com.github.masahitojp.botan.brain.LocalBrain;
import com.github.masahitojp.botan.exception.BotanException;
import com.github.masahitojp.botan.handler.BotanMessageHandler;
import com.github.masahitojp.botan.handler.BotanMessageHandlers;
import com.github.masahitojp.botan.message.BotanMessage;
import com.github.masahitojp.botan.message.BotanMessageSimple;

import com.github.masahitojp.botan.utils.BotanUtils;
import lombok.extern.slf4j.Slf4j;
import org.reflections.Reflections;

import java.util.*;

@Slf4j
public final class Botan {
    public final BotanBrain brain;
    private String name;
    private final BotanAdapter adapter;
    private final Robot robot;

    private Botan(final BotanBuilder builder) {
        this.adapter = builder.adapter;
        this.name = builder.name;
        this.brain = builder.brain;
        this.robot = new Robot(this, builder.handlers);
    }

    static public void main(final String[] Args) {
        BotanUtils.readDotEnv();
        final Botan botan = new Botan.BotanBuilder()
                .build();

        java.lang.Runtime.getRuntime().addShutdownHook(
                new Thread() {
                    @Override
                    public void run() {
                        botan.stop();
                    }
                }
        );
        try {
            botan.start();
        } catch (final BotanException e) {
            log.warn("[Botan] {}", e);
        }

    }

    public final String getName() {
        return name;
    }

    public void say(BotanMessage message) {
        this.adapter.say(message);
    }

    public final void receive(BotanMessageSimple message) {
        this.robot.receive(message);
    }

    @SuppressWarnings("unused")
    public final void start() throws BotanException {
        log.info("bot start");
        adapter.initialize(this);

        // adapterはRunしたあとじゃないと名前がとれないことがあるため
        if (adapter.getFromAdapterName().isPresent()) {
            this.name = adapter.getFromAdapterName().get();
        }
        log.info("bot name : {}", this.name);

        this.robot.run();
        adapter.run();

    }

    @SuppressWarnings("unused")
    public final void stop() {
        log.info("bot stop");
        adapter.beforeShutdown();
        brain.beforeShutdown();
        robot.beforeShutdown();
    }

    public static class BotanBuilder {
        private static String DEFAULT_NAME = "botan";
        private BotanAdapter adapter;
        private String name = DEFAULT_NAME;
        private BotanBrain brain;
        private BotanMessageHandlers handlers;
        public BotanBuilder() {
        }

        @SuppressWarnings("unused")
        public final BotanBuilder setAdapter(final BotanAdapter adapter) {
            this.adapter = adapter;
            return this;
        }

        public final BotanBuilder setMessageHandlers(final BotanMessageHandlers handlers) {
            this.handlers = handlers;
            return this;
        }

        @SuppressWarnings("unused")
        public final BotanBuilder setName(final String name) {
            this.name = name;
            return this;
        }

        @SuppressWarnings("unused")
        public final BotanBuilder setBrain(final BotanBrain brain) {
            this.brain = brain;
            return this;
        }

        @SuppressWarnings("unused")
        public final Botan build() {
            setDefaultAdapter();
            setDefaultBrain();
            return new Botan(this);
        }

        private void setDefaultAdapter() {
            if (this.adapter == null) {
                final Optional<String> designatedClassName = Optional.ofNullable(System.getProperty("adapter"));
                final Reflections reflections = new Reflections();
                Set<Class<? extends BotanAdapter>> classes = reflections.getSubTypesOf(BotanAdapter.class);

                designatedClassName.ifPresent(x -> classes.stream().filter(clazz -> clazz.getName().equals(x)).forEach(
                        y -> {
                            try {
                                this.adapter = y.newInstance();
                            } catch (InstantiationException | IllegalAccessException e) {
                                log.warn("{}", e);
                            }
                        }
                ));


                if (this.adapter == null) {
                    Optional<Class<? extends BotanAdapter>> last = classes.stream()
                            .filter(clazz -> !clazz.getName().equals(ComandLineAdapter.class.getName()))
                            .reduce((previous, current) -> current);

                    last.ifPresent(adapter -> {
                        try {
                            this.adapter = adapter.newInstance();
                            if (this.adapter.getFromAdapterName().isPresent()) {
                                this.name = this.adapter.getFromAdapterName().get();
                            }
                        } catch (InstantiationException | IllegalAccessException e) {
                            log.warn("{}", e);
                        }
                    });
                }
                if (this.adapter == null) {
                    this.adapter = new ComandLineAdapter();
                }
            }
            log.info("adapter: {}", this.adapter.getClass().getSimpleName());
        }

        private void setDefaultBrain() {
            if (this.brain == null) {
                final Optional<String> designatedClassName = Optional.ofNullable(System.getProperty("brain"));
                final Reflections reflections = new Reflections();
                Set<Class<? extends BotanBrain>> classes = reflections.getSubTypesOf(BotanBrain.class);

                designatedClassName.ifPresent(x -> classes.stream().filter(clazz -> clazz.getName().equals(x)).forEach(
                        clazz -> {
                            try {
                                this.brain = clazz.newInstance();
                            } catch (InstantiationException | IllegalAccessException e) {
                                log.warn("{}", e);
                            }
                        }
                ));

                if (this.brain == null) {
                    Optional<Class<? extends BotanBrain>> last = classes.stream()
                            .filter(clazz -> !clazz.getName().equals(LocalBrain.class.getName()))
                            .reduce((previous, current) -> current);

                    last.ifPresent(x -> {
                        try {
                            this.brain = x.newInstance();
                        } catch (InstantiationException | IllegalAccessException e) {
                            log.warn("{}", e);
                        }
                    });
                }

                if (this.brain == null) {
                    this.brain = new LocalBrain();
                }
            }
            this.brain.initialize();
            log.info("brain: {}", this.brain.getClass().getSimpleName());
        }
    }
    public final List<BotanMessageHandler> getHandlers() {
        return robot.getHandlers();
    }
}

@@ -620,16 +620,38 @@
 nUtils;%0A
+import lombok.Getter;%0A
 import l
@@ -864,24 +864,36 @@
 er adapter;%0A
+    @Getter%0A
     private 
@@ -2144,20 +2144,21 @@
         
-if (
+this.
 adapter.
@@ -2175,33 +2175,33 @@
 pterName().i
-s
+f
 Present(
 )) %7B%0A       
@@ -2192,82 +2192,28 @@
 ent(
-)) %7B%0A            this.name = adapter.getFromAdapterName().get();%0A        %7D
+x -%3E this.name = x);
 %0A   
@@ -2264,26 +2264,23 @@
         
-this.robot
+adapter
 .run();%0A
@@ -2279,39 +2279,42 @@
 .run();%0A        
-adapter
+this.robot
 .run();%0A%0A    %7D%0A%0A
@@ -4988,36 +4988,32 @@
                 
-if (
 this.adapter.get
@@ -5035,17 +5035,17 @@
 ().i
-s
+f
 Present(
 )) %7B
@@ -5044,127 +5044,28 @@
 ent(
-)) %7B%0A                                this.name = this.adapter.getFromAdapterName().get();%0A                            %7D
+x -%3E this.name = x);
 %0A   

Change the order of start processing for Adapter