/**
 * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.timeseries.precise.instant;

import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.SortedMap;
import java.util.concurrent.ConcurrentSkipListMap;

import org.threeten.bp.Instant;

import com.opengamma.timeseries.precise.PreciseObjectTimeSeries;

/**
 * Builder implementation for {@code ImmutableInstantObjectTimeSeries}.
 *
 * @param <V>  the type of the values
 */
@SuppressWarnings("synthetic-access")
final class ImmutableInstantObjectTimeSeriesBuilder<V>
    implements InstantObjectTimeSeriesBuilder<V> {

  /**
   * The time-series.
   */
  private final SortedMap<Long, V> _series = new ConcurrentSkipListMap<>();  // use this map to block nulls

  /**
   * Creates an instance.
   */
  protected ImmutableInstantObjectTimeSeriesBuilder() {
  }

  //-------------------------------------------------------------------------
  private static long convertToLong(final Instant instant) {
    return InstantToLongConverter.convertToLong(instant);
  }

  private static Instant convertFromLong(final long instant) {
    return InstantToLongConverter.convertToInstant(instant);
  }

  //-------------------------------------------------------------------------
  @Override
  public int size() {
    return _series.size();
  }

  @Override
  public InstantObjectEntryIterator<V> iterator() {
    return new InstantObjectEntryIterator<V>() {
      private final Iterator<Entry<Long, V>> _iterator = _series.entrySet().iterator();
      private int _index = -1;
      private Entry<Long, V> _current;

      @Override
      public boolean hasNext() {
        return _iterator.hasNext();
      }

      @Override
      public Entry<Instant, V> next() {
        return new SimpleImmutableEntry<>(nextTime(), currentValue());
      }

      @Override
      public long nextTimeFast() {
        if (!hasNext()) {
          throw new NoSuchElementException("No more elements in the iteration");
        }
        _index++;
        _current = _iterator.next();
        return _current.getKey();
      }

      @Override
      public Instant nextTime() {
        return convertFromLong(nextTimeFast());
      }

      @Override
      public long currentTimeFast() {
        if (_index < 0) {
          throw new IllegalStateException("Iterator has not yet been started");
        }
        if (_current == null) {
          throw new IllegalStateException("Element has been removed");
        }
        return _current.getKey();
      }

      @Override
      public Instant currentTime() {
        return convertFromLong(currentTimeFast());
      }

      @Override
      public V currentValue() {
        if (_index < 0) {
          throw new IllegalStateException("Iterator has not yet been started");
        }
        if (_current == null) {
          throw new IllegalStateException("Element has been removed");
        }
        return _current.getValue();
      }

      @Override
      public int currentIndex() {
        return _index;
      }

      @Override
      public void remove() {
        if (_index < 0) {
          throw new IllegalStateException("Iterator has not yet been started");
        }
        if (_current == null) {
          throw new IllegalStateException("Element has been removed");
        }
        _iterator.remove();
        _current = null;
        _index--;
      }
    };
  }

  //-------------------------------------------------------------------------
  @Override
  public InstantObjectTimeSeriesBuilder<V> put(final Instant time, final V value) {
    return put(convertToLong(time), value);
  }

  @Override
  public InstantObjectTimeSeriesBuilder<V> put(final long time, final V value) {
    _series.put(time, value);
    return this;
  }

  @Override
  public InstantObjectTimeSeriesBuilder<V> putAll(final Instant[] times, final V[] values) {
    if (times.length != values.length) {
      throw new IllegalArgumentException("Arrays are of different sizes: " + times.length + ", " + values.length);
    }
    for (int i = 0; i < times.length; i++) {
      put(times[i], values[i]);
    }
    return this;
  }

  @Override
  public InstantObjectTimeSeriesBuilder<V> putAll(final long[] times, final V[] values) {
    if (times.length != values.length) {
      throw new IllegalArgumentException("Arrays are of different sizes: " + times.length + ", " + values.length);
    }
    for (int i = 0; i < times.length; i++) {
      put(times[i], values[i]);
    }
    return this;
  }

  //-------------------------------------------------------------------------
  @Override
  public InstantObjectTimeSeriesBuilder<V> putAll(final PreciseObjectTimeSeries<?, V> timeSeries) {
    return putAll(timeSeries, 0, timeSeries.size());
  }

  @Override
  public InstantObjectTimeSeriesBuilder<V> putAll(final PreciseObjectTimeSeries<?, V> timeSeries, final int startPos, final int endPos) {
    if (startPos < 0 || startPos > timeSeries.size()) {
      throw new IndexOutOfBoundsException("Invalid start index: " + startPos);
    }
    if (endPos < 0 || endPos > timeSeries.size()) {
      throw new IndexOutOfBoundsException("Invalid end index: " + endPos);
    }
    if (startPos > endPos) {
      throw new IndexOutOfBoundsException("End index not be less than start index");
    }
    if (startPos == endPos) {
      return this;
    }
    for (int i = startPos; i < endPos; i++) {
      put(timeSeries.getTimeAtIndexFast(i), timeSeries.getValueAtIndex(i));
    }
    return this;
  }

  @Override
  public InstantObjectTimeSeriesBuilder<V> putAll(final Map<Instant, V> timeSeriesMap) {
    if (timeSeriesMap.size() == 0) {
      return this;
    }
    for (final Entry<Instant, V> entry : timeSeriesMap.entrySet()) {
      put(entry.getKey(), entry.getValue());
    }
    return this;
  }

  //-------------------------------------------------------------------------
  @Override
  public InstantObjectTimeSeriesBuilder<V> clear() {
    _series.clear();
    return this;
  }

  //-------------------------------------------------------------------------
  @Override
  public ImmutableInstantObjectTimeSeries<V> build() {
    final long[] times = new long[_series.size()];
    @SuppressWarnings("unchecked")
    final
    V[] values = (V[]) new Object[_series.size()];
    int i = 0;
    for (final Entry<Long, V> entry : _series.entrySet()) {
      times[i] = entry.getKey();
      values[i++] = entry.getValue();
    }
    return new ImmutableInstantObjectTimeSeries<>(times, values);
  }

  //-------------------------------------------------------------------------
  @Override
  public String toString() {
    return "Builder[size=" + _series.size() + "]";
  }

}
