/*
 * Copyright (C) 2017 Square, 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 okio;

/**
 * Okio assumes most applications use UTF-8 exclusively, and offers optimized implementations of
 * common operations on UTF-8 strings.
 *
 * <p><table border="1" cellspacing="0" cellpadding="3" summary="">
 * <tr>
 *   <th></th>
 *   <th>{@link ByteString}</th>
 *   <th>{@link Buffer}, {@link BufferedSink}, {@link BufferedSource}</th>
 * </tr>
 * <tr>
 *   <td>Encode a string</td>
 *   <td>{@link ByteString#encodeUtf8(String)}</td>
 *   <td>{@link BufferedSink#writeUtf8(String)}</td>
 * </tr>
 * <tr>
 *   <td>Encode a code point</td>
 *   <td></td>
 *   <td>{@link BufferedSink#writeUtf8CodePoint(int)}</td>
 * </tr>
 * <tr>
 *   <td>Decode a string</td>
 *   <td>{@link ByteString#utf8()}</td>
 *   <td>{@link BufferedSource#readUtf8()}, {@link BufferedSource#readUtf8(long)}</td>
 * </tr>
 * <tr>
 *   <td>Decode a code point</td>
 *   <td></td>
 *   <td>{@link BufferedSource#readUtf8CodePoint()}</td>
 * </tr>
 * <tr>
 *   <td>Decode until the next {@code \r\n} or {@code \n}</td>
 *   <td></td>
 *   <td>{@link BufferedSource#readUtf8LineStrict()},
 *       {@link BufferedSource#readUtf8LineStrict(long)}</td>
 * </tr>
 * <tr>
 *   <td>Decode until the next {@code \r\n}, {@code \n}, or {@code EOF}</td>
 *   <td></td>
 *   <td>{@link BufferedSource#readUtf8Line()}</td>
 * </tr>
 * <tr>
 *   <td>Measure the bytes in a UTF-8 string</td>
 *   <td colspan="2">{@link Utf8#size}, {@link Utf8#size(String, int, int)}</td>
 * </tr>
 * </table>
 */
public final class Utf8 {
  private Utf8() {
  }

  /**
   * Returns the number of bytes used to encode {@code string} as UTF-8 when using {@link
   * ByteString#encodeUtf8} or {@link Buffer#writeUtf8(String)}.
   */
  public static long size(String string) {
    return size(string, 0, string.length());
  }

  /**
   * Returns the number of bytes used to encode the slice of {@code string} as UTF-8 when using
   * {@link BufferedSink#writeUtf8(String, int, int)}.
   */
  public static long size(String string, int beginIndex, int endIndex) {
    if (string == null) throw new IllegalArgumentException("string == null");
    if (beginIndex < 0) throw new IllegalArgumentException("beginIndex < 0: " + beginIndex);
    if (endIndex < beginIndex) {
      throw new IllegalArgumentException("endIndex < beginIndex: " + endIndex + " < " + beginIndex);
    }
    if (endIndex > string.length()) {
      throw new IllegalArgumentException(
          "endIndex > string.length: " + endIndex + " > " + string.length());
    }

    long result = 0;
    for (int i = beginIndex; i < endIndex;) {
      int c = string.charAt(i);

      if (c < 0x80) {
        // A 7-bit character with 1 byte.
        result++;
        i++;

      } else if (c < 0x800) {
        // An 11-bit character with 2 bytes.
        result += 2;
        i++;

      } else if (c < 0xd800 || c > 0xdfff) {
        // A 16-bit character with 3 bytes.
        result += 3;
        i++;

      } else {
        int low = i + 1 < endIndex ? string.charAt(i + 1) : 0;
        if (c > 0xdbff || low < 0xdc00 || low > 0xdfff) {
          // A malformed surrogate, which yields '?'.
          result++;
          i++;

        } else {
          // A 21-bit character with 4 bytes.
          result += 4;
          i += 2;
        }
      }
    }

    return result;
  }
}
