/*
 * Copyright (c) 2007, Michael Feathers, James Grenning and Bas Vodde
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the <organization> nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE EARLIER MENTIONED AUTHORS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "CppUTest/TestHarness.h"
#include "CppUTestExt/CodeMemoryReportFormatter.h"
#include "CppUTestExt/MemoryReportAllocator.h"
#include "CppUTest/PlatformSpecificFunctions.h"


#define MAX_VARIABLE_NAME_LINE_PART 10
#define MAX_VARIABLE_NAME_FILE_PART 53
#define MAX_VARIABLE_NAME_SEPERATOR_PART 1
#define MAX_VARIABLE_NAME_LENGTH MAX_VARIABLE_NAME_FILE_PART + MAX_VARIABLE_NAME_SEPERATOR_PART + MAX_VARIABLE_NAME_LINE_PART

struct CodeReportingAllocationNode
{
    char variableName_[MAX_VARIABLE_NAME_LENGTH + 1];
    void* memory_;
    CodeReportingAllocationNode* next_;
};

CodeMemoryReportFormatter::CodeMemoryReportFormatter(TestMemoryAllocator* internalAllocator)
    : codeReportingList_(NULLPTR), internalAllocator_(internalAllocator)
{
}

CodeMemoryReportFormatter::~CodeMemoryReportFormatter()
{
    clearReporting();
}

void CodeMemoryReportFormatter::clearReporting()
{
    while (codeReportingList_) {
        CodeReportingAllocationNode* oldNode = codeReportingList_;
        codeReportingList_ = codeReportingList_->next_;
        internalAllocator_->free_memory((char*) oldNode, 0, __FILE__, __LINE__);
    }
}

void CodeMemoryReportFormatter::addNodeToList(const char* variableName, void* memory, CodeReportingAllocationNode* next)
{
    CodeReportingAllocationNode* newNode = (CodeReportingAllocationNode*) (void*) internalAllocator_->alloc_memory(sizeof(CodeReportingAllocationNode), __FILE__, __LINE__);
    newNode->memory_ = memory;
    newNode->next_ = next;
    SimpleString::StrNCpy(newNode->variableName_, variableName, MAX_VARIABLE_NAME_LENGTH);
    codeReportingList_ = newNode;
}

CodeReportingAllocationNode* CodeMemoryReportFormatter::findNode(void* memory)
{

    CodeReportingAllocationNode* current = codeReportingList_;
    while (current && current->memory_ != memory) {
        current = current->next_;
    }
    return current;
}

static SimpleString extractFileNameFromPath(const char* file)
{
    const char* fileNameOnly = file + SimpleString::StrLen(file);
    while (fileNameOnly != file && *fileNameOnly != '/')
        fileNameOnly--;
    if (*fileNameOnly == '/') fileNameOnly++;
    return fileNameOnly;
}

SimpleString CodeMemoryReportFormatter::createVariableNameFromFileLineInfo(const char *file, size_t line)
{
    SimpleString fileNameOnly = extractFileNameFromPath(file);
    fileNameOnly.replace(".", "_");

    for (int i = 1; i < 100; i++) {
        SimpleString variableName = StringFromFormat("%s_%d_%d", fileNameOnly.asCharString(), (int) line, i);
        if (!variableExists(variableName))
            return variableName;
    }
    return "";
}

bool CodeMemoryReportFormatter::isNewAllocator(TestMemoryAllocator* allocator)
{
    return SimpleString::StrCmp(allocator->alloc_name(), defaultNewAllocator()->alloc_name()) == 0 || SimpleString::StrCmp(allocator->alloc_name(), defaultNewArrayAllocator()->alloc_name()) == 0;
}

bool CodeMemoryReportFormatter::variableExists(const SimpleString& variableName)
{
    CodeReportingAllocationNode* current = codeReportingList_;
    while (current) {
        if (variableName == current->variableName_)
            return true;
        current = current->next_;
    }
    return false;
}

SimpleString CodeMemoryReportFormatter::getAllocationString(TestMemoryAllocator* allocator, const SimpleString& variableName, size_t size)
{
    if (isNewAllocator(allocator))
        return StringFromFormat("char* %s = new char[%lu]; /* using %s */", variableName.asCharString(), (unsigned long) size, allocator->alloc_name());
    else
        return StringFromFormat("void* %s = malloc(%lu);", variableName.asCharString(), (unsigned long) size);
}

SimpleString CodeMemoryReportFormatter::getDeallocationString(TestMemoryAllocator* allocator, const SimpleString& variableName, const char* file, size_t line)
{
    if (isNewAllocator(allocator))
        return StringFromFormat("delete [] %s; /* using %s at %s:%d */", variableName.asCharString(), allocator->free_name(), file, (int) line);
    else
        return StringFromFormat("free(%s); /* at %s:%d */", variableName.asCharString(), file, (int) line);
}

void CodeMemoryReportFormatter::report_test_start(TestResult* result, UtestShell& test)
{
    clearReporting();
    result->print(StringFromFormat("*/\nTEST(%s_memoryReport, %s)\n{ /* at %s:%d */\n",
            test.getGroup().asCharString(), test.getName().asCharString(), test.getFile().asCharString(), (int) test.getLineNumber()).asCharString());
}

void CodeMemoryReportFormatter::report_test_end(TestResult* result, UtestShell&)
{
    result->print("}/*");
}

void CodeMemoryReportFormatter::report_testgroup_start(TestResult* result, UtestShell& test)
{
    result->print(StringFromFormat("*/TEST_GROUP(%s_memoryReport)\n{\n};\n/*",
            test.getGroup().asCharString()).asCharString());
}

void CodeMemoryReportFormatter::report_alloc_memory(TestResult* result, TestMemoryAllocator* allocator, size_t size, char* memory, const char* file, size_t line)
{
    SimpleString variableName = createVariableNameFromFileLineInfo(file, line);
    result->print(StringFromFormat("\t%s\n", getAllocationString(allocator, variableName, size).asCharString()).asCharString());
    addNodeToList(variableName.asCharString(), memory, codeReportingList_);
}

void CodeMemoryReportFormatter::report_free_memory(TestResult* result, TestMemoryAllocator* allocator, char* memory, const char* file, size_t line)
{
    SimpleString variableName;
    CodeReportingAllocationNode* node = findNode(memory);

    if (memory == NULLPTR) variableName = "NULL";
    else variableName = node->variableName_;

    result->print(StringFromFormat("\t%s\n", getDeallocationString(allocator, variableName, file, line).asCharString()).asCharString());
}
