/*
 * Copyright (C) 2009 Google Inc.  All rights reserved.
 *
 * 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.polo.wire.json;

import com.google.polo.exception.PoloException;
import com.google.polo.json.JSONException;
import com.google.polo.json.JSONObject;
import com.google.polo.pairing.PairingContext;
import com.google.polo.pairing.PoloUtil;
import com.google.polo.pairing.message.PoloMessage;
import com.google.polo.pairing.message.PoloMessage.PoloMessageType;
import com.google.polo.wire.PoloWireInterface;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * A {@link PoloWireInterface} which uses JavaScript Object Notation (JSON) for
 * the message representation.
 * <p>
 * Messages are streamed over the wire prepended with an integer which indicates
 * the total length, in bytes, of the message which follows. The format of the
 * message is JSON.
 * <p>
 * See {@link JsonMessageBuilder} for the underlying message translation
 * implementation.
 */
public class JsonWireAdapter implements PoloWireInterface {

  /**
   * The output coming from the peer.
   */
  private final DataInputStream mInputStream;

  /**
   * The input going to the peer.
   */
  private final DataOutputStream mOutputStream;

  /**
   * Constructor.
   *
   * @param input the {@link InputStream} from the peer
   * @param output the {@link OutputStream} to the peer
   */
  public JsonWireAdapter(InputStream input, OutputStream output) {
    mInputStream = new DataInputStream(input);
    mOutputStream = new DataOutputStream(output);
  }

  /**
   * Generates a new instance from a {@link PairingContext}.
   *
   * @param context the {@link PairingContext}
   * @return the new instance
   */
  public static JsonWireAdapter fromContext(PairingContext context) {
    return new JsonWireAdapter(context.getPeerInputStream(), context
        .getPeerOutputStream());
  }

  public PoloMessage getNextMessage() throws IOException, PoloException {
    byte[] payloadLenBytes = new byte[4];
    mInputStream.readFully(payloadLenBytes);
    long payloadLen = PoloUtil.intBigEndianBytesToLong(payloadLenBytes);
    byte[] outerJsonBytes = new byte[(int) payloadLen];
    mInputStream.readFully(outerJsonBytes);
    return parseOuterMessageString(new String(outerJsonBytes));
  }

  public PoloMessage parseOuterMessageString(String outerString)
      throws PoloException {
    JSONObject outerMessage;
    try {
      outerMessage = new JSONObject(outerString);
    } catch (JSONException e) {
      throw new PoloException("Error parsing incoming message", e);
    }
    return JsonMessageBuilder.outerJsonToPoloMessage(outerMessage);
  }

  public PoloMessage getNextMessage(PoloMessageType type) throws IOException,
      PoloException {
    PoloMessage message = getNextMessage();
    if (message.getType() != type) {
      throw new PoloException("Wrong message type (wanted " + type + ", got "
          + message.getType() + ")");
    }
    return message;
  }

  public void sendErrorMessage(Exception exception) throws IOException {
    try {
      writeJson(JsonMessageBuilder.getErrorJson(exception));
    } catch (PoloException e) {
      throw new IOException("Error sending error message");
    }
  }

  public void sendMessage(PoloMessage message) throws IOException {
    String outString;
    JSONObject outerJson;

    try {
      outerJson = JsonMessageBuilder.getOuterJson(message);
    } catch (PoloException e) {
      throw new IOException("Error generating message");
    }

    writeJson(outerJson);
  }

  /**
   * Writes a {@link JSONObject} to the output stream as a {@link String}.
   *
   * @param  message      the message to write
   * @throws IOException  on error generating the serialized message
   */
  private void writeJson(JSONObject message) throws IOException {
    byte[] outBytes = message.toString().getBytes();
    mOutputStream.write(PoloUtil.intToBigEndianIntBytes(outBytes.length));
    mOutputStream.write(outBytes);
  }

}