/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.uima.cas.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.uima.cas.FSIndexRepository;
import org.apache.uima.internal.util.IntVector;


/**
 * Record information on what was removed, from which view, and (optionally) how many times.
 * 
 * 4 varieties:
 *   1) for a single FS
 *      a) without count
 *      b) with count
 *   2) for multiple FSs
 *      a) without count
 *      b) with count   
 */
abstract class FSsTobeAddedback implements AutoCloseable {
  
  final static boolean SHOW = false;
  final static AtomicInteger removes = new AtomicInteger(0);
  
  /**
   * does an add back if needed 
   */
  public void close() { 
  	addback();
  	((FSsTobeAddedbackMultiple) this).cas.dropProtectIndexesLevel();
  }

  protected void logPart(FSIndexRepository view) {
    System.out.format("%,d tobeReindexed: view: %s", removes.incrementAndGet(), view);
  }
  
  protected void log(FSIndexRepositoryImpl view, int count) {
    if (SHOW) {
      logPart(view);
      System.out.format(",  count = %d%n", count);
    }
  }
  
  private void logPart(int fsAddr, FSIndexRepositoryImpl view) {
    log(view);
    System.out.format(",  fsAddr = %,d", fsAddr);
  }
  
  protected void log(int fsAddr, FSIndexRepositoryImpl view, int count) {
    if (SHOW) {
      log(fsAddr, view);
      System.out.format(",  count = %d%n", count);
    }
  }

  protected void log(FSIndexRepositoryImpl view) {
    if (SHOW) {
      logPart(view);
      System.out.println();
    }
  }
  
  protected void log(int fsAddr, FSIndexRepositoryImpl view) {
    if (SHOW) {
      logPart(fsAddr, view);
      System.out.println();
    }
  }
  
  void recordRemove(FSIndexRepositoryImpl view)                        {throw new UnsupportedOperationException();}
  void recordRemove(FSIndexRepositoryImpl view, int count)             {
    if (count == 1) {
      recordRemove(view);
    } else {
      throw new UnsupportedOperationException();
    }
  }
  void recordRemove(int fsAddr, FSIndexRepositoryImpl view)            {throw new UnsupportedOperationException();}
  void recordRemove(int fsAddr, FSIndexRepositoryImpl view, int count) {
    if (count == 1) {
      recordRemove(fsAddr, view);
    } else { 
      throw new UnsupportedOperationException();
    }
  }
  
  void addback()                                                       {throw new UnsupportedOperationException();}
  void addback(int fsAddr)                                             {throw new UnsupportedOperationException();}
  abstract void clear();

  static class FSsTobeAddedbackSingle extends FSsTobeAddedback {
    final List<FSIndexRepositoryImpl> views = new ArrayList<FSIndexRepositoryImpl>();
    
    @Override
    void recordRemove(FSIndexRepositoryImpl view) {
      log(view);
      views.add(view);
    }
    
    @Override
    void recordRemove(int fsAddr, FSIndexRepositoryImpl view) {
      recordRemove(view);
    }
    
    @Override
    void recordRemove(int fsAddr, FSIndexRepositoryImpl view, int count) {
      if (count != 1) {
        throw new RuntimeException("internal error");
      }
      recordRemove(view);
    }
          
    @Override
    void addback(int fsAddr) {
      for (FSIndexRepositoryImpl ir : views) {
        ir.ll_addback(fsAddr, 1);
      }
      clear();
    }
    
    @Override
    void clear() {
      views.clear();     
//      if (SHOW) removes.set(0);
    }
  }
  
  static class FSsTobeAddedbackSingleCounts extends FSsTobeAddedbackSingle {
    final IntVector counts = new IntVector(4);
    
    @Override
    void recordRemove(FSIndexRepositoryImpl view, int count) {
      log(view, count);
      views.add(view);
      counts.add(count);
    }
          
    @Override
    void addback(int fsAddr) {
      int i = 0;
      for (FSIndexRepositoryImpl ir : views) {
        ir.ll_addback(fsAddr, counts.get(i++));
      }
      clear();
    }
    
    @Override
    void clear() {
      views.clear();
      counts.removeAllElementsAdjustSizeDown();
//      if (SHOW) removes.set(0);
    }

  }

  static class FSsTobeAddedbackMultiple extends FSsTobeAddedback {
  
    // impl note: for support of allow_multiple_add_to_indexes, each entry is two List elements:
    //   the count
    //   the ref to the view
    final Map<Integer, List<?>> fss2views = new HashMap<Integer, List<?>>();
    
    final CASImpl cas;
    
    FSsTobeAddedbackMultiple(CASImpl cas) {
      this.cas = cas;
    }
    
    @Override
    void recordRemove(int fsAddr, FSIndexRepositoryImpl view) {
      log(fsAddr, view);
      @SuppressWarnings("unchecked")
      List<FSIndexRepositoryImpl> irList = (List<FSIndexRepositoryImpl>) fss2views.get(fsAddr);
      if (null == irList) {
        fss2views.put(fsAddr,  irList = new ArrayList<FSIndexRepositoryImpl>());
      }
      irList.add(view);
    }
          
    @Override
    void addback() {
      for (Entry<Integer, List<?>> e : fss2views.entrySet()) {
        final int fsAddr = e.getKey();
        @SuppressWarnings("unchecked")
        final List<FSIndexRepositoryImpl> views = (List<FSIndexRepositoryImpl>) e.getValue();
        for (FSIndexRepositoryImpl ir : views) {
          ir.ll_addback(fsAddr, 1);
        }
      }
      clear();
//      cas.dropProtectIndexesLevel();  // all callers of addback do what's needed, don't do it here 6/2020 MIS
    }
    
    @Override
    void clear() {
      fss2views.clear();
//      if (SHOW) removes.set(0);
    }
  }
  
  static class FSsTobeAddedbackMultipleCounts extends FSsTobeAddedbackMultiple {
     
    public FSsTobeAddedbackMultipleCounts(CASImpl cas) {
      super(cas);
    }
    
    @Override
    void recordRemove(int fsAddr, FSIndexRepositoryImpl view, int count) {
      log(fsAddr, view, count);
      @SuppressWarnings("unchecked")
      List<Object> countsAndViews = (List<Object>) fss2views.get(fsAddr);
      if (null == countsAndViews) {
        fss2views.put(fsAddr,  countsAndViews = new ArrayList<Object>());
      }
      countsAndViews.add(count);
      countsAndViews.add(view);
    }
    
    @Override
    void addback() {
      for (Entry<Integer, List<?>> e : fss2views.entrySet()) {
        final int fsAddr = e.getKey();
        final List<?> countsAndViews = e.getValue();
      
        for (int i = 0; i < countsAndViews.size(); ) {
          final int count = (Integer) countsAndViews.get(i++);
          final FSIndexRepositoryImpl view = (FSIndexRepositoryImpl) countsAndViews.get(i++);
          view.ll_addback(fsAddr, count);
        }  
      }
      clear();
    }
    
    @Override
    void clear() {
      fss2views.clear();
//      if (SHOW) removes.set(0);
    }
  }
  
  /**
   * @return an impl of this class
   */
  public static FSsTobeAddedback createSingle() {
    return (FSIndexRepositoryImpl.IS_ALLOW_DUP_ADD_2_INDEXES) ?
        new FSsTobeAddedbackSingleCounts() :
        new FSsTobeAddedbackSingle();
  }
  
  public static FSsTobeAddedback createMultiple(CASImpl cas) {
    return (FSIndexRepositoryImpl.IS_ALLOW_DUP_ADD_2_INDEXES) ?
       new FSsTobeAddedbackMultipleCounts(cas) :
       new FSsTobeAddedbackMultiple(cas);
  }
}

