#ifndef OSMIUM_UTIL_DATA_FILE_HPP
#define OSMIUM_UTIL_DATA_FILE_HPP

/*

This file is part of Osmium (http://osmcode.org/libosmium).

Copyright 2013-2015 Jochen Topf <jochen@topf.org> and others (see README).

Boost Software License - Version 1.0 - August 17th, 2003

Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:

The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.

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, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

*/

#include <cerrno>
#include <cstddef>
#include <cstdio>
#include <stdexcept>
#include <string>
#include <system_error>

#ifdef _WIN32
# include <io.h>
# include <windows.h>
#endif

#include <osmium/util/file.hpp>

namespace osmium {

    namespace util {

        /**
         * Class wrapper for convenient access to some low-level file
         * functions.
         */
        class DataFile {

            FILE* m_file;

        public:

            /**
             * Create and open a temporary file. It is removed after opening.
             *
             * @throws std::system_error if something went wrong.
             */
            DataFile() :
                m_file(::tmpfile()) {
                if (!m_file) {
                    throw std::system_error(errno, std::system_category(), "tmpfile failed");
                }
            }

            /**
             * Create and open a temporary file with the specified size. It
             * is removed after opening.
             *
             * @throws std::system_error if something went wrong.
             */
            explicit DataFile(size_t size) :
                DataFile() {
                grow(size);
            }

            /**
             * Create and open a named file.
             *
             * @param filename the name of the file
             * @param writable should the file be writable?
             * @throws std::system_error if something went wrong.
             */
            DataFile(const char* filename, bool writable) :
                m_file(::fopen(filename, writable ? "wb+" : "rb" )) {
                if (!m_file) {
                    throw std::system_error(errno, std::system_category(), "fopen failed");
                }
            }

            /**
             * Create and open a named file.
             *
             * @param filename the name of the file
             * @param writable should the file be writable?
             * @throws std::system_error if something went wrong.
             */
            DataFile(const std::string& filename, bool writable) :
                DataFile(filename.c_str(), writable) {
            }

            /**
             * In boolean context the DataFile class returns true if the file
             * is open.
             */
            operator bool() const noexcept {
                return m_file != nullptr;
            }

            /**
             * Close the file.
             *
             * Does nothing if the file is already closed.
             *
             * @throws std::system_error if file could not be closed
             */
            void close() {
                if (m_file) {
                    if (::fclose(m_file) != 0) {
                        throw std::system_error(errno, std::system_category(), "fclose failed");
                    }
                    m_file = nullptr;
                }
            }

            ~DataFile() noexcept {
                try {
                    close();
                } catch (std::system_error&) {
                    // ignore
                }
            }

            /**
             * Get file descriptor of underlying file.
             *
             * @throws std::runtime_errro if file is not open
             * @throws std::system_error if fileno(3) call failed
             */
            int fd() const {
                if (!m_file) {
                    throw std::runtime_error("no open file");
                }

                int fd = ::fileno(m_file);

                if (fd == -1) {
                    throw std::system_error(errno, std::system_category(), "fileno failed");
                }

                return fd;
            }

            /**
             * Ask the operating system for the size of this file.
             *
             * @throws std::system_error if fstat(2) call failed
             */
            size_t size() const {
                return osmium::util::file_size(fd());
            }

            /**
             * Grow file to given size.
             *
             * If the file is large enough already, nothing is done.
             * The file is never shrunk.
             *
             * @throws std::system_error if ftruncate(2) call failed
             */
            void grow(size_t new_size) const {
                if (size() < new_size) {
                    osmium::util::resize_file(fd(), new_size);
                }
            }

        }; // class DataFile

    } // namespace util

} // namespace osmium


#endif // OSMIUM_UTIL_DATA_FILE_HPP
