/*
 * Copyright (C) 2014 Indeed Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */
 package com.indeed.imhotep.client;

import com.indeed.imhotep.api.FTGSIterator;
import com.indeed.imhotep.api.ImhotepOutOfMemoryException;
import com.indeed.imhotep.api.ImhotepSession;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Scanner;
import java.util.StringTokenizer;

/**
 * @author jsgroth
 */
public class InteractiveShell {
    public static void main(String[] args) throws ImhotepOutOfMemoryException, IOException {
        final HostsReloader reloader;
        if (args[0].equals("--hostsfile")) {
            reloader = new FileHostsReloader(args[1]);
        } else if (args[0].equals("--zknodes")) {
            reloader = new ZkHostsReloader(args[1], true);
        } else {
            throw new RuntimeException();
        }

        main(reloader);
    }

    public static void main(final HostsReloader reloader) throws ImhotepOutOfMemoryException, IOException {
        final ImhotepClient client = new ImhotepClient(reloader);
        final Scanner sc = new Scanner(System.in);
        ImhotepSession activeSession = null;
        int numStats = 0;
        try {
            while (true) {
                System.out.print("> ");
                final String input = sc.nextLine();
                if (input.startsWith("open")) {
                    final StringTokenizer tokenizer = new StringTokenizer(input);
                    tokenizer.nextToken(); // open
                    final String dataset = tokenizer.nextToken();
                    final List<String> requestedShards = new ArrayList<String>();
                    while (tokenizer.hasMoreTokens()) {
                        requestedShards.add(tokenizer.nextToken());
                    }
                    activeSession = client.sessionBuilder(dataset, null, null).shardsOverride(requestedShards).build();
                } else if (input.equals("close")) {
                    if (activeSession != null) activeSession.close();
                    activeSession = null;
                } else if (input.startsWith("push")) {
                    final String metric = input.split(" ", 2)[1];
                    if (activeSession != null) numStats = activeSession.pushStat(metric);
                } else if (input.equals("pop")) {
                    if (activeSession != null) numStats = activeSession.popStat();
                } else if (input.startsWith("top")) {
                    final StringTokenizer tokenizer = new StringTokenizer(input);
                    tokenizer.nextToken(); // top
                    final int k = Integer.parseInt(tokenizer.nextToken());
                    String[] intFields = null;
                    String[] stringFields = null;
                    while (tokenizer.hasMoreTokens()) {
                        final String token = tokenizer.nextToken();
                        if (token.startsWith("if=")) {
                            intFields = token.substring("if=".length()).split(",");
                        } else if (token.startsWith("sf=")) {
                            stringFields = token.substring("sf=".length()).split(",");
                        }
                    }
                    if (intFields == null) intFields = new String[0];
                    if (stringFields == null) stringFields = new String[0];
                    final FTGSIterator iterator = activeSession.getFTGSIterator(intFields, stringFields);
                    final List<TGSTuple>[] topk = collectTopK(iterator, k, numStats);
                    for (int i = 0; i < numStats; ++i) {
                        System.out.println("stat "+i+":");
                        for (final TGSTuple t : topk[i]) {
                            System.out.println(t);
                        }
                    }
                } else if (input.equals("quit")) {
                    break;
                }
            }
        } finally {
            if (activeSession != null) activeSession.close();
            client.close();
        }
    }

    @SuppressWarnings("unchecked")
    public static List<TGSTuple>[] collectTopK(final FTGSIterator iterator, final int k, final int numStats) {
        final long[] statAccumBuf = new long[numStats];
        final long[] statBuf = new long[numStats];
        final PriorityQueue<TGSTuple>[] pqs = new PriorityQueue[numStats];
        for (int i = 0; i < pqs.length; ++i) {
            pqs[i] = new PriorityQueue<TGSTuple>(k+1);
        }

        while (iterator.nextField()) {
            final String fieldName = iterator.fieldName();
            final boolean fieldIsIntType = iterator.fieldIsIntType();
            while (iterator.nextTerm()) {
                final long termIntVal = fieldIsIntType ? iterator.termIntVal() : 0;
                final String termStringVal = fieldIsIntType ? null : iterator.termStringVal();

                Arrays.fill(statAccumBuf, 0L);
                while (iterator.nextGroup()) {
                    iterator.groupStats(statBuf);
                    for (int i = 0; i < numStats; ++i) {
                        statAccumBuf[i] += statBuf[i];
                    }
                }

                for (int i = 0; i < numStats; ++i) {
                    final PriorityQueue<TGSTuple> pq = pqs[i];
                    pq.add(new TGSTuple(fieldName, fieldIsIntType, termIntVal, termStringVal, statAccumBuf[i]));
                    while (pq.size() > k) pq.remove();
                }
            }
        }

        final List<TGSTuple>[] ret = new List[numStats];
        for (int i = 0; i < numStats; ++i) {
            ret[i] = new ArrayList<TGSTuple>(pqs[i]);
            Collections.sort(ret[i]);
            Collections.reverse(ret[i]);
        }
        return ret;
    }

    public static class TGSTuple implements Comparable<TGSTuple> {
        public final String field;
        public final boolean fieldIsIntType;
        public final long termIntVal;
        public final String termStringVal;
        public final long stat;

        public TGSTuple(String field, boolean fieldIsIntType, long termIntVal, String termStringVal, long stat) {
            this.field = field;
            this.fieldIsIntType = fieldIsIntType;
            this.termIntVal = termIntVal;
            this.termStringVal = termStringVal;
            this.stat = stat;
        }

        @Override
        public int compareTo(TGSTuple o) {
            return (int)(stat - o.stat);
        }

        @Override
        public String toString() {
            return field+":"+(fieldIsIntType ? termIntVal : termStringVal)+" - "+stat;
        }
    }
}
