/*
 * Druid - a distributed column store.
 * Copyright (C) 2012, 2013  Metamarkets Group Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package io.druid.query;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.metamx.common.guava.Sequence;
import com.metamx.common.guava.Sequences;
import com.metamx.common.guava.Yielder;
import com.metamx.common.guava.YieldingAccumulator;
import com.metamx.common.guava.YieldingSequenceBase;
import com.metamx.emitter.EmittingLogger;
import io.druid.query.spec.MultipleSpecificSegmentSpec;
import io.druid.segment.SegmentMissingException;

import java.util.List;
import java.util.Map;

public class RetryQueryRunner<T> implements QueryRunner<T>
{
  public static String MISSING_SEGMENTS_KEY = "missingSegments";
  private static final EmittingLogger log = new EmittingLogger(RetryQueryRunner.class);

  private final QueryRunner<T> baseRunner;
  private final QueryToolChest<T, Query<T>> toolChest;
  private final RetryQueryRunnerConfig config;
  private final ObjectMapper jsonMapper;

  public RetryQueryRunner(
      QueryRunner<T> baseRunner,
      QueryToolChest<T, Query<T>> toolChest,
      RetryQueryRunnerConfig config,
      ObjectMapper jsonMapper
  )
  {
    this.baseRunner = baseRunner;
    this.toolChest = toolChest;
    this.config = config;
    this.jsonMapper = jsonMapper;
  }

  @Override
  public Sequence<T> run(final Query<T> query, final Map<String, Object> context)
  {
    final List<Sequence<T>> listOfSequences = Lists.newArrayList();
    final Sequence<T> returningSeq = baseRunner.run(query, context);
    listOfSequences.add(returningSeq);

    return new YieldingSequenceBase<T>()
    {
      @Override
      public <OutType> Yielder<OutType> toYielder(
          OutType initValue, YieldingAccumulator<OutType, T> accumulator
      )
      {
        final List<SegmentDescriptor> missingSegments = getMissingSegments(context);

        if (missingSegments.isEmpty()) {
          return returningSeq.toYielder(initValue, accumulator);
        }

        for (int i = 0; i < config.numTries(); i++) {
          log.info("[%,d] missing segments found. Retry attempt [%,d]", missingSegments.size(), i);

          context.put(MISSING_SEGMENTS_KEY, Lists.newArrayList());
          final Query<T> retryQuery = query.withQuerySegmentSpec(
              new MultipleSpecificSegmentSpec(
                  missingSegments
              )
          );
          Sequence<T> retrySequence = baseRunner.run(retryQuery, context);
          listOfSequences.add(retrySequence);
          if (getMissingSegments(context).isEmpty()) {
            break;
          }
        }

        final List<SegmentDescriptor> finalMissingSegs = getMissingSegments(context);
        if (!config.returnPartialResults() && !finalMissingSegs.isEmpty()) {
          throw new SegmentMissingException("No results found for segments[%s]", finalMissingSegs);
        }

        return toolChest.mergeSequencesUnordered(Sequences.simple(listOfSequences)).toYielder(initValue, accumulator);
      }
    };
  }

  private List<SegmentDescriptor> getMissingSegments(final Map<String, Object> context)
  {
    final Object maybeMissingSegments = context.get(MISSING_SEGMENTS_KEY);
    if (maybeMissingSegments == null) {
      return Lists.newArrayList();
    }

    return jsonMapper.convertValue(
        maybeMissingSegments,
        new TypeReference<List<SegmentDescriptor>>()
        {
        }
    );
  }
}


@@ -2294,22 +2294,22 @@
     
-final 
+listOf
 Sequence
 %3CT%3E 
@@ -2308,27 +2308,14 @@
 ence
-%3CT%3E returningSeq = 
+s.add(
 base
@@ -2344,46 +2344,8 @@
 ext)
-;%0A    listOfSequences.add(returningSeq
 );%0A%0A
@@ -2645,16 +2645,17 @@
     if (
+!
 missingS
@@ -2681,82 +2681,8 @@
 %7B%0A  
-        return returningSeq.toYielder(initValue, accumulator);%0A        %7D%0A%0A
     
@@ -2727,24 +2727,26 @@
 s(); i++) %7B%0A
+  
           lo
@@ -2830,24 +2830,26 @@
 ize(), i);%0A%0A
+  
           co
@@ -2909,24 +2909,26 @@
 ;%0A          
+  
 final Query%3C
@@ -2981,24 +2981,26 @@
             
+  
 new Multiple
@@ -3038,16 +3038,18 @@
         
+ 
+ 
 missingS
@@ -3066,26 +3066,30 @@
             
-)%0A
+  )%0A  
           );
@@ -3089,16 +3089,18 @@
      );%0A
+  
         
@@ -3158,32 +3158,34 @@
 uery, context);%0A
+  
           listOf
@@ -3220,24 +3220,26 @@
 ;%0A          
+  
 if (getMissi
@@ -3287,15 +3287,19 @@
     
+  
 break;%0A
+  
     
@@ -3306,32 +3306,34 @@
       %7D%0A        
+  
 %7D%0A%0A        final
@@ -3319,32 +3319,34 @@
      %7D%0A%0A        
+ 
+ 
 final List%3CSegme
@@ -3411,24 +3411,26 @@
 t);%0A        
+  
 if (!config.
@@ -3478,32 +3478,34 @@
 gs.isEmpty()) %7B%0A
+  
           throw 
@@ -3600,25 +3600,179 @@
     
-%7D%0A%0A        return
+  %7D%0A        %7D%0A%0A%0A        final Sequence%3CT%3E retSeq;%0A        if (listOfSequences.size() == 1) %7B%0A          retSeq = listOfSequences.get(0);%0A        %7D else %7B%0A          retSeq =
  too
@@ -3836,16 +3836,50 @@
 uences))
+;%0A        %7D%0A%0A        return retSeq
 .toYield

address cr