/*
 * Copyright (C) 2009 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.
 */

//#define LOG_NDEBUG 0
#define LOG_TAG "HTTPDataSource"
#include <utils/Log.h>

#include "include/stagefright_string.h"
#include "include/HTTPStream.h"

#include <stdlib.h>

#include <cutils/properties.h>
#include <media/stagefright/HTTPDataSource.h>
#include <media/stagefright/MediaDebug.h>

namespace android {

status_t HTTPDataSource::connectWithRedirectsAndRange(off_t rangeStart) {
    string host = mStartingHost.string();
    string path = mStartingPath.string();
    int port = mStartingPort;

    LOGV("Connecting to host '%s', port %d, path '%s'",
         host.c_str(), port, path.c_str());

    int numRedirectsRemaining = 5;
    while (numRedirectsRemaining-- > 0) {
        {
            Mutex::Autolock autoLock(mStateLock);
            if (mState == DISCONNECTED) {
                return UNKNOWN_ERROR;
            }
        }

        status_t err = mHttp->connect(host.c_str(), port);

        if (err != OK) {
            return err;
        }

        String8 request;
        request.append("GET ");
        request.append(path.c_str());
        request.append(" HTTP/1.1\r\n");
        request.append(mHeaders);
        request.append("Host: ");
        request.append(host.c_str());
        request.append("\r\n");

        if (rangeStart > 0) {
            char range[128];
            sprintf(range, "Range: bytes=%ld-\r\n", rangeStart);

            request.append(range);
        }

        request.append("\r\n");

        err = mHttp->send(request.string());

        if (err != OK) {
            return err;
        }

        int httpStatus;
        err = mHttp->receive_header(&httpStatus);

        if (err != OK) {
            return err;
        }

        if (httpStatus >= 200 && httpStatus < 300) {
            applyTimeoutResponse();
            return OK;
        }

        if (httpStatus != 301 && httpStatus != 302) {
            LOGE("HTTP request failed w/ http status %d", httpStatus);
            return ERROR_IO;
        }

        string location;
        CHECK(mHttp->find_header_value("Location", &location));

        CHECK(string(location, 0, 7) == "http://");
        location.erase(0, 7);
        string::size_type slashPos = location.find('/');
        if (slashPos == string::npos) {
            slashPos = location.size();
            location += '/';
        }

        mHttp->disconnect();

        LOGV("Redirecting to %s\n", location.c_str());

        host = string(location, 0, slashPos);

        string::size_type colonPos = host.find(':');
        if (colonPos != string::npos) {
            const char *start = host.c_str() + colonPos + 1;
            char *end;
            long tmp = strtol(start, &end, 10);
            CHECK(end > start && (*end == '\0'));

            port = (tmp >= 0 && tmp < 65536) ? (int)tmp : 80;

            host.erase(colonPos, host.size() - colonPos);
        } else {
            port = 80;
        }

        path = string(location, slashPos);

        mStartingHost = host.c_str();
        mStartingPath = path.c_str();
        mStartingPort = port;
    }

    return ERROR_IO;
}

void HTTPDataSource::applyTimeoutResponse() {
    string timeout;
    if (mHttp->find_header_value("X-SocketTimeout", &timeout)) {
        const char *s = timeout.c_str();
        char *end;
        long tmp = strtol(s, &end, 10);
        if (end == s || *end != '\0') {
            LOGW("Illegal X-SocketTimeout value given.");
            return;
        }

        LOGI("overriding default timeout, new timeout is %ld seconds", tmp);
        mHttp->setReceiveTimeout(tmp);
    }
}

HTTPDataSource::HTTPDataSource(
        const char *uri, const KeyedVector<String8, String8> *headers) {
    CHECK(!strncasecmp("http://", uri, 7));

    string host;
    string path;
    int port;

    char *slash = strchr(uri + 7, '/');
    if (slash == NULL) {
        host = uri + 7;
        path = "/";
    } else {
        host = string(uri + 7, slash - (uri + 7));
        path = slash;
    }

    char *colon = strchr(host.c_str(), ':');
    if (colon == NULL) {
        port = 80;
    } else {
        char *end;
        long tmp = strtol(colon + 1, &end, 10);
        CHECK(end > colon + 1);
        CHECK(tmp > 0 && tmp < 65536);
        port = tmp;

        host = string(host, 0, colon - host.c_str());
    }

    mStartingHost = host.c_str();
    mStartingPath = path.c_str();
    mStartingPort = port;

    init(headers);
}

HTTPDataSource::HTTPDataSource(
        const char *_host, int port, const char *_path,
        const KeyedVector<String8, String8> *headers) {
    mStartingHost = _host;
    mStartingPath = _path;
    mStartingPort = port;

    init(headers);
}

void HTTPDataSource::init(const KeyedVector<String8, String8> *headers) {
    mState = DISCONNECTED;
    mHttp = new HTTPStream;

    initHeaders(headers);

    mBuffer = malloc(kBufferSize);

    mNumRetriesLeft = kMaxNumRetries;
}

status_t HTTPDataSource::connect() {
    {
        Mutex::Autolock autoLock(mStateLock);

        if (mState != DISCONNECTED) {
            return ERROR_ALREADY_CONNECTED;
        }

        mState = CONNECTING;
    }

    mBufferLength = 0;
    mBufferOffset = 0;
    mContentLengthValid = false;

    status_t err = connectWithRedirectsAndRange(0);

    if (err != OK) {
        Mutex::Autolock autoLock(mStateLock);

        if (mState != CONNECTING) {
            LOGV("connect() cancelled");
        }
        mState = DISCONNECTED;

        return err;
    }

    string value;
    if (mHttp->find_header_value("Content-Length", &value)) {
        char *end;
        mContentLength = strtoull(value.c_str(), &end, 10);
        mContentLengthValid = true;
    }

    Mutex::Autolock autoLock(mStateLock);

    if (mState != CONNECTING) {
        // disconnect was called when we had just successfully connected.
        LOGV("connect() cancelled (we had just succeeded connecting)");

        mHttp->disconnect();
        return UNKNOWN_ERROR;
    }

    mState = CONNECTED;

    return OK;
}

void HTTPDataSource::disconnect() {
    Mutex::Autolock autoLock(mStateLock);

    if (mState == CONNECTING || mState == CONNECTED) {
        mHttp->disconnect();
        mState = DISCONNECTED;
    }
}

status_t HTTPDataSource::initCheck() const {
    Mutex::Autolock autoLock(mStateLock);

    return (mState == CONNECTED) ? (status_t)OK : ERROR_NOT_CONNECTED;
}

status_t HTTPDataSource::getSize(off_t *size) {
    *size = 0;

    {
        Mutex::Autolock autoLock(mStateLock);
        if (mState != CONNECTED) {
            return ERROR_NOT_CONNECTED;
        }
    }

    if (!mContentLengthValid) {
        return ERROR_UNSUPPORTED;
    }

    *size = mContentLength;

    return OK;
}

HTTPDataSource::~HTTPDataSource() {
    disconnect();

    delete mHttp;
    mHttp = NULL;

    free(mBuffer);
    mBuffer = NULL;
}

ssize_t HTTPDataSource::sendRangeRequest(size_t offset) {
    status_t err = connectWithRedirectsAndRange(offset);

    if (err != OK) {
        return err;
    }

    string value;
    if (!mHttp->find_header_value("Content-Length", &value)) {
        return kBufferSize;
    }

    char *end;
    unsigned long contentLength = strtoul(value.c_str(), &end, 10);

    return contentLength;
}

ssize_t HTTPDataSource::readAt(off_t offset, void *data, size_t size) {
    LOGV("readAt %ld, size %d", offset, size);

rinse_repeat:
    {
        Mutex::Autolock autoLock(mStateLock);
        if (mState != CONNECTED) {
            return ERROR_NOT_CONNECTED;
        }
    }

    if (offset >= mBufferOffset
            && offset < (off_t)(mBufferOffset + mBufferLength)) {
        size_t num_bytes_available = mBufferLength - (offset - mBufferOffset);

        size_t copy = num_bytes_available;
        if (copy > size) {
            copy = size;
        }

        memcpy(data, (const char *)mBuffer + (offset - mBufferOffset), copy);

        if (copy < size) {
            LOGV("short read (1), returning %d vs. %d requested", copy, size);
        }

        return copy;
    }

    ssize_t contentLength = 0;
    if (offset != (off_t)(mBufferOffset + mBufferLength)) {
        LOGV("new range offset=%ld (old=%ld)",
             offset, mBufferOffset + mBufferLength);

        mHttp->disconnect();

        contentLength = sendRangeRequest(offset);

        if (contentLength > kBufferSize) {
            contentLength = kBufferSize;
        }
    } else {
        contentLength = kBufferSize;
    }

    mBufferOffset = offset;

    if (mContentLengthValid
            && mBufferOffset + contentLength >= (off_t)mContentLength) {
        // If we never triggered a range request but know the content length,
        // make sure to not read more data than there could be, otherwise
        // we'd block indefinitely if the server doesn't close the connection.

        contentLength = mContentLength - mBufferOffset;
    }

    if (contentLength <= 0) {
        return contentLength;
    }

    ssize_t num_bytes_received = mHttp->receive(mBuffer, contentLength);

    if (num_bytes_received < 0
            || (mContentLengthValid && num_bytes_received < contentLength)) {
        if (mNumRetriesLeft-- > 0) {
            mHttp->disconnect();
            mBufferLength = 0;
            num_bytes_received = connectWithRedirectsAndRange(mBufferOffset);
            if (num_bytes_received == OK) {
                LOGI("retrying connection succeeded.");
                goto rinse_repeat;
            }
            LOGE("retrying connection failed");
        }

        mBufferLength = 0;

        return num_bytes_received;
    }

    mBufferLength = (size_t)num_bytes_received;

    size_t copy = mBufferLength;
    if (copy > size) {
        copy = size;
    }

    memcpy(data, mBuffer, copy);

    return copy;
}

void HTTPDataSource::initHeaders(
        const KeyedVector<String8, String8> *overrides) {
    mHeaders = String8();

    mHeaders.append("User-Agent: stagefright/1.0 (Linux;Android ");

#if (PROPERTY_VALUE_MAX < 8)
#error "PROPERTY_VALUE_MAX must be at least 8"
#endif

    char value[PROPERTY_VALUE_MAX];
    property_get("ro.build.version.release", value, "Unknown");
    mHeaders.append(value);
    mHeaders.append(")\r\n");

    if (overrides == NULL) {
        return;
    }

    for (size_t i = 0; i < overrides->size(); ++i) {
        String8 line;
        line.append(overrides->keyAt(i));
        line.append(": ");
        line.append(overrides->valueAt(i));
        line.append("\r\n");

        mHeaders.append(line);
    }
}

uint32_t HTTPDataSource::flags() {
    uint32_t f = kWantsPrefetching;

    if (!strcasecmp(mStartingHost.string(), "localhost")
            || !strcmp(mStartingHost.string(), "127.0.0.1")) {
        f |= kStreamedFromLocalHost;
    }

    return f;
}

}  // namespace android

