#include "descriptorloader.h"
#include "unordered_map.h"

#include <google/protobuf/descriptor.pb.h>
#include <google/protobuf/duration.pb.h>
#include <google/protobuf/timestamp.pb.h>
#include <google/protobuf/wrappers.pb.h>
#include <google/protobuf/io/zero_copy_stream.h>

#include "EXTERN.h"
#include "perl.h"
#include "perl_unpollute.h"

using namespace google::protobuf::compiler;
using namespace google::protobuf;
using namespace gpd;
using namespace std;

#if __cplusplus < 201103L
namespace {
    string to_string(int value) {
        char buffer[30];

        // only used for error handling, so it does not have to be performant
        sprintf(buffer, "%d", value);

        return buffer;
    }
}
#endif

namespace {
    void PerlLogHandler(LogLevel level, const char *filename, int line,
                        const std::string &message) {
        const char *level_str = NULL;

        switch (level) {
        case LOGLEVEL_WARNING:
            level_str = "";
            break;
        case LOGLEVEL_ERROR:
            level_str = " error";
            break;
        case LOGLEVEL_FATAL:
            level_str = " fatal error";
            break;
        default:
            return;
        }

        warn("protobuf%s: %s [%s:%d]", level_str, message.c_str(), filename, line);
    }

    class CaptureWarnings {
    public:
        CaptureWarnings() {
            previous = SetLogHandler(PerlLogHandler);
        }

        ~CaptureWarnings() {
            SetLogHandler(previous);
        }

    private:
        LogHandler *previous;
    };
}

SourceTreeDescriptorDatabaseWithFallback::SourceTreeDescriptorDatabaseWithFallback(
    SourceTree *source_tree, DescriptorDatabase *fallback_database) :
        source_tree(source_tree),
        database(source_tree),
        fallback_database(fallback_database) {
}

bool SourceTreeDescriptorDatabaseWithFallback::FindFileByName(
        const std::string& filename,
        FileDescriptorProto* output) {
    // this will open the file twice, but I prefer that to copy more of
    // SourceTreeDescriptorDatabase implementation
    google::protobuf::io::ZeroCopyInputStream *input = source_tree->Open(filename);

    if (input == NULL) {
        if (fallback_database->FindFileByName(filename, output)) {
            return true;
        }
        // if file is not found, FindFileByName will report the error,
        // no need to do it here (other than it would be slightly more efficient)
    }

    delete input;
    return database.FindFileByName(filename, output);
}

void DescriptorLoader::CollectMultiFileErrors::AddError(const string &filename, int line, int column, const string &message) {
    if (!errors.empty())
        errors += "\n";

    errors +=
        "Error during protobuf parsing: " +
        filename + ":" + to_string(line) + ":" + to_string(column) + ": " +
        message;
}

void DescriptorLoader::CollectMultiFileErrors::AddWarning(const string &filename, int line, int column, const string &message) {
    // seems to never be called, warnings go to log
    warn("Parsing protobuf file: %s:%d:%d: %s", filename.c_str(), line, column, message.c_str());
}

void DescriptorLoader::CollectMultiFileErrors::maybe_croak() {
    if (errors.empty())
        return;

    string copy = errors;

    errors.clear();
    croak("%s", copy.c_str());
}

DescriptorLoader::DescriptorLoader() :
        overlay_source_tree(&memory_source_tree, &disk_source_tree),
        generated_database(*DescriptorPool::generated_pool()),
        source_database(&overlay_source_tree, &generated_database),
        merged_source_binary_database(&binary_database, &source_database),
        merged_pool(&merged_source_binary_database, source_database.GetValidationErrorCollector()) {
    merged_pool.EnforceWeakDependencies(true);
    source_database.RecordErrorsTo(&multifile_error_collector);

    // make sure the descriptors are available in the generated pool (doing this for one descriptor
    // pulls in all the descriptors in the same file)
    Duration::descriptor();
    Timestamp::descriptor();
    FloatValue::descriptor();
    DescriptorProto::descriptor();
}

DescriptorLoader::~DescriptorLoader() { }

const FileDescriptor *DescriptorLoader::load_proto(const string &filename) {
    CaptureWarnings capture_warnings;

    return merged_pool.FindFileByName(filename);
}

const vector<const FileDescriptor *> DescriptorLoader::load_serialized(const char *buffer, size_t length) {
    CaptureWarnings capture_warnings;
    FileDescriptorSet fds;

    if (!fds.ParseFromArray(buffer, length))
        croak("Error deserializing message descriptors");
    vector<const FileDescriptor *> result;

    for (int i = 0, max = fds.file_size(); i < max; ++i) {
        const FileDescriptorProto &file = fds.file(i);

        if (!binary_database.Add(file))
            break;

        const FileDescriptor *file_def = merged_pool.FindFileByName(file.name());
        if (file_def == NULL)
            break;
        result.push_back(file_def);
    }

    return result;
}