//===-- sketch.cpp ----------------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include <CoreFoundation/CoreFoundation.h>

#include "lldb-perf/lib/Timer.h"
#include "lldb-perf/lib/Metric.h"
#include "lldb-perf/lib/Measurement.h"
#include "lldb-perf/lib/TestCase.h"
#include "lldb-perf/lib/Xcode.h"

#include <iostream>
#include <unistd.h>
#include <fstream>
#include <getopt.h>

using namespace lldb_perf;

static struct option g_long_options[] = {
    { "verbose",    no_argument,            NULL, 'v' },
    { "sketch",     required_argument,      NULL, 'c' },
    { "foobar",     required_argument,      NULL, 'f' },
    { "out-file",   required_argument,      NULL, 'o' },
    { NULL,         0,                      NULL,  0  }
};

class SketchTest : public TestCase
{
public:
    SketchTest () :
        m_fetch_frames_measurement ([this] () -> void
            {
                Xcode::FetchFrames (GetProcess(),false,false);
            }, "fetch-frames", "time to dump backtrace for every frame in every thread"),
        m_file_line_bp_measurement([this] (const char* file, uint32_t line) -> void
            {
                Xcode::CreateFileLineBreakpoint(GetTarget(), file, line);
            }, "file-line-bkpt", "time to set a breakpoint given a file and line"),
        m_fetch_modules_measurement ([this] () -> void
            {
                Xcode::FetchModules(GetTarget());
            }, "fetch-modules", "time to get info for all modules in the process"),
        m_fetch_vars_measurement([this] (int depth) -> void
            {
                SBProcess process (GetProcess());
                auto threads_count = process.GetNumThreads();
                for (size_t thread_num = 0; thread_num < threads_count; thread_num++)
                {
                    SBThread thread(process.GetThreadAtIndex(thread_num));
                    SBFrame frame(thread.GetFrameAtIndex(0));
                    Xcode::FetchVariables(frame,depth,GetVerbose());
                }
            }, "fetch-vars", "time to dump variables for the topmost frame in every thread"),
        m_run_expr_measurement([this] (SBFrame frame, const char* expr) -> void
            {
                SBValue value(frame.EvaluateExpression(expr, lldb::eDynamicCanRunTarget));
                Xcode::FetchVariable (value, 0, GetVerbose());
            }, "run-expr", "time to evaluate an expression and display the result")
    {
        m_app_path.clear();
        m_out_path.clear();
        m_doc_path.clear();
        m_print_help = false;
    }
    
    virtual
    ~SketchTest ()
    {
    }
    
    virtual bool
    ParseOption (int short_option, const char* optarg)
    {
        switch (short_option)
        {
            case 0:
                return false;
                
            case -1:
                return false;
                
            case '?':
            case 'h':
                m_print_help = true;
                break;
                
            case 'v':
                SetVerbose(true);
                break;
                
            case 'c':
            {
                SBFileSpec file(optarg);
                if (file.Exists())
                    SetExecutablePath(optarg);
                else
                    fprintf(stderr, "error: file specified in --sketch (-c) option doesn't exist: '%s'\n", optarg);
            }
                break;
                
            case 'f':
            {
                SBFileSpec file(optarg);
                if (file.Exists())
                    SetDocumentPath(optarg);
                else
                    fprintf(stderr, "error: file specified in --foobar (-f) option doesn't exist: '%s'\n", optarg);
            }
                break;
                
            case 'o':
                SetResultFilePath(optarg);
                break;
                
            default:
                m_print_help = true;
                fprintf (stderr, "error: unrecognized option %c\n", short_option);
                break;
        }
        return true;
    }
    
    virtual struct option*
    GetLongOptions ()
    {
        return g_long_options;
    }
    
    virtual bool
	Setup (int& argc, const char**& argv)
    {
        TestCase::Setup(argc,argv);
        bool error = false;
        
        if (GetExecutablePath() == NULL)
        {
            // --sketch is mandatory
            error = true;
            fprintf (stderr, "error: the '--sketch=PATH' option is mandatory\n");
        }
        
        if (GetDocumentPath() == NULL)
        {
            // --foobar is mandatory
            error = true;
            fprintf (stderr, "error: the '--foobar=PATH' option is mandatory\n");
        }
        
        if (error || GetPrintHelp())
        {
            puts(R"(
                 NAME
                 lldb_perf_sketch -- a tool that measures LLDB peformance while debugging sketch.
                 
                 SYNOPSIS
                 lldb_perf_sketch --sketch=PATH --foobar=PATH [--out-file=PATH --verbose]
                 
                 DESCRIPTION
                 Runs a set of static timing and memory tasks against sketch and outputs results
                 to a plist file.
                 )");
        }
        
        if (error)
        {
            exit(1);
        }
        lldb::SBLaunchInfo launch_info = GetLaunchInfo();
        m_target = m_debugger.CreateTarget(m_app_path.c_str());
        m_file_line_bp_measurement("SKTDocument.m",245);
        m_file_line_bp_measurement("SKTDocument.m",283);
        m_file_line_bp_measurement("SKTText.m",326);
        return Launch (launch_info);
    }
    
    lldb::SBLaunchInfo
    GetLaunchInfo ()
    {
        const char* file_arg = m_doc_path.c_str();
        const char* persist_arg = "-ApplePersistenceIgnoreState";
        const char* persist_skip = "YES";
        const char* empty = nullptr;
        const char* args[] = {file_arg,persist_arg,persist_skip,empty};
        return SBLaunchInfo(args);
    }
    
    void
    DoTest ()
    {
        m_fetch_frames_measurement();
        m_fetch_modules_measurement();
        m_fetch_vars_measurement(1);
    }
    
	virtual void
	TestStep (int counter, ActionWanted &next_action)
    {
        static int launch = 1;
        switch (counter % 10)
        {
        case 0:
            {
                DoTest ();
                if (counter == 0)
                    m_file_line_bp_measurement("SKTDocument.m",254);
                next_action.Continue();
            }
            break;
                
        case 1:
            {
                DoTest ();
                m_run_expr_measurement(m_thread.GetFrameAtIndex(0),"properties");
                m_run_expr_measurement(m_thread.GetFrameAtIndex(0),"[properties description]");
                m_run_expr_measurement(m_thread.GetFrameAtIndex(0),"typeName");
                m_run_expr_measurement(m_thread.GetFrameAtIndex(0),"data");
                m_run_expr_measurement(m_thread.GetFrameAtIndex(0),"[data description]");
                next_action.Continue();
            }
            break;

        case 2:
            {
                DoTest ();
                next_action.Continue();
            }
            break;

        case 3:
            {
                DoTest ();
                next_action.StepOver(m_thread);
            }
            break;

        case 4:
            {
                DoTest ();
                m_run_expr_measurement(m_thread.GetFrameAtIndex(0),"layoutManager");
                m_run_expr_measurement(m_thread.GetFrameAtIndex(0),"contents");
                next_action.StepOver(m_thread);
            }
            break;
        
        case 5:
            {
                DoTest ();
                next_action.StepOver(m_thread);
            }
            break;

        case 6:
            {
                DoTest ();
                next_action.StepOver(m_thread);
            }
            break;

        case 7:
            {
                DoTest ();
                m_run_expr_measurement(m_thread.GetFrameAtIndex(0),"@\"an NSString\"");
                m_run_expr_measurement(m_thread.GetFrameAtIndex(0),"[(id)@\"an NSString\" description]");
                m_run_expr_measurement(m_thread.GetFrameAtIndex(0),"@[@1,@2,@3]");
                next_action.StepOut(m_thread);
            }
            break;

        case 8:
            {
                DoTest ();
                m_run_expr_measurement(m_thread.GetFrameAtIndex(0),"[graphics description]");
                m_run_expr_measurement(m_thread.GetFrameAtIndex(0),"[selectionIndexes description]");
                m_run_expr_measurement(m_thread.GetFrameAtIndex(0),"(BOOL)NSIntersectsRect(rect, graphicDrawingBounds)");
            }
            next_action.CallNext();
            break;
        case 9:
            if (++launch < 10)
                next_action.Relaunch(GetLaunchInfo());
            else
                next_action.Kill();
            break;
        
                
        default:
            {
                next_action.Kill();
            }
            break;
        }
    }
    
    virtual void
    WriteResults (Results &results)
    {
        m_fetch_frames_measurement.WriteAverageAndStandardDeviation(results);
        m_file_line_bp_measurement.WriteAverageAndStandardDeviation(results);
        m_fetch_modules_measurement.WriteAverageAndStandardDeviation(results);
        m_fetch_vars_measurement.WriteAverageAndStandardDeviation(results);
        m_run_expr_measurement.WriteAverageAndStandardDeviation(results);
        results.Write(GetResultFilePath());
    }
    
    void
    SetExecutablePath (const char* str)
    {
        if (str)
            m_app_path.assign(str);
    }
    
    const char*
    GetExecutablePath ()
    {
        if (m_app_path.empty())
            return NULL;
        return m_app_path.c_str();
    }
    
    void
    SetDocumentPath (const char* str)
    {
        if (str)
            m_doc_path.assign(str);
    }
    
    const char*
    GetDocumentPath ()
    {
        if (m_doc_path.empty())
            return NULL;
        return m_doc_path.c_str();
    }

    
    void
    SetResultFilePath (const char* str)
    {
        if (str)
            m_out_path.assign(str);
    }
    
    const char*
    GetResultFilePath ()
    {
        if (m_out_path.empty())
            return "/dev/stdout";
        return m_out_path.c_str();
    }
    
    bool
    GetPrintHelp ()
    {
        return m_print_help;
    }
    
private:
    Measurement<lldb_perf::TimeGauge, std::function<void()>> m_fetch_frames_measurement;
    Measurement<lldb_perf::TimeGauge, std::function<void(const char*, uint32_t)>> m_file_line_bp_measurement;
    Measurement<lldb_perf::TimeGauge, std::function<void()>> m_fetch_modules_measurement;
    Measurement<lldb_perf::TimeGauge, std::function<void(int)>> m_fetch_vars_measurement;
    Measurement<lldb_perf::TimeGauge, std::function<void(SBFrame, const char*)>> m_run_expr_measurement;
    
    std::string m_app_path;
    std::string m_doc_path;
    std::string m_out_path;
    bool m_print_help;
};

int main(int argc, const char * argv[])
{
    SketchTest test;
    return TestCase::Run(test, argc, argv);
}