// See the file "COPYING" in the main distribution directory for copyright.

#pragma once

#include <fcntl.h>
#include <list>
#include <string>
#include <utility>

#include "zeek/Val.h"
#include "zeek/util.h"

namespace zeek {

namespace detail {

class PrintStmt;
class Attributes;

extern void do_print_stmt(const std::vector<ValPtr>& vals);

} // namespace detail

class RecordVal;
class Type;
using TypePtr = IntrusivePtr<Type>;

class File;
using FilePtr = IntrusivePtr<File>;

class File final : public Obj {
public:
    explicit File(FILE* arg_f);
    File(FILE* arg_f, const char* filename, const char* access);
    File(const char* filename, const char* access);
    ~File() override;

    const char* Name() const;

    // Returns false if an error occurred.
    bool Write(const char* data, int len = 0);

    void Flush() { fflush(f); }

    FILE* Seek(long position); // seek to absolute position

    void SetBuf(bool buffered); // false=line buffered, true=fully buffered

    const TypePtr& GetType() const { return t; }

    // Whether the file is open in a general sense; it might
    // not be open as a Unix file due to our management of
    // a finite number of FDs.
    bool IsOpen() const { return is_open; }

    // Returns true if the close made sense, false if it was already
    // closed, not active, or whatever.
    bool Close();

    void Describe(ODesc* d) const override;

    // Rotates the logfile. Returns rotate_info.
    RecordVal* Rotate();

    // Set &raw_output attribute.
    void SetAttrs(detail::Attributes* attrs);

    // Returns the current size of the file, after fresh stat'ing.
    double Size();

    // Close all files which are currently open.
    static void CloseOpenFiles();

    // Get the file with the given name, opening it if it doesn't yet exist.
    static FilePtr Get(const char* name);

    void EnableRawOutput() { raw_output = true; }
    bool IsRawOutput() const { return raw_output; }

protected:
    friend void detail::do_print_stmt(const std::vector<ValPtr>& vals);

    File() { Init(); }
    void Init();

    /**
     * If file is given, it's an open file to use already.
     * If file is not given and mode is, the filename will be opened with that
     * access mode.
     */
    bool Open(FILE* f = nullptr, const char* mode = nullptr);

    void Unlink();

    // Returns nil if the file is not active, was in error, etc.
    // (Protected because we do not want anyone to write directly
    // to the file, but the PrintStmt friend uses this to check whether
    // it's really stdout.)
    FILE* FileHandle();

    // Raises a file_opened event.
    void RaiseOpenEvent();

    FILE* f = nullptr;
    TypePtr t;
    char* name = nullptr;
    char* access = nullptr;
    detail::Attributes* attrs = nullptr;
    double open_time = 0.0;
    bool is_open = false; // whether the file is open in a general sense
    bool buffered = false;
    bool raw_output = false;

    static constexpr int MIN_BUFFER_SIZE = 1024;

private:
    static std::list<std::pair<std::string, File*>> open_files;
};

} // namespace zeek
