package org.boon.slumberdb.service.server;

import org.boon.*;
import org.boon.concurrent.SimpleExecutors;
import org.boon.concurrent.Timer;
import org.boon.core.Sys;
import org.boon.core.reflection.BeanUtils;
import org.boon.core.reflection.ClassMeta;
import org.boon.core.reflection.Invoker;
import org.boon.core.reflection.MethodAccess;
import org.boon.json.JsonParserAndMapper;
import org.boon.json.JsonParserFactory;
import org.boon.json.JsonSerializer;
import org.boon.json.JsonSerializerFactory;
import org.boon.primitive.Arry;
import org.boon.primitive.CharBuf;
import org.boon.slumberdb.config.GlobalConfig;
import org.boon.slumberdb.service.config.DataStoreServerConfig;
import org.boon.slumberdb.service.protocol.Action;
import org.boon.slumberdb.service.protocol.ProtocolConstants;
import org.boon.slumberdb.service.protocol.factory.RequestFactory;
import org.boon.slumberdb.service.protocol.requests.*;
import org.boon.slumberdb.service.results.SingleResult;
import org.boon.slumberdb.service.results.StatCount;
import org.boon.slumberdb.service.results.StatsResults;
import org.boon.slumberdb.stores.DataOutputQueue;
import org.boon.slumberdb.stores.DataStoreSource;
import org.boon.slumberdb.stores.MasterDataStore;

import java.io.File;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.MemoryUsage;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedTransferQueue;

import static org.boon.Boon.configurableLogger;
import static org.boon.Boon.puts;
import static org.boon.Lists.safeList;
import static org.boon.slumberdb.service.protocol.ProtocolConstants.DELIMITER_STR;

/**
 * Created by Richard on 9/10/14.
 */
public class RequestHandler {

    final List<CallStatusTracker> callStatusTrackers = safeList(CallStatusTracker.class);
    private final boolean debug = GlobalConfig.DEBUG;
    protected LinkedTransferQueue<BroadCastSendData> broadCastQueue = new LinkedTransferQueue<>();
    ThreadLocal<JsonParserAndMapper> parserThreadLocal = new ThreadLocal<>();
    ThreadLocal<CharBuf> prettyPrintBuf = new ThreadLocal<>();
    ThreadLocal<JsonSerializer> jsonSerializer = new ThreadLocal<>();
    ThreadLocal<CallStatusTracker> callStatusTrackerThreadLocal = new ThreadLocal<>();
    int textBodyCall;
    int mapBodyCall;
    private boolean verbose = GlobalConfig.VERBOSE;
    private MasterDataStore masterDataStore = null;
    private ExecutorService executorService;
    private ExecutorService utilityThreads;
    private RequestFactory<String, DataStoreRequest> requestFromTextFactory;
    private RequestFactory<Map<String, String>, DataStoreRequest> requestFromMapFactory;
    private DataOutputQueue queue = null;
    /*

_____         .__         ___________       __                  .___ __________________________
/     \ _____  |__| ____   \_   _____/ _____/  |________ ___.__. |   |\      \__    ___/\_____  \
/  \ /  \\__  \ |  |/    \   |    __)_ /    \   __\_  __ <   |  | |   |/   |   \|    |    /   |   \
/    Y    \/ __ \|  |   |  \  |        \   |  \  |  |  | \/\___  | |   /    |    \    |   /    |    \
\____|__  (____  /__|___|  / /_______  /___|  /__|  |__|   / ____| |___\____|__  /____|   \_______  /
    \/     \/        \/          \/     \/             \/                  \/                 \/
________      _____________________      ____________________________ _____________________
\______ \    /  _  \__    ___/  _  \    /   _____/\__    ___/\_____  \\______   \_   _____/
|    |  \  /  /_\  \|    | /  /_\  \   \_____  \   |    |    /   |   \|       _/|    __)_
|    `   \/    |    \    |/    |    \  /        \  |    |   /    |    \    |   \|        \
/_______  /\____|__  /____|\____|__  / /_______  /  |____|   \_______  /____|_  /_______  /
    \/         \/              \/          \/                    \/       \/        \/
 */
    private Logger logger = configurableLogger(this.getClass());
    private DataStoreServerConfig config;
    private DataStoreServer storeServer = null;
    private Map<String, ClassMeta<?>> servicesDefinition;

    private Map<String, Object> services;
    private long messageIdGenerator = System.currentTimeMillis();
    private Set<String> dontEncodeMethods = Sets.safeSet("jmxStats");

    /**
     * Main entry into system.
     *

     *

     *

     * If you are debugging something not coming into the system.
     *

     * Start here!
     */

    @ServiceMethod
    public void mainRequestHandler(DataStoreRequest dataStoreRequest) {

        if (verbose || debug) {
            logger.info("RequestHandler::mainRequestHandler", dataStoreRequest);
        }
        switch (dataStoreRequest.action()) {
            case GET:
                masterDataStore.get((GetRequest) dataStoreRequest);
                break;
            case GET_LOCAL_DB:
                handleGetLocalDbVerb(dataStoreRequest);
                break;
            case GET_MEM:
                handleGetMemVerb(dataStoreRequest);
                break;
            case SET:
                masterDataStore.set((SetRequest) dataStoreRequest);
                break;
            case SET_BROADCAST:
                handleSetAndBroadCastVerb(dataStoreRequest);
                break;
            case SET_IF_NOT_EXIST:
                handleSetIfNotExistsVerb(dataStoreRequest);
                break;
            case SET_BATCH:
                handleSetBatch(dataStoreRequest);
                break;
            case SET_BATCH_IF_NOT_EXISTS:
                handleSetBatchIfNotExists(dataStoreRequest);
                break;
            case SET_SOURCE:
                handleSetSource(dataStoreRequest);
                break;
            case GET_SOURCE:
                handleGetSource(dataStoreRequest);
                break;

            case BATCH_READ:
                handleBatchRead(dataStoreRequest);
                break;

            case CLEAR_STATS:
                handleClearStats(dataStoreRequest);
                break;

            case GET_STATS:
                handleGetStats(dataStoreRequest);
                break;

            case REMOVE:
                handleRemove(dataStoreRequest);
                break;

            case REMOVE_SOURCE:
                handleRemoveSource(dataStoreRequest);
                break;

            case METHOD_CALL:
                handleMethodCall((MethodCall) dataStoreRequest);
                break;

            case SEARCH:
                handleSearchVerb(dataStoreRequest);
                break;

            default:
                puts(dataStoreRequest);

        }
        trackCall(dataStoreRequest.action());
    }

    @ServiceMethod
    public String jmxStats() {
        return MBeans.toJson();
    }

    @ServiceMethod
    public Object jmxStatsPretty() {
        final Object fromJson = Boon.fromJson(MBeans.toJson());
        return fromJson;
    }

    @ServiceMethod
    public List<Map<String, Object>> methodList(String object) {

        List<Map<String, Object>> methodList = new ArrayList<>();
        Iterable<MethodAccess> methods = servicesDefinition.get(object).methods();

        for (MethodAccess access : methods) {
            boolean publicFlag = true;
            String methodName = access.name();
            boolean weirdMethod = methodName.indexOf('$') != -1;
            boolean serviceMethod = access.hasAnnotation("serviceMethod");

            if (publicFlag && !weirdMethod && serviceMethod) {

                methodList.add(
                        Maps.map(
                                "name", access.name(),
                                "parameterTypes", BeanUtils.idx(access.parameterTypes(), "name"),
                                "returnType", access.returnType().getName()
                        )
                );
            }
        }

        return methodList;
    }

    private void handleMethodCall(MethodCall methodCall) {

        logger.info(methodCall);

        final ClassMeta<?> classMeta = servicesDefinition.get(methodCall.object());
        if (classMeta == null) {
            this.storeServer.sendMessageToClientId(methodCall.clientId(), "[\"UNKNOWN OBJECT\"]");
            return;
        }

        MethodAccess methodAccess = classMeta.method(methodCall.method());

        if (methodAccess == null) {
            this.storeServer.sendMessageToClientId(methodCall.clientId(), "[\"UNKNOWN METHOD\"]");
            return;
        }

        Object service = services.get(methodCall.object());

        Object returnValue = null;

        try {

            /* If there are no parameters, just invoke it. */
            if (methodAccess.parameterTypes().length == 0) {
                returnValue = methodAccess.invoke(service);

            /* If the length and argument sizes match then invoke using invokeFromList. */
            } else if (methodAccess.parameterTypes().length
                    == methodCall.arguments().size()) {

                returnValue = Invoker.invokeFromList(service, methodAccess.name(), methodCall.arguments());

            /* If method call only has one argument, and we know they don't match, then
                convert this argument into list or map and try to invoke it. */
            } else if (methodCall.arguments().size() == 1) {

                final Object objectArg = jsonParser().parse(methodCall.arguments().get(0));
                if (objectArg instanceof List) {

                    List argList = (List) objectArg;

                    final Object[] objectArray = Arry.objectArray(argList);
                    if (methodAccess.respondsTo(objectArray)) {
                        returnValue = methodAccess.invoke(service, objectArray);
                    } else {
                        returnValue = Invoker.invokeFromList(service, methodAccess.name(), (List) objectArg);
                    }

                } else if (objectArg instanceof Map) {
                    returnValue = Invoker.invokeFromObject(service, methodAccess.name(), objectArg);
                } else {
                    returnValue = Invoker.invokeFromList(service, methodAccess.name(), Lists.list(objectArg));

                }
            }

        } catch (Exception ex) {
            logger.error(ex, "Error tyring to invoke method", methodCall);
            returnValue = ex;
        }

        String returnString;

        if (dontEncodeMethods.contains(methodCall.method())) {
            returnString = Str.str(returnValue.toString());
        } else if (methodCall.isPrettyPrint()) {

            final CharBuf charBuf = prettyPrintBuf();
            charBuf.recycle();

            returnString = prettyPrintBuf().prettyPrintObject(returnValue, false, 0).toString();
        } else {
            returnString = jsonSerializer().serialize(returnValue).toString();
        }

        storeServer.sendMessageToClientId(methodCall.clientId(), returnString);

    }

    private JsonParserAndMapper jsonParser() {

        JsonParserAndMapper jsonParser = parserThreadLocal.get();
        if (jsonParser == null) {
            jsonParser = new JsonParserFactory().create();

            parserThreadLocal.set(jsonParser);
        }
        return jsonParser;
    }

    private CharBuf prettyPrintBuf() {

        CharBuf buf = prettyPrintBuf.get();
        if (buf == null) {

            buf = CharBuf.createCharBuf();
            prettyPrintBuf.set(buf);
        }
        return buf;
    }

    private JsonSerializer jsonSerializer() {

        JsonSerializer jsonSerializer = this.jsonSerializer.get();
        if (jsonSerializer == null) {

            jsonSerializer = new JsonSerializerFactory().create();
            this.jsonSerializer.set(jsonSerializer);
        }
        return jsonSerializer;
    }

    private void trackCall(Action action) {
        callStatusTracker().addCall(action, queue);
    }

    CallStatusTracker callStatusTracker() {

        CallStatusTracker callStatusTracker = callStatusTrackerThreadLocal.get();
        if (callStatusTracker == null) {
            callStatusTracker = new CallStatusTracker();
            callStatusTrackers.add(callStatusTracker);
            callStatusTrackerThreadLocal.set(callStatusTracker);
        }
        return callStatusTracker;

    }

    private void handleGetLocalDbVerb(DataStoreRequest dataStoreRequest) {
        masterDataStore.getSource((GetRequest) dataStoreRequest);

    }

    private void handleGetMemVerb(DataStoreRequest dataStoreRequest) {
        String clientId = dataStoreRequest.clientId();
        String key = dataStoreRequest.key();
        String value = masterDataStore.get(key);
        value = value == null ? "null" : value;

        SingleResult dataItem = new SingleResult(dataStoreRequest.messageId(), dataStoreRequest.clientId(), DataStoreSource.MEMORY, key, value);

        if (debug) logger.info("RequestHandler::handleGetMemVerb::", dataItem);

        storeServer.sendMessageToClientId(clientId, dataItem.toTextMessage());
    }

    private void handleSetIfNotExistsVerb(DataStoreRequest request) {

        if (debug) logger.info("RequestHandler::handleSetNotExistsVerb\n", request);

        if (!masterDataStore.exists(request.key())) {
            masterDataStore.set((SetRequest) request);
        }
    }

    private void handleSetAndBroadCastVerb(DataStoreRequest request) {

        if (debug) logger.info("RequestHandler::handleSetAndBroadCastVerb::", request);

        masterDataStore.set((SetRequest) request);

        SingleResult dataItem = new SingleResult(request.messageId(), request.clientId(), DataStoreSource.CLIENT, request.key(), request.payload());

        broadCastQueue.offer(new BroadCastSendData(request.key(), dataItem.toBroadcastTextMessage()));

    }

    @ServiceMethod
    public void turnOnVerbose() {
        this.verbose = true;
    }

    private void handleGetStats(final DataStoreRequest dataStoreRequest) {

        final long now = Timer.timer().time();

        utilityThreads.submit(new Runnable() {
            @Override
            public void run() {
                masterDataStore.sendStats(now);

            }
        });

        try {

            StatCount count;
            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Thread CPU Time " + Thread.currentThread().getName(), Sys.threadCPUTime());
            this.queue.put(count);

        } catch (Throwable ex) {
            logger.warn("Could not get thread cpu time");
        }

        utilityThreads.submit(new Runnable() {
            @Override
            public void run() {

                collectStats(now);

            }
        });

        utilityThreads.submit(new Runnable() {
            @Override
            public void run() {

                Sys.sleep(1000);

                queue.put(new StatsResults((StatsRequest) dataStoreRequest));

            }
        });

    }

    private void handleRemoveSource(DataStoreRequest dataStoreRequest) {

        masterDataStore.removeFromSource((RemoveRequest) dataStoreRequest);
    }

    @ServiceMethod
    public void collectStats(long now) {

        collectConfig(now);

        StatCount count;

        try {

            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Thread TIME USER  RequestHandler "
                    + Thread.currentThread().getName(), Sys.threadUserTime());
            this.queue.put(count);

            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Thread TIME CPU  RequestHandler "
                    + Thread.currentThread().getName(), Sys.threadCPUTime());
            this.queue.put(count);

            long num = Sys.availableProcessors();
            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "CPU availableProcessors", num);
            this.queue.put(count);

            num = Sys.freeMemory();
            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Memory freeMemory", num);
            this.queue.put(count);

            num = Sys.maxMemory();
            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Memory maxMemory", num);
            this.queue.put(count);

            num = Sys.totalMemory();
            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Memory totalMemory", num);
            this.queue.put(count);

            num = Sys.pendingFinalizationCount();
            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Memory pendingFinalizationCount", num);
            this.queue.put(count);

            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Load ", Sys.loadAverage());
            this.queue.put(count);

            MemoryUsage memoryUsage = Sys.heapMemoryUsage();
            num = memoryUsage.getCommitted();
            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Memory heapUsage committed", num);
            this.queue.put(count);

            num = memoryUsage.getInit();
            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Memory heapUsage init", num);
            this.queue.put(count);

            num = memoryUsage.getMax();
            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Memory heapUsage max", num);
            this.queue.put(count);

            num = memoryUsage.getUsed();
            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Memory heapUsage used", num);
            this.queue.put(count);

            memoryUsage = Sys.nonHeapMemoryUsage();
            num = memoryUsage.getCommitted();
            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Memory non-heap committed", num);
            this.queue.put(count);

            num = memoryUsage.getInit();
            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Memory non-heap init", num);
            this.queue.put(count);

            num = memoryUsage.getMax();
            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Memory non-heap max", num);
            this.queue.put(count);

            num = memoryUsage.getUsed();
            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Memory non-heap used", num);
            this.queue.put(count);

            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Thread count", Sys.threadCount());
            this.queue.put(count);

            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Thread started", Sys.threadsStarted());
            this.queue.put(count);

            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Thread peakCount", Sys.threadPeakCount());
            this.queue.put(count);

            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Thread daemon count", Sys.threadDaemonCount());
            this.queue.put(count);

            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "FILE max file descriptor count", Sys.maxFileDescriptorCount());
            this.queue.put(count);

            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "FILE open file descriptor count", Sys.openFileDescriptorCount());
            this.queue.put(count);

            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Memory committedVirtualMemorySize", Sys.committedVirtualMemorySize());
            this.queue.put(count);

            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Memory totalSwapSpaceSize", Sys.totalSwapSpaceSize());
            this.queue.put(count);

            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Memory freeSwapSpaceSize", Sys.freeSwapSpaceSize());
            this.queue.put(count);

            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "CPU processCpuTime", Sys.processCpuTime());
            this.queue.put(count);

            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "CPU systemCpuLoad", Sys.systemCpuLoad());
            this.queue.put(count);

            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Memory freePhysicalMemorySize", Sys.freePhysicalMemorySize());
            this.queue.put(count);

            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Memory totalPhysicalMemorySize", Sys.totalPhysicalMemorySize());
            this.queue.put(count);

            final List<GarbageCollectorMXBean> gc = Sys.gc();

            for (GarbageCollectorMXBean bean : gc) {
                if (bean.isValid()) {
                    String area = Str.joinObjects(' ', "GC", bean.getName(), "collection count");
                    count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, area, bean.getCollectionCount());
                    this.queue.put(count);
                    area = Str.joinObjects(' ', "GC", bean.getName(), "collection time");

                    count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, area, bean.getCollectionTime());
                    this.queue.put(count);

                }
            }

            num = Sys.uptime();
            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Time uptime", num);
            this.queue.put(count);

            num = Sys.startTime();
            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Time start time", num);
            this.queue.put(count);

            num = Timer.timer().time();
            count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Time now", num);
            this.queue.put(count);

        } catch (Throwable t) {

            if (debug) logger.info(t, "Unable to get JMX data ");
        }

        try {
            String sDir = masterDataStore.config().outputDirectory();
            File dir = new File(sDir);
            if (dir.exists()) {
                count = new StatCount(DataStoreSource.FILE_SYSTEM, Action.GET_STATS, "localDB FreeSpace " + dir, dir.getFreeSpace());
                this.queue.put(count);
                count = new StatCount(DataStoreSource.FILE_SYSTEM, Action.GET_STATS, "localDB TotalSpace " + dir, dir.getTotalSpace());
                this.queue.put(count);
                count = new StatCount(DataStoreSource.FILE_SYSTEM, Action.GET_STATS, "localDB UsableSpace " + dir, dir.getUsableSpace());
                this.queue.put(count);
            }

            sDir = config.logDirectory();
            dir = new File(sDir);
            if (dir.exists()) {
                count = new StatCount(DataStoreSource.FILE_SYSTEM, Action.GET_STATS, "log FreeSpace " + dir, dir.getFreeSpace());
                this.queue.put(count);
                count = new StatCount(DataStoreSource.FILE_SYSTEM, Action.GET_STATS, "log TotalSpace " + dir, dir.getTotalSpace());
                this.queue.put(count);
                count = new StatCount(DataStoreSource.FILE_SYSTEM, Action.GET_STATS, "log UsableSpace " + dir, dir.getUsableSpace());
                this.queue.put(count);
            }

        } catch (Throwable ex) {
            if (debug) logger.info(ex, "RequestHandler::Unable to send file stats");
        }

    }

    private void collectConfig(long now) {

        StatCount statCount;

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG websocketWorkerCount",
                config.websocketWorkerCount());
        queue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG dataStoreDrainerCount",
                config.dataStoreDrainerCount());
        queue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG broadcastInterval",
                config.broadcastInterval());
        queue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG broadcastInterval",
                config.broadcastInterval());
        queue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG maxFrameSize",
                config.maxFrameSize());
        queue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG port",
                config.port());
        queue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG batchResultSize",
                config.batchResultSize());
        queue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG statusInterval",
                config.statusInterval());
        queue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG debug",
                config.debug() ? 1 : 0);
        queue.put(statCount);

        statCount = new StatCount(now, DataStoreSource.SERVER,
                Action.NONE,
                "CONFIG logDirectory " + config.logDirectory(),
                config.logDirectory().length());
        queue.put(statCount);

    }

    private void handleClearStats(DataStoreRequest dataStoreRequest) {

        for (CallStatusTracker tracker : callStatusTrackers) {
            tracker.clearStats();
        }

        masterDataStore.clearStats();
    }

    private void handleRemove(DataStoreRequest dataStoreRequest) {

        RemoveRequest removeRequest = (RemoveRequest) dataStoreRequest;

        masterDataStore.remove(removeRequest);

    }

    public void handleCallFromClient(final String textData, final Object commChannel) {

        textBodyCall++;
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                doHandleCallFromClient(textData, commChannel);
            }
        });

        if (textBodyCall % 20 == 0) {
            StatCount count = new StatCount(DataStoreSource.SERVER, Action.NONE, "Call Counter TEXT" + Thread.currentThread().getName(), textBodyCall);
            queue.put(count);
        }
    }

    public void handleCallFromClient(final Map<String, String> message, final String uri, final Object commChannel) {

        mapBodyCall++;
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                doHandleCallFromClient(message, uri, commChannel);
            }
        });

        if (mapBodyCall % 20 == 0) {
            StatCount count = new StatCount(DataStoreSource.SERVER, Action.NONE, "Call Counter MAP" + Thread.currentThread().getName(), mapBodyCall);
            queue.put(count);
        }
    }

    private DataStoreRequest createRequest(String message) {
        try {
            return requestFromTextFactory.createRequest(message);
        } catch (Exception ex) {
            logger.error("Unable to parse message\n", message, "\n");

            logger.error(ex, "Unable to parse message");
            logger.error("Unable to parse message\n", message, "\n", "RECIEVED: \n\n\t", message.replaceAll(DELIMITER_STR, "\n\t"), "\n");
            return null;
        }
    }

    private DataStoreRequest createRequest(Map<String, String> message) {
        try {
            return requestFromMapFactory.createRequest(message);
        } catch (Exception ex) {
            logger.error("Unable to parse message\n", message, "\n");

            logger.error(ex, "Unable to parse message");
            logger.error("Unable to parse message\n", message, "\n", "RECIEVED: \n\n\t", message, "\n");
            return null;
        }
    }

    public void init(DataStoreServer dataStoreServer,
                     DataStoreServerConfig config,
                     MasterDataStore masterDataStore,
                     DataOutputQueue queue) {

        this.storeServer = dataStoreServer;
        this.config = config;
        this.masterDataStore = masterDataStore;
        this.queue = queue;

        services = Maps.safeMap(
                "server", storeServer,
                "this", storeServer,
                "admin", this,
                "dataStore", masterDataStore,
                "masterDataStore", masterDataStore,
                "storeServer", storeServer,
                "queue", queue
        );

        servicesDefinition = Maps.safeMap(
                "server", ClassMeta.classMeta(storeServer.getClass()),
                "this", ClassMeta.classMeta(storeServer.getClass()),
                "admin", ClassMeta.classMeta(this.getClass()),
                "dataStore", ClassMeta.classMeta(masterDataStore.getClass()),
                "masterDataStore", ClassMeta.classMeta(masterDataStore.getClass()),
                "storeServer", ClassMeta.classMeta(storeServer.getClass()),
                "queue", ClassMeta.classMeta(queue.getClass())
        );

        executorService = SimpleExecutors.threadPool(config.websocketWorkerCount(), "DS WebSocketWorker");
        utilityThreads = SimpleExecutors.threadPool("DS RequestHandler Utility");

        requestFromTextFactory = ProtocolConstants.textProtocolFactory();
        requestFromMapFactory = ProtocolConstants.mapProtocolFactory();

        startBroadcastDrainer();

    }

    private void doHandleCallFromClient(final String message, final Object commChannel) {

        try {
            if (debug) logger.info("RequestHandler:doHandleCallFromClient\n", ProtocolConstants.prettyPrintMessageWithLinesTabs(message));

            final DataStoreRequest dataStoreRequest = createRequest(message);

            if (debug) logger.info("RequestHandler:doHandleCallFromClient", "REQUEST=", dataStoreRequest);

            storeServer.registerOutputHandler(dataStoreRequest.clientId(), commChannel);

            mainRequestHandler(dataStoreRequest);
        }
        catch (Exception ex) {
            logger.error(ex, "RequestHandler::Unable to handle request");
            logger.error("RequestHandler::Unable to handle request TEXT DATA\n",
            ProtocolConstants.prettyPrintMessageWithLinesTabs(message));
        }

    }

    private void doHandleCallFromClient(final Map<String, String> message, String uri, final Object commChannel) {
        try {
            if (debug) logger.info("RequestHandler::doHandleCallFromClient", message);

            final DataStoreRequest dataStoreRequest = createRequest(message);

            if (debug) logger.info("RequestHandler::doHandleCallFromClient", dataStoreRequest);

            storeServer.registerOutputHandler(dataStoreRequest.clientId(), commChannel);
            mainRequestHandler(dataStoreRequest);
        }
        catch (Exception ex) {
            logger.error(ex, "RequestHandler::Unable to handle request");
            logger.error("RequestHandler::Unable to handle request MAP DATA", message);
        }

    }

    private void startBroadcastDrainer() {

        storeServer.startWorker("Broadcast handler",
                new Runnable() {
                    @Override
                    public void run() {
                        runBroadcastProcess();
                    }
                }
        );

    }

    private void runBroadcastProcess() {

        while (true) {
            Sys.sleep(config.broadcastInterval());

            Map<String, BroadCastSendData> map = new LinkedHashMap();

            BroadCastSendData broadCastSendData = broadCastQueue.poll();

            while (broadCastSendData != null) {

                map.put(broadCastSendData.key, broadCastSendData);
                broadCastSendData = broadCastQueue.poll();

            }

            for (String key : map.keySet()) {
                storeServer.sendToAllClients(map.get(key).reply);
            }
        }
    }

    public void handleCallWithMap(String ipAddress, Map<String, String> message, String uri,  Object commChannel ) {

        if (debug) {
            logger.info("RequestHandler::handleCallWithMap()", ipAddress, message, uri, commChannel);

        }

        if (!message.containsKey(ProtocolConstants.MESSAGE_ID_MAP_KEY)) {
            message.put(ProtocolConstants.MESSAGE_ID_MAP_KEY, "" + (messageIdGenerator++));
        }

        if (!message.containsKey(ProtocolConstants.CLIENT_ID_MAP_KEY)) {
            message.put(ProtocolConstants.CLIENT_ID_MAP_KEY, "generatedClientId" + ipAddress);
        }

        handleCallFromClient(message, uri, commChannel);

    }

    private void handleSetSource(DataStoreRequest dataStoreRequest) {
        SetRequest request = (SetRequest) dataStoreRequest;
        masterDataStore.setSource(request);
    }

    private void handleGetSource(DataStoreRequest dataStoreRequest) {
        GetRequest request = (GetRequest) dataStoreRequest;
        masterDataStore.getSource(request);
    }

    private void handleSetBatchIfNotExists(DataStoreRequest dataStoreRequest) {

        BatchSetRequest request = (BatchSetRequest) dataStoreRequest;
        final List<String> requestKeys = request.keys();
        final List<String> requestValues = request.values();
        int size = requestKeys.size();

        List<String> notExistKeys = new ArrayList<>(size);
        List<String> notExistValues = new ArrayList<>(size);
        for (int x = 0; x < size; x++) {
            String key = requestKeys.get(x);
            if (!masterDataStore.exists(key)) {
                notExistKeys.add(key);
                notExistValues.add(requestValues.get(x));
            }
        }

        BatchSetRequest shortBsr = new BatchSetRequest(
                request.source(), request.action(), request.messageId(), request.clientId(),
                notExistKeys, notExistValues);
        masterDataStore.addAll(shortBsr);
    }

    private void handleSetBatch(DataStoreRequest dataStoreRequest) {

        BatchSetRequest request = (BatchSetRequest) dataStoreRequest;

        masterDataStore.addAll(request);

    }

    private void handleBatchRead(DataStoreRequest request) {

        try {

            ReadBatchRequest batchRequest = (ReadBatchRequest) request;

            int chunkSize = config.batchResultSize() * 2;

            List<String> keysToFetch = new ArrayList<>();
            for (String key : batchRequest.keys()) {
                keysToFetch.add(key);
                if (keysToFetch.size() > chunkSize) {

                    masterDataStore.batchRead(
                            new ReadBatchRequest(batchRequest, new ArrayList<>(keysToFetch)));

                    keysToFetch.clear();
                }

            }

            if (keysToFetch.size() > 0) {
                masterDataStore.batchRead(
                        new ReadBatchRequest(batchRequest, new ArrayList<>(keysToFetch)));

            }

        } catch (Exception ex) {

            logger.error(ex, "Unable to handle bulk send", request.clientId(), request.payload());
        }
    }

    private void handleSearchVerb(DataStoreRequest dataStoreRequest) {
        masterDataStore.search((SearchRequest) dataStoreRequest);
    }

    public void stop() {

        try {
            if (utilityThreads != null) {
                utilityThreads.shutdownNow();
            }
        } catch (Exception ex) {

            if (debug) logger.error(ex, "error shutting down utilityThreads executor service");
        }

        try {
            if (executorService != null) {
                executorService.shutdownNow();
            }
        } catch (Exception ex) {

            if (debug) logger.error(ex, "error shutting down executor service");
        }

    }

    private static class CallStatus {

        Action action;
        int count;
    }

    private static class CallStatusTracker {
        long numberOfCallsSinceLastSendAttempt = 0;
        long totalNumCalls = 0;
        long lastStatSend = 0;
        Map<Action, CallStatus> callStatusMap = new HashMap<>();
        private Logger logger = configurableLogger(this.getClass());

        void addCall(Action action, DataOutputQueue queue) {
            CallStatus callStatus = callStatusMap.get(action);

            if (callStatus == null) {
                callStatus = new CallStatus();
                callStatus.action = action;
                callStatus.count = 1;
                callStatusMap.put(action, callStatus);
            } else {
                callStatus.count++;
            }

            numberOfCallsSinceLastSendAttempt++;
            if (numberOfCallsSinceLastSendAttempt > 100) {
                totalNumCalls += numberOfCallsSinceLastSendAttempt;
                numberOfCallsSinceLastSendAttempt = 0;

                long now = Timer.timer().time();
                long duration = now - lastStatSend;

                /* Only send every 20 seconds. */
                if (duration > 20_000) {
                    for (CallStatus stat : callStatusMap.values()) {
                        StatCount count = new StatCount(now, DataStoreSource.SERVER,
                                stat.action,
                                "CALL COUNT " + Thread.currentThread().getName(),
                                stat.count);
                        queue.put(count);
                    }

                    StatCount count = new StatCount(now, DataStoreSource.SERVER,
                            Action.NONE,
                            "CALL COUNT TOTAL COUNT FOR " + Thread.currentThread().getName(),
                            totalNumCalls);
                    queue.put(count);
                    lastStatSend = now;

                    try {

                        count = new StatCount(now, DataStoreSource.SERVER, Action.GET_STATS, "Thread CPU Time " + Thread.currentThread().getName(), Sys.threadCPUTime());
                        queue.put(count);

                    } catch (Throwable ex) {

                        logger.info(ex, "Could not get CPU time");
                    }

                }
            }
        }

        public void clearStats() {

            numberOfCallsSinceLastSendAttempt = 0;
            totalNumCalls = 0;
            lastStatSend = 0;
            callStatusMap.clear();
        }
    }

    public static class BroadCastSendData {
        private String key;
        private String reply;

        public BroadCastSendData(String key, String reply) {
            this.key = key;
            this.reply = reply;
        }

    }

    public Map<String, ClassMeta<?>> getServicesDefinition() {
        return servicesDefinition;
    }

}


/*
 * Copyright 2013-2014 Richard M. Hightower
 * 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.
 *
 * __________                              _____          __   .__
 * \______   \ ____   ____   ____   /\    /     \ _____  |  | _|__| ____    ____
 *  |    |  _//  _ \ /  _ \ /    \  \/   /  \ /  \\__  \ |  |/ /  |/    \  / ___\
 *  |    |   (  <_> |  <_> )   |  \ /\  /    Y    \/ __ \|    <|  |   |  \/ /_/  >
 *  |______  /\____/ \____/|___|  / \/  \____|__  (____  /__|_ \__|___|  /\___  /
 *         \/                   \/              \/     \/     \/       \//_____/
 *      ____.                     ___________   _____    ______________.___.
 *     |    |____ ___  _______    \_   _____/  /  _  \  /   _____/\__  |   |
 *     |    \__  \\  \/ /\__  \    |    __)_  /  /_\  \ \_____  \  /   |   |
 * /\__|    |/ __ \\   /  / __ \_  |        \/    |    \/        \ \____   |
 * \________(____  /\_/  (____  / /_______  /\____|__  /_______  / / ______|
 *               \/           \/          \/         \/        \/  \/
 */

package org.boon.validation.validators;

/**
 * Abstract class for range validation.
 */
@SuppressWarnings ( "unchecked" )
public abstract class AbstractRangeValidator extends BaseValidator {

    /* The min value for comparison. */
    private Comparable realMin;
    /* The max value for comparison. */
    private Comparable realMax;
    /* Has this been initialized? */
    private boolean isInitialized;

    /* Allows this base class to be initialized by the subclasses. */
    protected void init( Comparable min, Comparable max ) {
        this.realMin = min;
        this.realMax = max;
        assert min.compareTo( max ) < 0;
        isInitialized = true;
    }

    /* Checks to see if the value is less than the min. */
    protected boolean isValueGreaterThanMin( Comparable value ) {
        if ( realMin == null ) {
            return true;
        }
        return value.compareTo( realMin ) >= 0;
    }

    /* Checks to see if the value is greater than the max. */
    protected boolean isValueLessThanMax( Comparable value ) {
        if ( realMax == null ) {
            return true;
        }
        return value.compareTo( realMax ) <= 0;
    }

    public boolean isInitialized() {
        return isInitialized;
    }
}


/*
 * Copyright 2013-2014 Richard M. Hightower
 * 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.
 *
 * __________                              _____          __   .__
 * \______   \ ____   ____   ____   /\    /     \ _____  |  | _|__| ____    ____
 *  |    |  _//  _ \ /  _ \ /    \  \/   /  \ /  \\__  \ |  |/ /  |/    \  / ___\
 *  |    |   (  <_> |  <_> )   |  \ /\  /    Y    \/ __ \|    <|  |   |  \/ /_/  >
 *  |______  /\____/ \____/|___|  / \/  \____|__  (____  /__|_ \__|___|  /\___  /
 *         \/                   \/              \/     \/     \/       \//_____/
 *      ____.                     ___________   _____    ______________.___.
 *     |    |____ ___  _______    \_   _____/  /  _  \  /   _____/\__  |   |
 *     |    \__  \\  \/ /\__  \    |    __)_  /  /_\  \ \_____  \  /   |   |
 * /\__|    |/ __ \\   /  / __ \_  |        \/    |    \/        \ \____   |
 * \________(____  /\_/  (____  / /_______  /\____|__  /_______  / / ______|
 *               \/           \/          \/         \/        \/  \/
 */

package org.boon.core.reflection;

import org.boon.Boon;
import org.boon.Exceptions;
import org.boon.Lists;
import org.boon.core.Conversions;
import org.boon.core.Typ;
import org.boon.core.TypeType;
import org.boon.core.reflection.fields.FieldAccessMode;
import org.boon.core.reflection.fields.FieldsAccessor;
import org.boon.core.value.ValueContainer;
import org.boon.primitive.CharBuf;

import java.lang.invoke.ConstantCallSite;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.*;

import static org.boon.Boon.className;
import static org.boon.Boon.puts;
import static org.boon.Exceptions.die;
import static org.boon.Exceptions.handle;
import static org.boon.core.Conversions.coerce;
import static org.boon.core.TypeType.gatherTypes;
import static org.boon.core.reflection.MapObjectConversion.*;

/**
 *
 * @author Rick Hightower
 *         Created by Richard on 2/17/14.
 */
public class Invoker {

    public static Object invokeOverloadedFromObject(Object object, String name, Object args) {
        return invokeOverloadedFromObject(false, null, null, object, name, args);
    }

    public static Object invokeOverloadedFromObject(boolean respectIgnore, String view,
                                                    Set<String> ignoreProperties,
                                                    Object object, String name,
                                                    Object args) {

        try {
            if (args instanceof Map) {
                return invokeOverloadedFromList(respectIgnore, view, ignoreProperties, object, name, Lists.list(args));
            } else if (args instanceof List) {
                List list = (List) args;
                ClassMeta classMeta = ClassMeta.classMeta(object.getClass());
                MethodAccess m = classMeta.method(name);
                if (m.parameterTypes().length == 1 && list.size() > 0) {

                    Object firstArg = list.get(0);
                    if (firstArg instanceof Map || firstArg instanceof List) {
                        return invokeOverloadedFromList(respectIgnore, view, ignoreProperties, object, name, list);

                    } else {
                        return invokeOverloadedFromList(respectIgnore, view, ignoreProperties, object, name, Lists.list(args));
                    }
                } else {

                    return invokeOverloadedFromList(respectIgnore, view, ignoreProperties, object, name, list);

                }
            } else if (args == null) {
                return invoke(object, name);
            } else {
                return invokeOverloadedFromList(respectIgnore, view, ignoreProperties, object, name, Lists.list(args));
            }
        }
        catch(Exception ex) {
            return Exceptions.handle(Object.class, ex, "Unable to invoke method object", object, "name", name, "args", args);
        }
    }

    public static Object invokeFromObject(Object object, String name, Object args) {
        return invokeFromObject(false, null, null, object, name, args);

    }

    public static Object invokeFromObject(Class<?> cls, String name, Object args) {
        return invokeFromObject(false, null, null, cls, null, name, args);

    }

    /**
     * Invokes method from list or map depending on what the Object arg is.
     * @param object
     * @param method
     * @param args
     * @return
     */
    public static Object invokeMethodFromObjectArg(Object object, MethodAccess method, Object args) {
        return invokeMethodFromObjectArg(false, null, null, object, method, args);

    }

    public static Object invokeMethodFromObjectArg(boolean respectIgnore, String view, Set<String> ignoreProperties,
                                          Object object, MethodAccess method, Object args) {

        try {
            if (args instanceof Map) {
                return invokeMethodFromList(respectIgnore, view, ignoreProperties, object, method, Lists.list(args));
            } else if (args instanceof List) {
                List list = (List) args;

                Class<?>[] paramTypes = method.parameterTypes();

                if (paramTypes.length == 1 && list.size() > 0) {

                    Class<?> firstParamType = paramTypes[0];
                    Object firstArg = list.get(0);

                    if ( firstArg instanceof Map ) {
                        return invokeMethodFromList(respectIgnore, view, ignoreProperties, object, method, list);

                    }

                    else if (firstArg instanceof List &&
                            !Typ.isCollection(firstParamType)
                            && !firstParamType.isArray()) {
                        return invokeMethodFromList(respectIgnore, view, ignoreProperties, object, method, list);
                    }
                    else {
                        return invokeMethodFromList(respectIgnore, view, ignoreProperties, object, method,
                                Lists.list(args));
                    }
                } else {

                    return invokeMethodFromList(respectIgnore, view, ignoreProperties, object, method, list);

                }
            } else if (args == null) {
                return method.invoke(object);
            } else {
                return invokeMethodFromList(respectIgnore, view, ignoreProperties, object, method, Lists.list(args));
            }
        }catch (Exception ex) {
            return Exceptions.handle(Object.class, ex, "Unable to invoke method object", object, "method", method, "args", args);

        }

    }

    public static Object invokeFromObject(boolean respectIgnore, String view, Set<String> ignoreProperties,
                                          Object object, String name, Object args) {
        return invokeFromObject(respectIgnore, view, ignoreProperties, object.getClass(), object, name, args);

    }

    public static Object invokeFromObject(boolean respectIgnore, String view, Set<String> ignoreProperties,
                                          Class<?> cls, Object object, String name, Object args) {

        try {
            if (args instanceof Map) {
                return invokeFromList(respectIgnore, view, ignoreProperties, cls, object, name, Lists.list(args));
            } else if (args instanceof List) {
                List list = (List) args;
                ClassMeta classMeta = ClassMeta.classMeta(cls);
                MethodAccess m = classMeta.method(name);
                if (m.parameterTypes().length == 1 && list.size() > 0) {

                    Object firstArg = list.get(0);
                    if (firstArg instanceof Map || firstArg instanceof List) {
                        return invokeFromList(respectIgnore, view, ignoreProperties, cls, object, name, list);

                    } else {
                        final Class<?> aClass = m.parameterTypes()[0];
                        final TypeType type = TypeType.getType(aClass);
                        switch (type) {
                            case INSTANCE:
                                return invokeFromList(respectIgnore, view, ignoreProperties, cls, object, name, Lists.list(args));

                            default:
                                return invokeFromList(respectIgnore, view, ignoreProperties, cls, object, name, list);

                        }
                    }
                } else {

                    return invokeFromList(respectIgnore, view, ignoreProperties, cls, object, name, list);

                }
            } else if (args == null) {
                return invoke(object, name);
            } else {
                return invokeFromList(respectIgnore, view, ignoreProperties, cls, object, name, Lists.list(args));
            }
        } catch (Exception ex) {
            return Exceptions.handle(Object.class, ex, "Unable to invoke method object", object, "name", name, "args", args);
        }

    }

    public static Object invokeFromList(Object object, String name, List<?> args) {
        return invokeFromList(true, null, null, object, name, args);
    }

    public static Object invokeFromList(Class<?> cls, String name, List<?> args) {
        return invokeFromList(true, null, null, cls, null, name, args);
    }

    public static Object invokeFromList(boolean respectIgnore, String view, Set<String> ignoreProperties, Object object, String name, List<?> args) {

        return invokeFromList(respectIgnore, view, ignoreProperties, object.getClass(), object, name, args);

    }

    private static Object[] convertArguments(boolean respectIgnore, String view, Set<String> ignoreProperties,
                                                 Object object,  List<?> argsList, MethodAccess methodAccess
                                                  ) {

        List<Object> convertedArguments = new ArrayList(argsList);
        Class<?>[] parameterTypes = methodAccess.parameterTypes();

        boolean[] flag = new boolean[1];

        if (convertedArguments.size() != parameterTypes.length) {
            return die(Object[].class, "The list size does not match the parameter" +
                    " length of the method. Unable to invoke method", methodAccess.name(), "on object", object, "with arguments", convertedArguments);
        }

        FieldsAccessor fieldsAccessor = FieldAccessMode.FIELD.create(true);

        for (int index = 0; index < parameterTypes.length; index++) {

            if (!matchAndConvertArgs(respectIgnore, view, fieldsAccessor, convertedArguments, methodAccess, parameterTypes, index, ignoreProperties, flag, true)) {
                return die(Object[].class, index, "Unable to invoke method as argument types did not match",
                        methodAccess.name(), "on object", object, "with arguments", convertedArguments,
                        "\nValue at index = ", convertedArguments.get(index));
            }

        }

        return convertedArguments.toArray(new Object[convertedArguments.size()]);
    }

    public static Object invokeFromList(boolean respectIgnore, String view, Set<String> ignoreProperties,
                                        Class<?> cls, Object object,
                                        String name, List<?> argsList) {

        Object[] finalArgs=null;
        ClassMeta classMeta;
        MethodAccess methodAccess;

        classMeta = ClassMeta.classMeta(cls);
        methodAccess = classMeta.method(name);

        try {
            finalArgs = convertArguments(respectIgnore, view, ignoreProperties,
                    object, argsList, methodAccess
                    );

            return methodAccess.invoke(object, finalArgs);

        } catch (Exception ex) {

            if (methodAccess != null)  {

                CharBuf buf = CharBuf.create(200);
                buf.addLine();
                buf.multiply('-', 10).add("FINAL ARGUMENTS").multiply('-', 10).addLine();
                if (finalArgs!=null) {
                    for (Object o : finalArgs) {
                        buf.puts("argument type    ", className(o));
                    }
                }

                buf.multiply('-', 10).add("INVOKE METHOD").add(methodAccess).multiply('-', 10).addLine();
                buf.multiply('-', 10).add("INVOKE METHOD PARAMS").multiply('-', 10).addLine();
                for (Class<?> c : methodAccess.parameterTypes()) {
                    buf.puts("constructor type ", c);
                }

                buf.multiply('-', 35).addLine();

                if (Boon.debugOn()) {
                    puts(buf);
                }

                Boon.error( ex, "unable to create invoke method", buf );

                return  handle(Object.class, ex, buf.toString(),
                        "\nconstructor parameter types", methodAccess.parameterTypes(),
                        "\noriginal args\n", argsList,
                        "\nlist args after conversion", finalArgs, "\nconverted types\n",
                        gatherTypes(finalArgs),
                        "original types\n", gatherTypes(argsList), "\n");
            } else {
                return  handle(Object.class, ex,
                        "\nlist args after conversion", finalArgs, "types",
                        gatherTypes(finalArgs),
                        "\noriginal args", argsList,
                        "original types\n", gatherTypes(argsList), "\n");

            }

        }

    }

    public static Object invokeMethodFromList(boolean respectIgnore, String view, Set<String> ignoreProperties,
                                              Object object, MethodAccess method, List<?> argsList) {

        try {

            if (argsList == null && method.parameterTypes().length == 0) {
                return method.invoke(object);
            } else {

                Object [] finalArgs = convertArguments(respectIgnore, view, ignoreProperties,
                        object, argsList, method
                );

                return method.invoke(object, finalArgs);
            }
        }catch (Exception ex) {
            return Exceptions.handle(Object.class, ex, "Unable to invoke method object", object, "method", method, "args", argsList);
        }

    }

    public static Object invokeEither(Object object, String name, Object... args) {
        if (object instanceof Class) {
            return invoke((Class<?>)object, name, args);
        } else {
            return invoke(object, name, args);
        }
    }

    public static Object invoke(Object object, String name, Object... args) {
        return ClassMeta.classMetaUnTyped(object.getClass()).invokeUntyped(object, name, args);
    }

    public static MethodAccess invokeMethodAccess( Object object, String name ) {
        return ClassMeta.classMeta(object.getClass()).invokeMethodAccess(name);
    }

    public static MethodAccess invokeMethodAccess(Class<?> cls, String name) {
        return ClassMeta.classMeta(cls).invokeMethodAccess(name);
    }

    public static Object invoke(Class cls, String name, Object... args) {
        return ClassMeta.classMeta(cls).invokeStatic(name, args);
    }

    public static Object invokeOverloaded(Object object, String name, Object... args) {
        ClassMeta classMeta = ClassMeta.classMeta(object.getClass());
        Iterable<MethodAccess> invokers = classMeta.methods(name);

        for (MethodAccess m : invokers) {
            if (m.respondsTo(args)) {
                return m.invoke(object, args);
            }
        }
        return die(Object.class, "Unable to invoke method", name, "on object", object, "with arguments", args);
    }

    public static Object invokeOverloadedFromList(Object object, String name, List<?> args) {

        return invokeOverloadedFromList(true, null, null, object, name, args);

    }

    public static Object invokeOverloadedFromList(boolean respectIgnore,
                                                  String view,
                                                  Set<String> ignoreProperties,
                                                  Object object,
                                                  String name,
                                                  List<?> args) {
        ClassMeta classMeta = ClassMeta.classMeta(object.getClass());
        Iterable<MethodAccess> invokers = classMeta.methods(name);

        List<Object> list = new ArrayList(args);
        FieldsAccessor fieldsAccessor = FieldAccessMode.FIELD.create(true);

        boolean[] flag = new boolean[1];

        MethodAccess method = lookupOverloadedMethod(respectIgnore, view, ignoreProperties, invokers, list, fieldsAccessor, flag, false);

        if (method == null) {
            method = lookupOverloadedMethod(respectIgnore, view, ignoreProperties, invokers, list, fieldsAccessor, flag, true);
        }

        if (method!=null) {
            return method.invoke(object, list.toArray(new Object[list.size()]));
        } else {
            return die(Object.class, "Unable to invoke method", name, "on object", object, "with arguments", args);
        }
    }

    private static
        MethodAccess
        lookupOverloadedMethod(boolean respectIgnore, String view, Set<String> ignoreProperties,
                                               Iterable<MethodAccess> invokers, List<Object> list,
                                               FieldsAccessor fieldsAccessor, boolean[] flag, boolean loose) {

        MethodAccess method = null;

        loop:
        for (MethodAccess m : invokers) {
            Class<?>[] parameterTypes = m.parameterTypes();
            if (!(parameterTypes.length == list.size())) {
                continue;
            }
            for (int index = 0; index < parameterTypes.length; index++) {
                if (!matchAndConvertArgs(respectIgnore, view, fieldsAccessor, list, m,
                        parameterTypes, index, ignoreProperties, flag, loose)) {

                    continue loop;
                }
            }
            method = m;
            break;
        }

        return method;
    }

    public static void invokeMethodWithAnnotationNoReturn(Object object, String annotation) {
        invokeMethodWithAnnotationWithReturnType(object, annotation, void.class);
    }

    public static void invokeMethodWithAnnotationWithReturnType(Object object, String annotation, Class<?> returnType) {
        invokeMethodWithAnnotationWithReturnType(object.getClass(), object, annotation, returnType);
    }

    public static void invokeMethodWithAnnotationWithReturnType(Class<?> type, Object object, String annotation, Class<?> returnType) {
        ClassMeta classMeta = ClassMeta.classMeta(type);
        Iterable<MethodAccess> iterate = classMeta.methods();
        for (MethodAccess m : iterate) {
            if (m.hasAnnotation(annotation)) {
                    if (m.parameterTypes().length == 0 && m.returnType() == void.class) {
                        m.invoke(object);
                        break;
                    }
            }
        }
    }

    public static <T> boolean invokeBooleanReturn(Object object, T v) {
        Class cls;
        Object instance = null;
        if (object instanceof  Class) {
            cls = (Class) object;
        } else {
            cls = object.getClass();
            instance = object;
        }

        ClassMeta meta = ClassMeta.classMeta(cls);
        return meta.invokePredicate(instance, v);

    }

    public static Object invokeReducer(Object object, Object sum, Object value) {
        if (object instanceof  Class) {
            ClassMeta meta = ClassMeta.classMeta((Class<?>)object);
            return meta.invokeReducer(null, sum, value);
        } else {
            ClassMeta meta = ClassMeta.classMeta(object.getClass());

            return meta.invokeReducer(object, sum, value);

        }
    }

    public static Object invokeFunction(Object object, Object arg) {

        if (object instanceof  Class) {
            ClassMeta meta = ClassMeta.classMeta((Class<?>)object);
            return meta.invokeFunction(null, arg);
        } else {
            ClassMeta meta = ClassMeta.classMeta(object.getClass());

            return meta.invokeFunction(object, arg);

        }
    }

    public static MethodAccess invokeFunctionMethodAccess(Object object) {

        if (object instanceof  Class) {
            ClassMeta meta = ClassMeta.classMeta((Class<?>)object);
            return meta.invokeFunctionMethodAccess();
        } else {
            ClassMeta meta = ClassMeta.classMeta(object.getClass());

            return meta.invokeFunctionMethodAccess();

        }
    }

    public static ConstantCallSite invokeReducerLongIntReturnLongMethodHandle(Object object ) {

            ClassMeta meta = ClassMeta.classMeta(object.getClass());
            return meta.invokeReducerLongIntReturnLongMethodHandle(object);
    }

    public  static <T> ConstantCallSite invokeReducerLongIntReturnLongMethodHandle(T object, String methodName ) {

        ClassMeta meta = ClassMeta.classMeta(object.getClass());
        return meta.invokeReducerLongIntReturnLongMethodHandle(object, methodName);
    }

    public static Method invokeReducerLongIntReturnLongMethod(Object object ) {

        ClassMeta meta = ClassMeta.classMeta(object.getClass());
        return meta.invokeReducerLongIntReturnLongMethod(object);
    }

    public  static <T> Method invokeReducerLongIntReturnLongMethod(T object, String methodName ) {

        ClassMeta meta = ClassMeta.classMeta(object.getClass());
        return meta.invokeReducerLongIntReturnLongMethod(object, methodName);
    }

    /**
     * This converts/coerce a constructor argument to the given parameter type.
     *
     * REFACTOR:
     * This method was automatically refactored and its functionality gets duplicated in a few places.
     * Namely Invoker lib. It needs to be documented. Refactored to use org.boon.core.TypeType.
     * And code coverage. I have used it on several projects and have modified to work on
     * edge cases for certain customers and have not updated the unit test.
     * This method is beastly and important. It is currently 250 lines of code.
     * It started off small, and kept getting added to. It needs love, but it was a bitch to write.
     * REFACTOR
     *
     * @param view honor views for fields
     * @param fieldsAccessor how we are going to access the fields (by field, by property, combination)
     * @param ignoreSet a set of properties to ignore
     * @param respectIgnore  honor @JsonIgnore, transients, etc. of the field
     * @param convertedArgumentList   arguments being converted to match parameter types
     * @param methodAccess    constructor
     * @param parameterTypes   parameterTypes
     * @param index           index of argument
     * @return   true or false
     */
    public static boolean matchAndConvertArgs( boolean respectIgnore,
                                               String view,
                                               FieldsAccessor fieldsAccessor,
                                               List<Object> convertedArgumentList,
                                               BaseAccess methodAccess,
                                               Class[] parameterTypes,
                                               int index,
                                               Set<String> ignoreSet,
                                               boolean[] flag, boolean loose) {

        Object value;

        try {

            Class parameterClass;
            Object item;

            parameterClass = parameterTypes[index];
            item = convertedArgumentList.get( index );

            final TypeType parameterType = TypeType.getType(parameterClass);

            if ( item instanceof ValueContainer) {
                item = ( ( ValueContainer ) item ).toValue();

                convertedArgumentList.set( index, item );
            }

            if (item == null) {
                return true;
            }

            switch (parameterType) {
                case INT:
                case SHORT:
                case BYTE:
                case BOOLEAN:
                case CHAR:
                case FLOAT:
                case DOUBLE:
                case LONG:
                    if (item == null) {
                        return false;
                    }

                case INTEGER_WRAPPER:
                case BYTE_WRAPPER:
                case SHORT_WRAPPER:
                case BOOLEAN_WRAPPER:
                case CHAR_WRAPPER:
                case FLOAT_WRAPPER:
                case DOUBLE_WRAPPER:
                case CHAR_SEQUENCE:
                case NUMBER:
                case LONG_WRAPPER:

                    if (!loose ) {
                        if (item instanceof Number) {
                            value = Conversions.coerceWithFlag(parameterType, parameterClass, flag, item );
                            convertedArgumentList.set( index, value );

                            return flag[0];
                        } else {
                            return false;
                        }

                    } else {
                        value = Conversions.coerceWithFlag(parameterType, parameterClass, flag, item );
                        convertedArgumentList.set( index, value );

                        return flag[0];

                    }

                case CLASS:
                case ENUM:
                case STRING:
                    if (!loose && !(item instanceof CharSequence)) {
                        return false;
                    }

                    value = Conversions.coerceWithFlag(parameterType, parameterClass, flag, item );

                    if (flag[0] == false) {
                        return false;
                    }
                    convertedArgumentList.set( index, value );
                    return true;

                case MAP:
                case VALUE_MAP:

                    if (item instanceof Map) {
                        Map itemMap = (Map)item;

                    /* This code creates a map based on the parameterized types of the constructor arg.
                     *  This does ninja level generics manipulations and needs to be captured in some
                     *  reusable way.
                      * */
                        java.lang.reflect.Type type = methodAccess.getGenericParameterTypes()[index];
                        if ( type instanceof ParameterizedType) {
                            ParameterizedType pType = (ParameterizedType) type;
                            Class<?> keyType = (Class<?>) pType.getActualTypeArguments()[0];

                            Class<?> valueType = (Class<?>) pType.getActualTypeArguments()[1];

                            Map newMap = Conversions.createMap(parameterClass, itemMap.size());

                    /* Iterate through the map items and convert the keys/values to match
                    the parameterized constructor parameter args.
                     */

                            for (Object o : itemMap.entrySet()) {
                                Map.Entry entry = (Map.Entry) o;

                                Object key = entry.getKey();
                                value = entry.getValue();

                                key = ValueContainer.toObject(key);

                                value = ValueContainer.toObject(value);

                        /* Here is the actual conversion from a list or a map of some object.
                        This can be captured in helper method the duplication is obvious.
                         */
                                if (value instanceof List) {
                                    value = fromList(respectIgnore, view, fieldsAccessor, (List) value, valueType, ignoreSet);

                                } else if (value instanceof Map) {
                                    value = fromMap(respectIgnore, view, fieldsAccessor, (Map) value, valueType, ignoreSet);

                                } else {
                                    value = coerce(valueType, value);
                                }

                                if (key instanceof List) {
                                    key = fromList(respectIgnore, view, fieldsAccessor, (List) key, keyType, ignoreSet);

                                } else if (value instanceof Map) {
                                    key = fromMap(respectIgnore, view, fieldsAccessor, (Map) key, keyType, ignoreSet);

                                } else {
                                    key = coerce(keyType, key);
                                }

                                newMap.put(key, value);
                            }
                            convertedArgumentList.set(index, newMap);
                            return true;
                        }
                    }
                    break;
                case INSTANCE:
                    if ( parameterClass.isInstance( item ) ) {
                        return true;
                    }

                    if (item instanceof Map) {
                        item = fromMap( respectIgnore, view, fieldsAccessor, ( Map<String, Object> ) item, parameterClass, ignoreSet );
                        convertedArgumentList.set( index, item );
                        return true;
                    } else if ( item instanceof List ) {

                        List<Object> listItem = null;

                        listItem =      ( List<Object> ) item;

                        value = fromList(respectIgnore, view, fieldsAccessor, listItem, parameterClass, ignoreSet );

                        convertedArgumentList.set( index, value );
                        return true;

                    } else {
                        convertedArgumentList.set( index, coerce( parameterClass, item ) );
                        return true;
                    }
                    //break;
                case INTERFACE:
                case ABSTRACT:
                    if ( parameterClass.isInstance( item ) ) {
                        return true;
                    }

                    if (item instanceof Map) {

                        /** Handle conversion of user define interfaces. */
                        String className = (String) ((Map) item).get("class");
                        if (className != null) {
                            item = fromMap(respectIgnore, view, fieldsAccessor, (Map<String, Object>) item, Reflection.loadClass(className), ignoreSet);
                            convertedArgumentList.set(index, item);
                            return true;
                        } else {
                            return false;
                        }

                    }
                    break;

                case SET:
                case COLLECTION:
                case LIST:
                    if (item instanceof List ) {

                        List<Object> itemList = ( List<Object> ) item;

                        /* Items have stuff in it, the item is a list of lists.
                         * This is like we did earlier with the map.
                         * Here is some more ninja generics Java programming that needs to be captured in one place.
                         * */
                        if ( itemList.size() > 0 && (itemList.get( 0 ) instanceof List ||
                                itemList.get(0) instanceof ValueContainer)  ) {

                            /** Grab the generic type of the list. */
                            java.lang.reflect.Type type = methodAccess.getGenericParameterTypes()[index];

                            /*  Try to pull the generic type information out so you can create
                               a strongly typed list to inject.
                             */
                            if ( type instanceof ParameterizedType ) {
                                ParameterizedType pType = ( ParameterizedType ) type;

                                Class<?> componentType;
                                if (! (pType.getActualTypeArguments()[0] instanceof Class)) {
                                    componentType = Object.class;
                                } else {
                                    componentType = (Class<?>) pType.getActualTypeArguments()[0];
                                }

                                Collection newList =  Conversions.createCollection( parameterClass, itemList.size() );

                                for ( Object o : itemList ) {
                                    if ( o instanceof ValueContainer ) {
                                        o = ( ( ValueContainer ) o ).toValue();
                                    }

                                    if (componentType==Object.class) {
                                        newList.add(o);
                                    } else {

                                        List fromList = ( List ) o;
                                        o = fromList( respectIgnore, view, fieldsAccessor, fromList, componentType, ignoreSet );
                                        newList.add( o );
                                    }
                                }
                                convertedArgumentList.set( index, newList );
                                return true;

                            }
                        } else {

                        /* Just a list not a list of lists so see if it has generics and pull out the
                        * type information and created a strong typed list. This looks a bit familiar.
                        * There is a big opportunity for some reuse here. */
                            java.lang.reflect.Type type = methodAccess.getGenericParameterTypes()[index];
                            if ( type instanceof ParameterizedType ) {
                                ParameterizedType pType = ( ParameterizedType ) type;

                                Class<?> componentType = pType.getActualTypeArguments()[0] instanceof Class ? (Class<?>) pType.getActualTypeArguments()[0] : Object.class;

                                Collection newList =  Conversions.createCollection( parameterClass, itemList.size() );

                                for ( Object o : itemList ) {
                                    if ( o instanceof ValueContainer ) {
                                        o = ( ( ValueContainer ) o ).toValue();
                                    }
                                    if (o instanceof List) {

                                        if (componentType != Object.class) {

                                            List fromList = ( List ) o;
                                            o = fromList(fieldsAccessor, fromList, componentType);
                                        }
                                        newList.add( o );
                                    } else if (o instanceof Map) {
                                        Map fromMap = ( Map ) o;
                                        o = fromMap(respectIgnore, view, fieldsAccessor, fromMap, componentType, ignoreSet);
                                        newList.add( o );

                                    } else {
                                        newList.add( Conversions.coerce(componentType, o));
                                    }
                                }
                                convertedArgumentList.set( index, newList );
                                return true;

                            }

                        }
                    }
                    return false;

                default:
                    final TypeType itemType = TypeType.getInstanceType(item);

                    switch (itemType) {
                        case LIST:
                            convertedArgumentList.set(index, fromList(respectIgnore, view, fieldsAccessor, (List<Object>) item, parameterClass, ignoreSet));
                        case MAP:
                        case VALUE_MAP:
                            convertedArgumentList.set(index, fromMap(respectIgnore, view, fieldsAccessor, (Map<String, Object>) item, parameterClass, ignoreSet));

                        case NUMBER:
                        case BOOLEAN:
                        case INT:
                        case SHORT:
                        case BYTE:
                        case FLOAT:
                        case DOUBLE:
                        case LONG:
                        case DOUBLE_WRAPPER:
                        case FLOAT_WRAPPER:
                        case INTEGER_WRAPPER:
                        case SHORT_WRAPPER:
                        case BOOLEAN_WRAPPER:
                        case BYTE_WRAPPER:
                        case LONG_WRAPPER:
                        case CLASS:
                        case VALUE:
                            value = Conversions.coerceWithFlag( parameterClass, flag, item );

                            if (flag[0] == false) {
                                return false;
                            }
                            convertedArgumentList.set( index, value );
                            return true;

                        case CHAR_SEQUENCE:
                        case STRING:

                            value = Conversions.coerceWithFlag( parameterClass, flag, item );

                            if (flag[0] == false) {
                                return false;
                            }
                            convertedArgumentList.set( index, value );
                            return true;

                    }

            }

            if ( parameterClass.isInstance( item ) ) {
                return true;
            }

        } catch (Exception ex) {
            Boon.error(ex, "PROBLEM WITH oldMatchAndConvertArgs",
                    "respectIgnore", respectIgnore, "view", view,
                    "fieldsAccessor", fieldsAccessor, "list", convertedArgumentList,
                    "constructor", methodAccess, "parameters", parameterTypes,
                    "index", index, "ignoreSet", ignoreSet);
            return false;
        }

        return false;
    }

}