/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 "write_file.h"

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>

#include "common/config/config.h"
#include "common/logger/elog.h"
#include "utils/errno_define.h"

#ifdef _WIN32
#include <io.h>
int fsync(int);
#endif

using namespace common;

namespace storage {

int WriteFile::create(const std::string& file_path, int flags, mode_t mode) {
    if (fd_ > 0) {
        // log_err("file already opened, fd=%d", fd_);
        ASSERT(false);
        return E_ALREADY_EXIST;
    }
    path_ = file_path;
    return do_create(flags, mode);
}

int WriteFile::do_create(int flags, mode_t mode) {
    int ret = E_OK;
    // TODO make sure no same file exists
    fd_ = ::open(path_.c_str(), flags, mode);
    if (fd_ < 0) {
        // log_err("open file error, path=%s, errno=%d", path_.c_str(), errno);
        ret = E_FILE_OPEN_ERR;
    } else {
    }
    return ret;
}

int WriteFile::write(const char* buf, uint32_t len) {
    ASSERT(fd_ > 0);

#if 0  // DEBUG_SE
    struct stat statbuf;
    if (fstat(fd_, &statbuf) < 0) {
      perror("fstat");
      ASSERT(false);
    }
    printf("writer buffer(%u bytes) to file at %zu\n", len, statbuf.st_size);
    for (uint32_t i = 0; i < len;) {
      printf("0x%02x ", (uint8_t)buf[i]);
      if ((++i) % 16 == 0) {
        printf("\n");
      }
    }
    printf("\n");
#endif

    int ret = E_OK;
    uint32_t write_done = 0;
    while (write_done < len && IS_SUCC(ret)) {
        int32_t cur_write = ::write(fd_, buf + write_done, len - write_done);
        if (cur_write < 0) {
            ret = E_FILE_WRITE_ERR;
            // log_err("file writer error, path=%s, error=%d", path_.c_str(),
            // errno);
        } else {
            write_done += cur_write;
        }
    }
    return ret;
}

int WriteFile::sync() {
    ASSERT(fd_ > 0);
    if (::fsync(fd_) < 0) {
        // log_err("file fsync error, path=%s, errno=%d", path_.c_str(), errno);
        return E_FILE_SYNC_ERR;
    }
    return E_OK;
}

int WriteFile::close() {
    // Idempotent: already closed is not an error
    if (fd_ < 0) {
#ifdef DEBUG_SE
        std::cout << "file already closed, path=" << path_;
#endif
        return E_OK;
    }
    if (::close(fd_) < 0) {
#ifdef DEBUG_SE
        std::cout << "failed to close " << path_ << " errorno " << errno
                  << std::endl;
#endif
        // log_err("file close error, path=%s, errno=%d", path_.c_str(), errno);
        return E_FILE_CLOSE_ERR;
    }
    fd_ = -1;
#ifdef DEBUG_SE
    std::cout << "close finish" << std::endl;
#endif
    return E_OK;
}

int WriteFile::truncate(int64_t size) {
    ASSERT(fd_ > 0);
#ifdef _WIN32
    if (_chsize_s(fd_, static_cast<long>(size)) != 0) {
        return E_FILE_WRITE_ERR;
    }
#else
    if (::ftruncate(fd_, static_cast<off_t>(size)) < 0) {
        return E_FILE_WRITE_ERR;
    }
#endif
    return E_OK;
}

int WriteFile::seek_to_end() {
    ASSERT(fd_ > 0);
#ifdef _WIN32
    if (_lseeki64(fd_, 0, SEEK_END) < 0) {
        return E_FILE_READ_ERR;
    }
#else
    if (::lseek(fd_, 0, SEEK_END) < 0) {
        return E_FILE_READ_ERR;
    }
#endif
    return E_OK;
}

int64_t WriteFile::get_position() {
    if (fd_ < 0) {
        return 0;
    }
    // SEEK_CUR with offset 0 returns current position without moving
#ifdef _WIN32
    int64_t pos = _lseeki64(fd_, 0, SEEK_CUR);
    return (pos < 0) ? 0 : pos;
#else
    off_t pos = ::lseek(fd_, 0, SEEK_CUR);
    return (pos < 0) ? 0 : static_cast<int64_t>(pos);
#endif
}

}  // end namespace storage

#ifdef _WIN32
int fsync(int fd) { return _commit(fd); }
#endif
