/*
 * Copyright (C) 2021 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.gson;

import com.google.gson.internal.LazilyParsedNumber;
import com.google.gson.internal.NumberLimits;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.MalformedJsonException;
import java.io.IOException;
import java.math.BigDecimal;

/**
 * An enumeration that defines two standard number reading strategies and a couple of strategies to
 * overcome some historical Gson limitations while deserializing numbers as {@link Object} and
 * {@link Number}.
 *
 * @see ToNumberStrategy
 * @since 2.8.9
 */
public enum ToNumberPolicy implements ToNumberStrategy {

  /**
   * Using this policy will ensure that numbers will be read as {@link Double} values. This is the
   * default strategy used during deserialization of numbers as {@link Object}.
   */
  DOUBLE {
    @Override
    public Double readNumber(JsonReader in) throws IOException {
      return in.nextDouble();
    }
  },

  /**
   * Using this policy will ensure that numbers will be read as a lazily parsed number backed by a
   * string. This is the default strategy used during deserialization of numbers as {@link Number}.
   */
  LAZILY_PARSED_NUMBER {
    @Override
    public Number readNumber(JsonReader in) throws IOException {
      return new LazilyParsedNumber(in.nextString());
    }
  },

  /**
   * Using this policy will ensure that numbers will be read as {@link Long} or {@link Double}
   * values depending on how JSON numbers are represented: {@code Long} if the JSON number can be
   * parsed as a {@code Long} value, or otherwise {@code Double} if it can be parsed as a {@code
   * Double} value. If the parsed double-precision number results in a positive or negative infinity
   * ({@link Double#isInfinite()}) or a NaN ({@link Double#isNaN()}) value and the {@code
   * JsonReader} is not {@link JsonReader#isLenient() lenient}, a {@link MalformedJsonException} is
   * thrown.
   */
  LONG_OR_DOUBLE {
    @Override
    public Number readNumber(JsonReader in) throws IOException, JsonParseException {
      String value = in.nextString();
      if (value.indexOf('.') >= 0) {
        return parseAsDouble(value, in);
      } else {
        try {
          return Long.parseLong(value);
        } catch (NumberFormatException e) {
          return parseAsDouble(value, in);
        }
      }
    }

    private Number parseAsDouble(String value, JsonReader in) throws IOException {
      try {
        Double d = Double.valueOf(value);
        if ((d.isInfinite() || d.isNaN()) && !in.isLenient()) {
          throw new MalformedJsonException(
              "JSON forbids NaN and infinities: " + d + "; at path " + in.getPreviousPath());
        }
        return d;
      } catch (NumberFormatException e) {
        throw new JsonParseException(
            "Cannot parse " + value + "; at path " + in.getPreviousPath(), e);
      }
    }
  },

  /**
   * Using this policy will ensure that numbers will be read as numbers of arbitrary length using
   * {@link BigDecimal}.
   */
  BIG_DECIMAL {
    @Override
    public BigDecimal readNumber(JsonReader in) throws IOException {
      String value = in.nextString();
      try {
        return NumberLimits.parseBigDecimal(value);
      } catch (NumberFormatException e) {
        throw new JsonParseException(
            "Cannot parse " + value + "; at path " + in.getPreviousPath(), e);
      }
    }
  }
}
