/********************************************************************
 * 2014 -
 * open source under Apache License Version 2.0
 ********************************************************************/
/**
 * 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 "platform.h"

#include "Exception.h"
#include "ExceptionInternal.h"
#include "Thread.h"

#include <cstring>
#include <cassert>
#include <sstream>

namespace Hdfs {

function<bool(void)> ChecnOperationCanceledCallback;

namespace Internal {

bool CheckOperationCanceled() {
    if (ChecnOperationCanceledCallback && ChecnOperationCanceledCallback()) {
        THROW(HdfsCanceled, "Operation has been canceled by the user.");
    }

    return false;
}

const char * GetSystemErrorInfo(int eno) {
    static THREAD_LOCAL char message[64];
    char buffer[64], *pbuffer;
    pbuffer = buffer;
#ifdef STRERROR_R_RETURN_INT
    strerror_r(eno, buffer, sizeof(buffer));
#else
    pbuffer = strerror_r(eno, buffer, sizeof(buffer));
#endif
    snprintf(message, sizeof(message), "(errno: %d) %s", eno, pbuffer);
    return message;
}

static void GetExceptionDetailInternal(const Hdfs::HdfsException & e,
                                       std::stringstream & ss, bool topLevel);

static void GetExceptionDetailInternal(const std::exception & e,
                                       std::stringstream & ss, bool topLevel) {
    try {
        if (!topLevel) {
            ss << "Caused by\n";
        }

        ss << e.what();
    } catch (const std::bad_alloc & e) {
        return;
    }

    try {
        Hdfs::rethrow_if_nested(e);
    } catch (const Hdfs::HdfsException & nested) {
        GetExceptionDetailInternal(nested, ss, false);
    } catch (const std::exception & nested) {
        GetExceptionDetailInternal(nested, ss, false);
    }
}

static void GetExceptionDetailInternal(const Hdfs::HdfsException & e,
                                       std::stringstream & ss, bool topLevel) {
    try {
        if (!topLevel) {
            ss << "Caused by\n";
        }

        ss << e.msg();
    } catch (const std::bad_alloc & e) {
        return;
    }

    try {
        Hdfs::rethrow_if_nested(e);
    } catch (const Hdfs::HdfsException & nested) {
        GetExceptionDetailInternal(nested, ss, false);
    } catch (const std::exception & nested) {
        GetExceptionDetailInternal(nested, ss, false);
    }
}

const char* GetExceptionDetail(const Hdfs::HdfsException& e,
                               std::string& buffer) {
    try {
        std::stringstream ss;
        ss.imbue(std::locale::classic());
        GetExceptionDetailInternal(e, ss, true);
        buffer = ss.str();
    } catch (const std::bad_alloc& e) {
        return "Out of memory";
    }

    return buffer.c_str();
}

const char* GetExceptionDetail(const exception_ptr e, std::string& buffer) {
    std::stringstream ss;
    ss.imbue(std::locale::classic());

    try {
        Hdfs::rethrow_exception(e);
    } catch (const Hdfs::HdfsException& nested) {
        GetExceptionDetailInternal(nested, ss, true);
    } catch (const std::exception& nested) {
        GetExceptionDetailInternal(nested, ss, true);
    }

    try {
        buffer = ss.str();
    } catch (const std::bad_alloc& e) {
        return "Out of memory";
    }

    return buffer.c_str();
}

static void GetExceptionMessage(const std::exception & e,
                                std::stringstream & ss, int recursive) {
    try {
        for (int i = 0; i < recursive; ++i) {
            ss << '\t';
        }

        if (recursive > 0) {
            ss << "Caused by: ";
        }

        ss << e.what();
    } catch (const std::bad_alloc & e) {
        return;
    }

    try {
        Hdfs::rethrow_if_nested(e);
    } catch (const std::exception & nested) {
        GetExceptionMessage(nested, ss, recursive + 1);
    }
}

const char * GetExceptionMessage(const exception_ptr e, std::string & buffer) {
    std::stringstream ss;
    ss.imbue(std::locale::classic());

    try {
        Hdfs::rethrow_exception(e);
    } catch (const std::bad_alloc & e) {
        return "Out of memory";
    } catch (const std::exception & e) {
        GetExceptionMessage(e, ss, 0);
    }

    try {
        buffer = ss.str();
    } catch (const std::bad_alloc & e) {
        return "Out of memory";
    }

    return buffer.c_str();
}

}
}

