/*
 * Copyright 2010 Google 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.google.template.soy.msgs.restricted;

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.collect.ImmutableList;
import com.google.template.soy.msgs.restricted.SoyMsgPart.Case;

import com.ibm.icu.text.PluralRules;
import com.ibm.icu.util.ULocale;

import java.util.Objects;

import javax.annotation.Nullable;

/**
 * Represents a plural statement within a message.
 *
 */
public final class SoyMsgPluralPart extends SoyMsgPart {

  /** The plural variable name. */
  private final String pluralVarName;

  /** The offset. */
  private final int offset;

  /** The various cases for this plural statement. The default statement has a null key. */
  private final ImmutableList<Case<SoyMsgPluralCaseSpec>> cases;


  /**
   * @param pluralVarName The plural variable name.
   * @param offset The offset for this plural statement.
   * @param cases The list of cases for this plural statement.
   */
  public SoyMsgPluralPart(
      String pluralVarName, int offset, Iterable<Case<SoyMsgPluralCaseSpec>> cases) {

    this.pluralVarName = pluralVarName;
    this.offset = offset;
    this.cases = ImmutableList.copyOf(cases);
  }


  /** Returns the plural variable name. */
  public String getPluralVarName() {
    return pluralVarName;
  }


  /** Returns the offset. */
  public int getOffset() {
    return offset;
  }


  /** Returns the cases. */
  public ImmutableList<Case<SoyMsgPluralCaseSpec>> getCases() {
    return cases;
  }

  /**
   * Returns the list of parts to implement the case.
   *
   * @param pluralValue The current plural value
   * @param locale The locale for interpreting non-specific plural parts.  Allowed to be null if it
   *     is known that there are no non-specific plural parts (This is commonly the case for
   *     default messages, since soy only allows direct specification of explicit or 'other').
   */
  public ImmutableList<SoyMsgPart> lookupCase(int pluralValue, @Nullable ULocale locale) {
    // TODO(lukes): clean up this method, the control flow is overly complex.  It could be cleaned
    // up by separating cases into 3 different lists.

    // Handle cases.
    ImmutableList<SoyMsgPart> caseParts = null;

    // Check whether the plural value matches any explicit numeric value.
    boolean hasNonExplicitCases = false;
    ImmutableList<SoyMsgPart> otherCaseParts = null;
    for (Case<SoyMsgPluralCaseSpec> case0 : getCases()) {

      SoyMsgPluralCaseSpec pluralCaseSpec = case0.spec();
      SoyMsgPluralCaseSpec.Type caseType = pluralCaseSpec.getType();
      if (caseType == SoyMsgPluralCaseSpec.Type.EXPLICIT) {
        if (pluralCaseSpec.getExplicitValue() == pluralValue) {
          caseParts = case0.parts();
          break;
        }

      } else if (caseType == SoyMsgPluralCaseSpec.Type.OTHER) {
        otherCaseParts = case0.parts();

      } else {
        hasNonExplicitCases = true;

      }
    }

    if (caseParts == null && hasNonExplicitCases) {
      // Didn't match any numeric value.  Check which plural rule it matches.
      String pluralKeyword = PluralRules.forLocale(locale).select(pluralValue - offset);
      SoyMsgPluralCaseSpec.Type correctCaseType =
          new SoyMsgPluralCaseSpec(pluralKeyword).getType();


      // Iterate the cases once again for non-numeric keywords.
      for (Case<SoyMsgPluralCaseSpec> case0 : getCases()) {

        if (case0.spec().getType() == correctCaseType) {
          caseParts = case0.parts();
          break;
        }
      }
    }

    if (caseParts == null) {
      // Fall back to the "other" case. This can happen either if there aren't any non-specific
      // cases, or there is not the non-specific case that we need.
      caseParts = otherCaseParts;
    }
    return checkNotNull(caseParts);
  }

  @Override public boolean equals(Object other) {
    if (!(other instanceof SoyMsgPluralPart)) {
      return false;
    }
    SoyMsgPluralPart otherPlural = (SoyMsgPluralPart) other;
    return offset == otherPlural.offset
        && pluralVarName.equals(otherPlural.pluralVarName)
        && cases.equals(otherPlural.cases);
  }


  @Override public int hashCode() {
    return Objects.hash(SoyMsgPluralPart.class, offset, pluralVarName, cases);
  }
}
