/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * 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.
 */
#include <arpa/inet.h>
#include <errno.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>

#include "Log.h"

#include "ClientSocket.h"

ClientSocket::ClientSocket()
    : mSocket(-1),
      mTimeoutEnabled(false)
{

}

ClientSocket::~ClientSocket()
{
    release();
}

bool ClientSocket::init(const char* hostIp, int port, bool enableTimeout)
{
    LOGD("ClientSocket::init");
    mSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (mSocket < 0) {
        LOGE("cannot open socket %d", errno);
        return false;
    }
    int reuse = 1;
    if (setsockopt(mSocket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {
        LOGE("setsockopt error %d", errno);
        release();
        return false;
    }

    struct sockaddr_in serverAddr;
    bzero((char*)&serverAddr, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(port);
    if (inet_pton(AF_INET, hostIp, &serverAddr.sin_addr) != 1) {
        release();
        LOGE("inet_pton failed %d", errno);
        return false;
    }
    if (connect(mSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {
        release();
        LOGE("cannot connect socket %d", errno);
        return false;
    }
    mTimeoutEnabled = enableTimeout;
    return true;
}

const int ZERO_RW_SLEEP_TIME_US = 10;

// make non-blocking mode only during read. This allows supporting time-out for read
bool ClientSocket::readData(char* data, int len, int timeoutInMs)
{
    bool useTimeout = (mTimeoutEnabled && (timeoutInMs > 0));
    int flOriginal = 0;
    int timeInSec = 0;
    int timeInUs = 0;
    if (useTimeout) {
        flOriginal = fcntl(mSocket, F_GETFL,0);
        if (flOriginal == -1) {
            LOGE("fcntl error %d", errno);
            return false;
        }
        if (fcntl(mSocket, F_SETFL, flOriginal | O_NONBLOCK) == -1) {
            LOGE("fcntl error %d", errno);
            return false;
        }
        timeInSec = timeoutInMs / 1000;
        timeInUs = (timeoutInMs % 1000) * 1000;
    }
    bool result = true;
    int read;
    int toRead = len;
    while (toRead > 0) {
        if (useTimeout) {
            fd_set rfds;
            struct timeval tv;
            tv.tv_sec = timeInSec;
            tv.tv_usec = timeInUs;
            FD_ZERO(&rfds);
            FD_SET(mSocket, &rfds);
            if (select(mSocket + 1, &rfds, NULL, NULL, &tv) == -1) {
                LOGE("select failed");
                result = false;
                break;
            }
            if (!FD_ISSET(mSocket, &rfds)) {
                LOGE("socket read timeout");
                result = false;
                break;
            }
        }
        read = recv(mSocket, (void*)data, toRead, 0);
        if (read > 0) {
            toRead -= read;
            data += read;
        } else if (read == 0) {
            // in blocking mode, zero read mean's peer closed.
            // in non-blocking mode, select said that there is data. so it should not happen
            LOGE("zero read, peer closed or what?, nonblocking: %d", useTimeout);
            result = false;
            break;
        } else {
            LOGE("recv returned %d", read);
            result = false;
            break;
        }
    }
    if (useTimeout) {
        fcntl(mSocket, F_SETFL, flOriginal); // now blocking again
    }
    return result;
}

bool ClientSocket::sendData(const char* data, int len)
{
    int sent;
    int toSend = len;
    while (toSend > 0) {
        sent = send(mSocket, (void*)data, (size_t)toSend, 0);
        if (sent > 0) {
            toSend -= sent;
            data += sent;
        } else if (sent == 0) { // no more buffer?
            usleep(ZERO_RW_SLEEP_TIME_US); // just wait
        } else {
            LOGE("send returned %d, error %d", sent, errno);
            return false;
        }
    }
    return true;
}

void ClientSocket::release()
{
    if (mSocket != -1) {
        close(mSocket);
        mSocket = -1;
    }
}