/**
 * Copyright 2000-2006 DFKI GmbH.
 * All Rights Reserved.  Use is subject to license terms.
 * 
 * Permission is hereby granted, free of charge, to use and distribute
 * this software and its documentation without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of this work, and to
 * permit persons to whom this work is furnished to do so, subject to
 * the following conditions:
 * 
 * 1. The code must retain the above copyright notice, this list of
 *    conditions and the following disclaimer.
 * 2. Any modifications must be clearly marked as such.
 * 3. Original authors' names are not deleted.
 * 4. The authors' names are not used to endorse or promote products
 *    derived from this software without specific prior written
 *    permission.
 *
 * DFKI GMBH AND THE CONTRIBUTORS TO THIS WORK DISCLAIM ALL WARRANTIES WITH
 * REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL DFKI GMBH NOR THE
 * CONTRIBUTORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
 * THIS SOFTWARE.
 */

#ifdef _WIN32
// use compiler option -L/<path to>/libwsock32.a
#include <winsock.h>
#else
#include <netdb.h>
#endif

#include <stdlib.h>
#include <iostream>
#include <string.h>

#include "MaryClient.h"

using namespace std;

/**
 * A C++ implementation of a simple client to the MARY TTS system.
 * result: an empty string serving as the container for the output.
 *    It will return text or audio data; text data will be encoded as UTF-8.
 * inputText: the UTF-8 encoded text (or XML document) to send as a request
 * maryInFormat: the input type of the data in inputText, e.g. TEXT
 * maryOutFormat: the output type to produce, e.g. MBROLA, AUDIO
 * locale: the language of the input, e.g. EN-US, DE
 * audioType: for AUDIO output, the type of audio data to produce,
 *            e.g. WAVE or MP3.
 * voice: the voice to be used, e.g. cmu-slt-hsmm, bits3.
 * effects: the list of effects to be generated.
 * return value: 0 on success, negative on failure.
 */
int
MaryClient::maryQuery( int server_port,
                       string server_host,
                       string& result, 
                       string inputText,
                       string maryInFormat,
                       string maryOutFormat,
                       string locale,
                       string audioType,
                       string voice,
                       string effects ) {

  // prepare the request
  string query = "MARY";
  query += " IN=" + maryInFormat;
  query += " OUT=" + maryOutFormat;
  query += " LOCALE=" + locale;	// remove this line, if using an older version than MARY 4.0
  query += " AUDIO=" + audioType;
  query += " VOICE=" + voice;
  if (effects != "") {
    query += " EFFECTS=" + effects;
  }
  query += "\012\015";

  //cout << "Constructed query: " << query << endl;

  // declare connection stuff
  struct sockaddr_in maryServer;
  struct sockaddr_in maryClient;
  struct hostent* hostInfo;

  // declare variables
  int maryInfoSocket;
  int maryDataSocket;

  // set configuration parameters

  // get host information
  hostInfo = gethostbyname (server_host.c_str());

  if (hostInfo == NULL)
  {
    return -2;
  }


  // create a tcp connection to the mary server
  maryInfoSocket = socket (AF_INET, SOCK_STREAM, 0);

  // verify that the socket could be opened successfully
  if (maryInfoSocket == -1)
  {
    return -2;
  }
  else
  // autoflush stdout, bind and connect
  {
    maryClient.sin_family = AF_INET;
    maryClient.sin_port = htons (0);
    maryClient.sin_addr.s_addr = INADDR_ANY;

    int status = bind (maryInfoSocket, (struct sockaddr*) &maryClient, sizeof (maryClient));

    if (status != 0)
    {
      return -2;
    }

    maryServer.sin_family = AF_INET;
    maryServer.sin_port = htons (server_port);
    memcpy ((char*) &maryServer.sin_addr.s_addr, hostInfo->h_addr_list [0], hostInfo->h_length);

    status = connect (maryInfoSocket, (struct sockaddr*) &maryServer, sizeof (maryServer));

    if (status != 0)
    {
      return -2;
    }
  }

  // send request to the Mary server
  if (send (maryInfoSocket, query.c_str (), query.size (), 0) == -1)
  {
    return -2;
  }


  // receive the request id
  char id [32] = "";

  if (recv (maryInfoSocket, id, 32, 0) == -1)
  {
    return -2;
  }

  //cout << "Read id: " << id << endl;

  // create a tcp connection to the mary server
  maryDataSocket = socket (AF_INET, SOCK_STREAM, 0);

  // verify that the socket could be opened successfully
  if (maryDataSocket == -1)
  {
    return -2;
  }
  else
  // autoflush stdout, bind and connect
  {
    maryClient.sin_family = AF_INET;
    maryClient.sin_port = htons (0);
    maryClient.sin_addr.s_addr = INADDR_ANY;

    int status = bind (maryDataSocket, (struct sockaddr*) &maryClient, sizeof (maryClient));

    if (status != 0)
    {
      return -2;
    }

    maryServer.sin_family = AF_INET;
    maryServer.sin_port = htons (server_port);
    memcpy ((char*) &maryServer.sin_addr.s_addr, hostInfo->h_addr_list [0], hostInfo->h_length);

    status = connect (maryDataSocket, (struct sockaddr*) &maryServer, sizeof (maryServer));

    if (status != 0)
    {
      return -2;
    }
  }


  // send the request id to the Mary server
  if (send (maryDataSocket, id, strlen (id), 0) == -1)
  {
    return -2;
  }

  //cout << "Sending request: " << inputText << endl;

  // send the query to the Mary server
  if (send (maryDataSocket, inputText.c_str (), inputText.size (), 0) == -1)
  {
    return -2;
  }

  if (send (maryDataSocket, "\012\015", 2, 0) == -1)
  {
    return -2;
  }


  // shutdown data socket
  shutdown (maryDataSocket, 1);


  //cout << "Reading result" << endl;

  unsigned int total_bytes = 0;
  int recv_bytes = 0;
  char data [1024] = "";

  result [0] = '\0';

  // receive the request result
  do
  {
    data [0] = '\0';

    recv_bytes = recv (maryDataSocket, data, 1024, 0);

    if (recv_bytes == -1)
    {
      return -2;
    }
    else if (recv_bytes > 0)
    {
      //cout << "("<<recv_bytes<<")" << endl;
      total_bytes += recv_bytes;
      data [recv_bytes] = '\0';

      if (maryOutFormat == "AUDIO")
      {
        for (unsigned int i=0; i<recv_bytes; i++)
        {
          result += data [i];
        }
      }
      else
      {
        result += data;
      }
    }
  } while (recv_bytes != 0);

  if (result.size () != total_bytes)
  {
    cerr << "error: total bytes received != result bytes!" << endl;
    cerr << "       total bytes received = " << total_bytes << endl;
    cerr << "       result bytes = " << result.size () << endl;
  }

  // receive the request error
  do
  {
    data [0] = '\0';

    recv_bytes = recv (maryInfoSocket, data, 1024, 0);

    if (recv_bytes == -1)
    {
      return -2;
    }
    else if (recv_bytes > 0)
    {
      cerr << endl << "Mary error code: " << data << endl;
      return -3;
    }
  } while (recv_bytes != 0);

#ifdef _WIN32
  // close all open sockets Windows
  closesocket (maryInfoSocket);
  closesocket (maryDataSocket);
#else
  // close all open sockets UNIX
  close (maryInfoSocket);
  close (maryDataSocket);
#endif

  return 0;
}
