// -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 4 -*-

/*
 * (c) 2016 Petr Ročkai <code@fixp.eu>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#pragma once

#include <divine/vm/pointer.hpp>
#include <divine/vm/program.hpp>
#include <divine/rt/runtime.hpp>

DIVINE_RELAX_WARNINGS
#include <llvm/IR/Instructions.h>
#include <llvm/IR/CallSite.h>
DIVINE_UNRELAX_WARNINGS

#include <cxxabi.h>
#include <brick-fs>

namespace divine {
namespace vm {

namespace print {

std::string opcode( int );

static void pad( std::ostream &o, int &col, int target )
{
    while ( col < target )
        ++col, o << " ";
}

template< typename B >
void hexbyte( std::ostream &o, int &col, int index, B byte )
{
    o << std::setw( 2 ) << std::setfill( '0' ) << std::setbase( 16 ) << +byte;
    col += 2;
    if ( index % 2 == 1 )
        ++col, o << " ";
}

template< typename B >
void ascbyte( std::ostream &o, int &col, B byte )
{
    std::stringstream os;
    os << byte;
    char b = os.str()[ 0 ];
    o << ( std::isprint( b ) ? b : '~' );
    ++ col;
}

enum class DisplayVal { Name, Value, PreferName };

template< typename Eval >
static std::string value( Eval &eval, llvm::Value *val, DisplayVal disp = DisplayVal::PreferName )
{
    std::stringstream num2str;
    num2str << std::setw( 2 ) << std::setfill( '0' ) << std::hex;

    std::string name;

    if ( disp != DisplayVal::Value )
    {
        if ( auto I = llvm::dyn_cast< llvm::Instruction >( val ) )
        {
            num2str << eval.program().pcmap[ I ].instruction();
            name = "%" + num2str.str();
        }
        else if ( auto B = llvm::dyn_cast< llvm::BasicBlock >( val ) )
        {
            num2str << eval.program().blockmap[ B ].instruction();
            name = B->getName().str();
            name = "label %" + ( name.empty() || name.size() > 20 ? num2str.str() : name );
        }
    }

    if ( name.empty() && disp != DisplayVal::Name )
    {
        auto slot = eval.program().valuemap[ val ].slot;
        if ( slot.type == Eval::Slot::Aggregate )
            name = "<aggregate>";
        else
            eval.template type_dispatch<>(
                slot.width(), slot.type,
                [&]( auto v ) { name = brick::string::fmt( v.get( slot ) ); } );
    }

    return name;
}

template< typename Eval >
static void result( std::ostream &out, int col, Eval &eval )
{
    while ( col < 60 )
        col ++, out << " ";
    out << "# " << value( eval, eval.instruction().op, DisplayVal::Value );
}

template< typename I >
decltype( I::opcode, std::string() ) opcode( I &insn )
{
    std::string op = opcode( insn.opcode );
    if ( insn.opcode == llvm::Instruction::ICmp )
        switch ( llvm::cast< llvm::ICmpInst >( insn.op )->getPredicate() )
        {
            case llvm::ICmpInst::ICMP_EQ: op += ".eq"; break;
            case llvm::ICmpInst::ICMP_NE: op += ".ne"; break;
            case llvm::ICmpInst::ICMP_ULT: op += ".ult"; break;
            case llvm::ICmpInst::ICMP_UGE: op += ".uge"; break;
            case llvm::ICmpInst::ICMP_UGT: op += ".ugt"; break;
            case llvm::ICmpInst::ICMP_ULE: op += ".ule"; break;
            case llvm::ICmpInst::ICMP_SLT: op += ".slt"; break;
            case llvm::ICmpInst::ICMP_SGT: op += ".sgt"; break;
            case llvm::ICmpInst::ICMP_SLE: op += ".sle"; break;
            case llvm::ICmpInst::ICMP_SGE: op += ".sge"; break;
            default: UNREACHABLE( "unexpected icmp predicate" ); break;
        }
    if ( insn.opcode == llvm::Instruction::FCmp )
        switch ( llvm::cast< llvm::FCmpInst >( insn.op )->getPredicate() )
        {
            case llvm::FCmpInst::FCMP_OEQ: op += ".oeq"; break;
            case llvm::FCmpInst::FCMP_ONE: op += ".one"; break;
            case llvm::FCmpInst::FCMP_OLE: op += ".ole"; break;
            case llvm::FCmpInst::FCMP_OLT: op += ".olt"; break;
            case llvm::FCmpInst::FCMP_OGE: op += ".oge"; break;
            case llvm::FCmpInst::FCMP_OGT: op += ".ogt"; break;

            case llvm::FCmpInst::FCMP_UEQ: op += ".ueq"; break;
            case llvm::FCmpInst::FCMP_UNE: op += ".une"; break;
            case llvm::FCmpInst::FCMP_ULE: op += ".ule"; break;
            case llvm::FCmpInst::FCMP_ULT: op += ".ult"; break;
            case llvm::FCmpInst::FCMP_UGE: op += ".uge"; break;
            case llvm::FCmpInst::FCMP_UGT: op += ".ugt"; break;

            case llvm::FCmpInst::FCMP_FALSE: op += ".false"; break;
            case llvm::FCmpInst::FCMP_TRUE: op += ".true"; break;
            case llvm::FCmpInst::FCMP_ORD: op += ".ord"; break;
            case llvm::FCmpInst::FCMP_UNO: op += ".uno"; break;
            default: UNREACHABLE( "unexpected fcmp predicate" ); break;
        }
    return op;
}

template< typename Eval >
static std::string instruction( Eval &eval, int padding = 0 )
{
    std::stringstream out;
    auto &insn = eval.instruction();
    ASSERT( insn.op );

    bool printres = true;

    if ( insn.result().type != Program::Slot::Void )
        out << value( eval, insn.op, DisplayVal::Name ) << " = ";
    else
        printres = false;

    out << opcode( insn ) << " ";
    const int numOperands = insn.op->getNumOperands();
    uint64_t skipMask = 0;
    int argalign = out.str().size() + padding, argcols = 0;

    if ( insn.opcode == llvm::Instruction::Call || insn.opcode == llvm::Instruction::Invoke )
    {
        llvm::CallSite cs( insn.op );

        skipMask |= uint64_t( 1 ) << (numOperands - (cs.isCall() ? 1 : 3));
        if ( auto *target = cs.getCalledFunction() ) {
            auto tgt = target->getName().str();
            argcols = tgt.size() + 2;
            out << "@" << tgt << " ";
        } else {
            auto *val = cs.getCalledValue();
            auto oname = value( eval, val, DisplayVal::PreferName );
            argcols = oname.size() + 1;
            out << ( oname.empty() ? "?" : oname ) << " ";
        }
    }

    for ( int i = 0; i < numOperands; ++i )
    {
        if ( ( (uint64_t( 1 ) << i) & skipMask ) != 0 )
            continue;

        auto val = insn.op->getOperand( i );
        auto oname = value( eval, val, DisplayVal::PreferName );

        int cols = argalign + argcols + oname.size() + 1;
        if ( ( printres && cols >= 60 ) || cols >= 80 )
        {
            if ( printres )
                printres = false, result( out, argalign + argcols, eval );
            out << std::endl;
            for ( int i = 0; i < argalign; ++i ) out << " ";
            argcols = 0;
        }
        argcols += oname.size() + 1;

        out << ( oname.empty() ? "?" : oname ) << " ";
    }

    if ( printres )
        result( out, argalign + argcols, eval );

    return out.str();
}

template< typename Heap >
std::string raw( Heap &heap, HeapPointer hloc, int sz )
{
    std::stringstream out;

    auto bytes = heap.unsafe_bytes( hloc, hloc.offset(), sz );
    auto types = heap.type( hloc, hloc.offset(), sz );
    auto defined = heap.defined( hloc, hloc.offset(), sz );

    for ( int c = 0; c < ( sz / 12 ) + ( sz % 12 ? 1 : 0 ); ++c )
    {
        int col = 0;
        for ( int i = c * 12; i < std::min( (c + 1) * 12, sz ); ++i )
            print::hexbyte( out, col, i, bytes[ i ] );
        print::pad( out, col, 30 ); out << "| ";
        for ( int i = c * 12; i < std::min( (c + 1) * 12, sz ); ++i )
            print::hexbyte( out, col, i, defined[ i ] );
        print::pad( out, col, 60 ); out << "| ";
        for ( int i = c * 12; i < std::min( (c + 1) * 12, sz ); ++i )
            print::ascbyte( out, col, bytes[ i ] );
        print::pad( out, col, 72 ); out << " | ";
        for ( int i = c * 12; i < std::min( (c + 1) * 12, sz ); ++i )
            print::ascbyte( out, col, types[ i ] );
        print::pad( out, col, 84 );
        if ( c + 1 < ( sz / 12 ) + ( sz % 12 ? 1 : 0 ) )
            out << std::endl;
    }

    return out.str();
}

static std::string demangle( std::string mangled )
{
    int stat;
    auto x = abi::__cxa_demangle( mangled.c_str(), nullptr, nullptr, &stat );
    auto ret = stat == 0 && x ? std::string( x ) : mangled;
    std::free( x );
    return ret;
}

template< typename Program >
static std::string source( llvm::DISubprogram *di, Program &program, CodePointer pc )
{
    std::stringstream out;

    brick::string::Splitter split( "\n", std::regex::extended );
    auto src = rt::source( di->getFilename() );
    if ( src.empty() )
        src = brick::fs::readFile( di->getFilename() );

    auto line = split.begin( src );
    unsigned lineno = 1, active = 0;
    while ( lineno < di->getLine() )
        ++ line, ++ lineno;
    unsigned endline = lineno;

    auto act_op = program.instruction( pc ).insn();
    if ( !act_op )
        act_op = program.instruction( pc + 1 ).insn();

    /* figure out the source code span the function covers; painfully */
    for ( auto &i : program.function( pc ).instructions )
    {
        if ( !i.insn() )
            continue;
        auto dl = i.insn()->getDebugLoc().get();
        if ( !dl )
            continue;
        while ( dl->getInlinedAt() )
            dl = dl->getInlinedAt();
        if ( i.insn() == act_op )
            active = dl->getLine();
        endline = std::max( endline, dl->getLine() );
    }

    while ( act_op && !active )
    {
        auto dl = act_op->getDebugLoc();
        while ( dl && dl->getInlinedAt() )
            dl = dl->getInlinedAt();
        active = dl ? dl->getLine() : active;
        pc = pc + 1;
        act_op = program.instruction( pc ).insn();
    }

    /* print it */
    while ( line != split.end() && lineno <= endline )
    {
        out << (lineno == active ? ">>" : "  ");
            out << std::setw( 5 ) << lineno++ << " " << *line++ << std::endl;
    }
    if ( line != split.end() ) {
        std::regex endbrace( "^[ \t]*}", std::regex::extended );
        if ( std::regex_search( line->str(), endbrace ) )
            out << "  " << std::setw( 5 ) << lineno++ << " " << *line++ << std::endl;
    }

    return out.str();
}

}

}
}
