//  Copyright (c) 2014 Thomas Goyne <tgoyne@gmail.com>
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.

#include "filehandle.h"

#include "utils.h"

#include <cstdarg>

extern "C" {
#include <libavformat/avio.h>
}

static AVIOContext *ffms_fopen(const char *filename, const char *mode) {
    int flags = 0;
    if (strchr(mode, 'r'))
        flags |= AVIO_FLAG_READ;
    if (strchr(mode, 'w'))
        flags |= AVIO_FLAG_WRITE;

    AVIOContext *ctx;
    int ret = avio_open2(&ctx, filename, flags, nullptr, nullptr);
    if (ret < 0)
        return nullptr;
    return ctx;
}

FileHandle::FileHandle(const char *filename, const char *mode, int error_source, int error_cause)
    : avio(ffms_fopen(filename, mode))
    , filename(filename)
    , error_source(error_source)
    , error_cause(error_cause) {
    if (!avio)
        throw FFMS_Exception(error_source, FFMS_ERROR_NO_FILE,
            "Failed to open '" + this->filename + "'");
}

FileHandle::~FileHandle() {
    avio_close(avio);
}

void FileHandle::Seek(int64_t offset, int origin) {
    int64_t ret = avio_seek(avio, offset, origin);
    if (ret < 0)
        throw FFMS_Exception(error_source, error_cause,
            "Failed to seek in '" + filename + "': " + AVErrorToString(ret));
}

int64_t FileHandle::Tell() {
    int64_t ret = avio_tell(avio);
    if (ret < 0)
        throw FFMS_Exception(error_source, error_cause,
            "Failed to read position in '" + filename + "': " + AVErrorToString(ret));
    return ret;
}

size_t FileHandle::Read(char *buffer, size_t size) {
    int count = avio_read(avio, (unsigned char *)buffer, size);
    if (count < 0) {
        throw FFMS_Exception(error_source, FFMS_ERROR_FILE_READ,
            "Failed to read from '" + filename + "': " + AVErrorToString(count));
    } else if (avio_feof(avio)) {
        // "Similar to feof() but also returns nonzero on read errors" -- FFmpeg docs
        if (avio->error != 0 && avio->error != AVERROR_EOF) {
            throw FFMS_Exception(error_source, FFMS_ERROR_FILE_READ,
                "Failed to read from '" + filename + "': " + AVErrorToString(avio->error));
        }
    }
    return (size_t)count;
}

size_t FileHandle::Write(const char *buffer, size_t size) {
    avio_write(avio, (const unsigned char *)buffer, size);
    avio_flush(avio);
    if (avio->error < 0)
        throw FFMS_Exception(error_source, FFMS_ERROR_FILE_WRITE,
            "Failed to write to '" + filename + "': " + AVErrorToString(avio->error));
    return size;
}

int64_t FileHandle::Size() {
    int64_t size = avio_size(avio);
    if (size < 0)
        throw FFMS_Exception(error_source, FFMS_ERROR_FILE_READ,
            "Failed to get file size for '" + filename + "': " + AVErrorToString(size));
    return size;
}

int FileHandle::Printf(const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);

    std::vector<char> OutBuffer(100);
    int ret = -1;
    while (OutBuffer.size() < 1024 * 1024) {
        ret = vsnprintf(OutBuffer.data(), OutBuffer.size(), fmt, args);
        if (ret > 0 && ret < (int)OutBuffer.size())
            break;
        OutBuffer.resize(OutBuffer.size() * 2);
    }

    va_end(args);

    avio_write(avio, reinterpret_cast<const unsigned char *>(OutBuffer.data()), ret);
    avio_flush(avio);

    return avio->error < 0 ? avio->error : ret;
}
