/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed 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 "Formatter.h"

#include <assert.h>

#include <android-base/logging.h>
#include <android-base/strings.h>
#include <string>
#include <vector>

namespace android {

Formatter::Formatter() : mFile(nullptr /* invalid */), mIndentDepth(0), mCurrentPosition(0) {}

Formatter::Formatter(FILE* file, size_t spacesPerIndent)
    : mFile(file == nullptr ? stdout : file),
      mIndentDepth(0),
      mSpacesPerIndent(spacesPerIndent),
      mCurrentPosition(0) {}

Formatter::~Formatter() {
    if (mFile != stdout && mFile != stdin && mFile != stderr) {
        fclose(mFile);
    }
    mFile = nullptr;
}

void Formatter::indent(size_t level) {
    mIndentDepth += level;
}

void Formatter::unindent(size_t level) {
    assert(mIndentDepth >= level);
    mIndentDepth -= level;
}

Formatter& Formatter::indent(size_t level, const std::function<void(void)>& func) {
    this->indent(level);
    func();
    this->unindent(level);
    return *this;
}

Formatter& Formatter::indent(const std::function<void(void)>& func) {
    return this->indent(1, func);
}

Formatter& Formatter::block(const std::function<void(void)>& func) {
    (*this) << "{\n";
    this->indent(func);
    return (*this) << "}";
}

void Formatter::pushLinePrefix(const std::string& prefix) {
    mLinePrefix.push_back(prefix);
}

void Formatter::popLinePrefix() {
    mLinePrefix.pop_back();
}

Formatter &Formatter::endl() {
    return (*this) << "\n";
}

Formatter& Formatter::sIf(const std::string& cond, const std::function<void(void)>& block) {
    (*this) << "if (" << cond << ") ";
    return this->block(block);
}

Formatter& Formatter::sElseIf(const std::string& cond, const std::function<void(void)>& block) {
    (*this) << " else if (" << cond << ") ";
    return this->block(block);
}

Formatter& Formatter::sElse(const std::function<void(void)>& block) {
    (*this) << " else ";
    return this->block(block);
}

Formatter& Formatter::sFor(const std::string& stmts, const std::function<void(void)>& block) {
    (*this) << "for (" << stmts << ") ";
    return this->block(block);
}

Formatter& Formatter::sTry(const std::function<void(void)>& block) {
    (*this) << "try ";
    return this->block(block);
}

Formatter& Formatter::sCatch(const std::string& exception, const std::function<void(void)>& block) {
    (*this) << " catch (" << exception << ") ";
    return this->block(block);
}

Formatter& Formatter::sFinally(const std::function<void(void)>& block) {
    (*this) << " finally ";
    return this->block(block);
}

Formatter& Formatter::sWhile(const std::string& cond, const std::function<void(void)>& block) {
    (*this) << "while (" << cond << ") ";
    return this->block(block);
}

Formatter& Formatter::operator<<(const std::string& out) {
    const size_t len = out.length();
    size_t start = 0;

    const std::string& prefix = base::Join(mLinePrefix, "");
    while (start < len) {
        size_t pos = out.find('\n', start);

        if (pos == std::string::npos) {
            if (mCurrentPosition == 0) {
                fprintf(mFile, "%*s", (int)(getIndentation()), "");
                fprintf(mFile, "%s", prefix.c_str());
                mCurrentPosition = getIndentation() + prefix.size();
            }

            std::string sub = out.substr(start);
            output(sub);
            mCurrentPosition += sub.size();
            break;
        }

        if (mCurrentPosition == 0 && (pos > start || !prefix.empty())) {
            fprintf(mFile, "%*s", (int)(getIndentation()), "");
            fprintf(mFile, "%s", prefix.c_str());
            mCurrentPosition = getIndentation() + prefix.size();
        }

        if (pos == start) {
            fprintf(mFile, "\n");
            mCurrentPosition = 0;
        } else if (pos > start) {
            output(out.substr(start, pos - start + 1));
            mCurrentPosition = 0;
        }

        start = pos + 1;
    }

    return *this;
}

void Formatter::printBlock(const WrappedOutput::Block& block, size_t lineLength) {
    size_t prefixSize = 0;
    for (const std::string& prefix : mLinePrefix) {
        prefixSize += prefix.size();
    }

    size_t lineStart = mCurrentPosition ?: (getIndentation() + prefixSize);
    size_t blockSize = block.computeSize(false);
    if (blockSize + lineStart < lineLength) {
        block.print(*this, false);
        return;
    }

    // Everything will not fit on this line. Try to fit it on the next line.
    blockSize = block.computeSize(true);
    if ((blockSize + getIndentation() + mSpacesPerIndent + prefixSize) < lineLength) {
        *this << "\n";
        indent();

        block.print(*this, true);

        unindent();
        return;
    }

    if (!block.content.empty()) {
        // Doesn't have subblocks. This means that the block itself is too big.
        // Have to print it out.
        *this << "\n";
        indent();

        block.print(*this, true);

        unindent();
        return;
    }

    // Everything will not fit on this line. Go through all the children
    for (const WrappedOutput::Block& subBlock : block.blocks) {
        printBlock(subBlock, lineLength);
    }
}

Formatter& Formatter::operator<<(const WrappedOutput& wrappedOutput) {
    printBlock(wrappedOutput.mRootBlock, wrappedOutput.mLineLength);

    return *this;
}

// NOLINT to suppress missing parentheses warning about __type__.
#define FORMATTER_INPUT_INTEGER(__type__)                       \
    Formatter& Formatter::operator<<(__type__ n) { /* NOLINT */ \
        return (*this) << std::to_string(n);                    \
    }

FORMATTER_INPUT_INTEGER(short);
FORMATTER_INPUT_INTEGER(unsigned short);
FORMATTER_INPUT_INTEGER(int);
FORMATTER_INPUT_INTEGER(unsigned int);
FORMATTER_INPUT_INTEGER(long);
FORMATTER_INPUT_INTEGER(unsigned long);
FORMATTER_INPUT_INTEGER(long long);
FORMATTER_INPUT_INTEGER(unsigned long long);
FORMATTER_INPUT_INTEGER(float);
FORMATTER_INPUT_INTEGER(double);
FORMATTER_INPUT_INTEGER(long double);

#undef FORMATTER_INPUT_INTEGER

// NOLINT to suppress missing parentheses warning about __type__.
#define FORMATTER_INPUT_CHAR(__type__)                          \
    Formatter& Formatter::operator<<(__type__ c) { /* NOLINT */ \
        return (*this) << std::string(1, (char)c);              \
    }

FORMATTER_INPUT_CHAR(char);
FORMATTER_INPUT_CHAR(signed char);
FORMATTER_INPUT_CHAR(unsigned char);

#undef FORMATTER_INPUT_CHAR

bool Formatter::isValid() const {
    return mFile != nullptr;
}

size_t Formatter::getIndentation() const {
    return mSpacesPerIndent * mIndentDepth;
}

void Formatter::output(const std::string &text) const {
    CHECK(isValid());

    fprintf(mFile, "%s", text.c_str());
}

WrappedOutput::Block::Block(const std::string& content, Block* const parent)
    : content(content), parent(parent) {}

size_t WrappedOutput::Block::computeSize(bool wrapped) const {
    CHECK(content.empty() || blocks.empty());

    // There is a wrap, so the block would not be printed
    if (printUnlessWrapped && wrapped) return 0;

    size_t size = content.size();
    for (auto block = blocks.begin(); block != blocks.end(); ++block) {
        if (block == blocks.begin()) {
            // Only the first one can be wrapped (since content.empty())
            size += block->computeSize(wrapped);
        } else {
            size += block->computeSize(false);
        }
    }

    return size;
}

void WrappedOutput::Block::print(Formatter& out, bool wrapped) const {
    CHECK(content.empty() || blocks.empty());

    // There is a wrap, so the block should not be printed
    if (printUnlessWrapped && wrapped) return;

    out << content;
    for (auto block = blocks.begin(); block != blocks.end(); ++block) {
        if (block == blocks.begin()) {
            // Only the first one can be wrapped (since content.empty())
            block->print(out, wrapped);
        } else {
            block->print(out, false);
        }
    }
}

WrappedOutput::WrappedOutput(size_t lineLength)
    : mLineLength(lineLength), mRootBlock(Block("", nullptr)) {
    mCurrentBlock = &mRootBlock;
}

WrappedOutput& WrappedOutput::operator<<(const std::string& str) {
    std::vector<Block>& blockVec = mCurrentBlock->blocks;
    if (!blockVec.empty()) {
        Block& last = blockVec.back();
        if (!last.populated && last.blocks.empty()) {
            last.content += str;

            return *this;
        }
    }

    blockVec.emplace_back(str, mCurrentBlock);
    return *this;
}

WrappedOutput& WrappedOutput::printUnlessWrapped(const std::string& str) {
    std::vector<Block>& blockVec = mCurrentBlock->blocks;
    if (!blockVec.empty()) {
        blockVec.back().populated = true;
    }

    blockVec.emplace_back(str, mCurrentBlock);
    blockVec.back().populated = true;
    blockVec.back().printUnlessWrapped = true;

    return *this;
}

void WrappedOutput::group(const std::function<void(void)>& block) {
    std::vector<Block>& blockVec = mCurrentBlock->blocks;
    if (!blockVec.empty()) {
        blockVec.back().populated = true;
    }

    blockVec.emplace_back("", mCurrentBlock);
    mCurrentBlock = &blockVec.back();

    block();

    mCurrentBlock->populated = true;
    mCurrentBlock = mCurrentBlock->parent;
}

}  // namespace android
