/*
 * Copyright (c) 2012-2018 Red Hat, Inc.
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Red Hat, Inc. - initial API and implementation
 */
package org.eclipse.che.infrastructure.docker.client;

import com.google.gson.Gson;
import com.google.gson.JsonIOException;
import com.google.gson.JsonParseException;
import com.google.gson.JsonStreamParser;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PushbackReader;

/**
 * Docker daemon sends chunked data in response. One chunk isn't always one JSON object so need to
 * read full chunk at once to be able restore JSON object. This reader merges (if needs) few chunks
 * until get full JSON object that we can parse. Parameter of this class is class where JSON message
 * should be parsed.
 *
 * @author Alexander Garagatyi
 */
public class JsonMessageReader<T> {
  private static final Gson GSON = new Gson();

  private final JsonStreamParser streamParser;
  private final Class<T> messageClass;
  private final PushbackReader reader;

  private boolean firstRead = true;

  /**
   * @param source source of messages in JSON format
   * @param messageClass class of the message object where JSON messages should be parsed. Because
   *     of erasure of generic information in runtime in some cases we can't get parameter class of
   *     current class.
   */
  public JsonMessageReader(InputStream source, Class<T> messageClass) {
    // we need to push back only 1 char, read more further
    this.reader = new PushbackReader(new InputStreamReader(source), 1);
    this.streamParser = new JsonStreamParser(reader);
    this.messageClass = messageClass;
  }

  /**
   * Returns message parsed from JSON stream.
   *
   * @return object of class passed as parameter of constructor or null if stream is empty
   * @throws IOException if error occurs on reading stream
   */
  public T next() throws IOException {
    // on first read we check if this stream is empty with reading of the first byte of stream
    // if so we do not call JsonStreamParser.hasNext() because it will throw exception
    // if not we return read byte to stream using PushbackInputStream
    if (firstRead) {
      int firstChar = reader.read();
      if (firstChar == -1) {
        return null;
      } else {
        reader.unread(firstChar);
        firstRead = false;
      }
    }
    try {
      if (streamParser.hasNext()) {
        return GSON.fromJson(streamParser.next(), messageClass);
      }
    } catch (JsonIOException e) {
      throw new IOException(e);
    } catch (JsonParseException ignore) {
    }
    return null;
  }
}
