/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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
 * <p/>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p/>
 * 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 org.apache.ambari.view.hive.resources.uploads.parsers.json;

import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import org.apache.ambari.view.hive.resources.uploads.parsers.EndOfDocumentException;
import org.apache.ambari.view.hive.resources.uploads.parsers.RowMapIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.LinkedHashMap;

/**
 * iterates over the JsonReader and reads creates row data
 * assumes the array of json objects.
 * eg : [ { "col1Name" : "value-1-1", "col2Name" : "value-1-2"}, { "col1Name" : "value-2-1", "col2Name" : "value-2-2"}]
 */
class JSONIterator implements RowMapIterator {

  protected final static Logger LOG =
          LoggerFactory.getLogger(JSONIterator.class);

  private LinkedHashMap<String, String> nextObject = null;

  private LinkedHashMap<String, String> readNextObject(JsonReader reader) throws IOException, EndOfDocumentException {
    LinkedHashMap<String, String> row = new LinkedHashMap<>();
    boolean objectStarted = false;
    boolean shouldBeName = false;
    String currentName = null;

    while (true) {
      JsonToken token = reader.peek();
      switch (token) {
        case BEGIN_ARRAY:
          throw new IllegalArgumentException("Row data cannot have an array.");
        case END_ARRAY:
          throw new EndOfDocumentException("End of Json Array document.");
        case BEGIN_OBJECT:
          if (objectStarted == true) {
            throw new IllegalArgumentException("Nested objects not supported.");
          }
          if (shouldBeName == true) {
            throw new IllegalArgumentException("name expected, got begin_object");
          }
          objectStarted = true;
          shouldBeName = true;
          reader.beginObject();
          break;
        case END_OBJECT:
          if (shouldBeName == false) {
            throw new IllegalArgumentException("value expected, got end_object");
          }
          reader.endObject();
          return row;
        case NAME:
          if (shouldBeName == false) {
            throw new IllegalArgumentException("name not expected at this point.");
          }
          shouldBeName = false;
          currentName = reader.nextName();
          break;
        case NUMBER:
        case STRING:
          if (shouldBeName == true) {
            throw new IllegalArgumentException("value not expected at this point.");
          }
          String n = reader.nextString();
          row.put(currentName, n);
          shouldBeName = true;
          break;
        case BOOLEAN:
          if (shouldBeName == true) {
            throw new IllegalArgumentException("value not expected at this point.");
          }
          String b = String.valueOf(reader.nextBoolean());
          row.put(currentName, b);
          shouldBeName = true;
          break;
        case NULL:
          if (shouldBeName == true) {
            throw new IllegalArgumentException("value not expected at this point.");
          }
          reader.nextNull();
          row.put(currentName, "");
          shouldBeName = true;
          break;
        case END_DOCUMENT:
          return row;

        default:
          throw new IllegalArgumentException("Illegal token detected inside json: token : " + token.toString());
      }
    }
  }

  private JsonReader reader;

  public JSONIterator(JsonReader reader) throws IOException {
    this.reader = reader;
    // test the start of array
    JsonToken jt = reader.peek();
    if (jt != JsonToken.BEGIN_ARRAY) {
      throw new IllegalArgumentException("Expected the whole document to contain a single JsonArray.");
    }

    reader.beginArray(); // read the start of array
    try {
      nextObject = readNextObject(this.reader);
    } catch (EndOfDocumentException e) {
    }
  }

  @Override
  public boolean hasNext() {
    return null != nextObject;
  }

  public LinkedHashMap<String, String> peek() {
    return nextObject;
  }

  @Override
  public LinkedHashMap<String, String> next() {
    LinkedHashMap<String, String> currObject = nextObject;
    try {
      nextObject = readNextObject(this.reader);
    } catch (EndOfDocumentException e) {
      LOG.debug("End of Json document reached with next character ending the JSON Array.");
      nextObject = null;
    } catch (Exception e){
      // for any other exception throw error right away
      throw new IllegalArgumentException(e);
    }
    return currObject;
  }

  @Override
  public void remove() {
    // no operation.
    LOG.info("No operation when remove called on JSONIterator.");
  }
}