/*
 * Copyright 2017, OpenCensus Authors
 *
 * 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 io.opencensus.implcore.tags;

import io.opencensus.common.Scope;
import io.opencensus.tags.InternalUtils;
import io.opencensus.tags.Tag;
import io.opencensus.tags.TagContext;
import io.opencensus.tags.TagContextBuilder;
import io.opencensus.tags.Tagger;
import io.opencensus.tags.TaggingState;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicReference;

public final class TaggerImpl extends Tagger {
  // All methods in this class use TagContextImpl and TagContextBuilderImpl. For example,
  // withTagContext(...) always puts a TagContextImpl into scope, even if the argument is another
  // TagContext subclass.
  //
  // TODO(sebright): Consider treating an unknown TagContext as empty.  That would allow us to
  // remove TagContext.unsafeGetIterator().

  private final AtomicReference<TaggingState> state;

  TaggerImpl(AtomicReference<TaggingState> state) {
    this.state = state;
  }

  @Override
  public TagContextImpl empty() {
    return TagContextImpl.EMPTY;
  }

  @Override
  public TagContextImpl getCurrentTagContext() {
    return toTagContextImpl(CurrentTagContextUtils.getCurrentTagContext(), state.get());
  }

  @Override
  public TagContextBuilder emptyBuilder() {
    return state.get() == TaggingState.DISABLED
        ? NoopTagContextBuilder.INSTANCE
        : new TagContextBuilderImpl();
  }

  @Override
  public TagContextBuilder currentBuilder() {
    return toBuilder(CurrentTagContextUtils.getCurrentTagContext());
  }

  @Override
  public TagContextBuilder toBuilder(TagContext tags) {
    return toTagContextBuilderImpl(tags, state.get());
  }

  @Override
  public Scope withTagContext(TagContext tags) {
    return CurrentTagContextUtils.withTagContext(toTagContextImpl(tags, state.get()));
  }

  private static TagContextImpl toTagContextImpl(TagContext tags, TaggingState state) {
    if (state == TaggingState.DISABLED) {
      return TagContextImpl.EMPTY;
    }
    if (tags instanceof TagContextImpl) {
      return (TagContextImpl) tags;
    } else {
      Iterator<Tag> i = InternalUtils.getTags(tags);
      if (!i.hasNext()) {
        return TagContextImpl.EMPTY;
      }
      TagContextBuilderImpl builder = new TagContextBuilderImpl();
      while (i.hasNext()) {
        Tag tag = i.next();
        if (tag != null) {
          TagContextUtils.addTagToBuilder(tag, builder);
        }
      }
      return builder.build();
    }
  }

  private static TagContextBuilder toTagContextBuilderImpl(TagContext tags, TaggingState state) {
    if (state == TaggingState.DISABLED) {
      return NoopTagContextBuilder.INSTANCE;
    }
    // Copy the tags more efficiently in the expected case, when the TagContext is a TagContextImpl.
    if (tags instanceof TagContextImpl) {
      return new TagContextBuilderImpl(((TagContextImpl) tags).getTags());
    } else {
      TagContextBuilderImpl builder = new TagContextBuilderImpl();
      for (Iterator<Tag> i = InternalUtils.getTags(tags); i.hasNext(); ) {
        Tag tag = i.next();
        if (tag != null) {
          TagContextUtils.addTagToBuilder(tag, builder);
        }
      }
      return builder;
    }
  }
}

@@ -670,16 +670,57 @@
 .Scope;%0A
+import io.opencensus.internal.NoopScope;%0A
 import i
@@ -1708,32 +1708,110 @@
 t() %7B%0A    return
+ state.get() == TaggingState.DISABLED%0A        ? TagContextImpl.EMPTY%0A        :
  toTagContextImp
@@ -1857,29 +1857,16 @@
 ontext()
-, state.get()
 );%0A  %7D%0A%0A
@@ -2122,16 +2122,104 @@
   return
+ state.get() == TaggingState.DISABLED%0A        ? NoopTagContextBuilder.INSTANCE%0A        :
  toBuild
@@ -2352,16 +2352,104 @@
   return
+ state.get() == TaggingState.DISABLED%0A        ? NoopTagContextBuilder.INSTANCE%0A        :
  toTagCo
@@ -2469,29 +2469,16 @@
 mpl(tags
-, state.get()
 );%0A  %7D%0A%0A
@@ -2548,16 +2548,97 @@
   return
+ state.get() == TaggingState.DISABLED%0A        ? NoopScope.getInstance()%0A        :
  Current
@@ -2693,21 +2693,8 @@
 tags
-, state.get()
 ));%0A
@@ -2766,114 +2766,11 @@
 tags
-, TaggingState state) %7B%0A    if (state == TaggingState.DISABLED) %7B%0A      return TagContextImpl.EMPTY;%0A    %7D
+) %7B
 %0A   
@@ -3281,16 +3281,20 @@
 tBuilder
+Impl
  toTagCo
@@ -3329,124 +3329,11 @@
 tags
-, TaggingState state) %7B%0A    if (state == TaggingState.DISABLED) %7B%0A      return NoopTagContextBuilder.INSTANCE;%0A    %7D
+) %7B
 %0A   

Check TaggingState at the start of every method in TaggerImpl.