#include <cassert>
#include "tatumparse.hpp"

namespace tp = tatumparse;

class PrintCallback : public tp::Callback {

    void start_parse() override { fprintf(stderr, "Starting parse\n"); }
    void filename(std::string fname) override { filename_ = fname; }
    void lineno(int line_num) override { line_no_ = line_num; }
    void finish_parse() override { fprintf(stderr, "Finished parse\n"); }

    void start_graph() override { fprintf(stdout, "timing_graph:\n"); }
    void add_node(int node_id, tp::NodeType type, std::vector<int> in_edge_ids, std::vector<int> out_edge_ids) override {
        fprintf(stdout, " node: %d", node_id);
        fprintf(stdout, "\n");

        fprintf(stdout, "  type:");
        switch(type) {
            case tp::NodeType::SOURCE: fprintf(stdout, " SOURCE"); break;
            case tp::NodeType::SINK: fprintf(stdout, " SINK"); break;
            case tp::NodeType::IPIN: fprintf(stdout, " IPIN"); break;
            case tp::NodeType::OPIN: fprintf(stdout, " OPIN"); break;
            default: assert(false);
        }
        fprintf(stdout, "\n");

        fprintf(stdout, "  in_edges: ");
        for(auto edge : in_edge_ids) {
            fprintf(stdout, "%d ", edge);
        }
        fprintf(stdout, "\n");
        fprintf(stdout, "  out_edges: ");
        for(auto edge : out_edge_ids) {
            fprintf(stdout, "%d ", edge);
        }
        fprintf(stdout, "\n");
    }
    void add_edge(int edge_id, tp::EdgeType type, int src_node_id, int sink_node_id, bool disabled) override {
        fprintf(stdout, " edge: %d\n", edge_id);

        fprintf(stdout, "  type:");
        switch(type) {
            case tp::EdgeType::PRIMITIVE_COMBINATIONAL: fprintf(stdout, " PRIMITIVE_COMBINATIONAL"); break;
            case tp::EdgeType::PRIMITIVE_CLOCK_LAUNCH : fprintf(stdout, " PRIMITIVE_CLOCK_LAUNCH"); break;
            case tp::EdgeType::PRIMITIVE_CLOCK_CAPTURE: fprintf(stdout, " PRIMITIVE_CLOCK_CAPTURE"); break;
            case tp::EdgeType::INTERCONNECT           : fprintf(stdout, " INTERCONNECT"); break;
            default: assert(false);
        }
        fprintf(stdout, "\n");

        fprintf(stdout, "  src_node: %d\n", src_node_id);
        fprintf(stdout, "  sink_node: %d\n", sink_node_id);
        if(disabled) {
            fprintf(stdout, "  disabled: true\n");
        }
    }
    void finish_graph() override { fprintf(stdout, "# end timing_graph\n"); }

    void start_constraints() override { fprintf(stdout, "timing_constraints:\n"); }
    void add_clock_domain(int domain_id, std::string name) override {
        fprintf(stdout, " type: CLOCK domain: %d name: \"%s\"\n", domain_id, name.c_str());
    }
    void add_clock_source(int node_id, int domain_id) override {
        fprintf(stdout, " type: CLOCK_SOURCE node: %d domain: %d\n", node_id, domain_id);
    }
    void add_constant_generator(int node_id) override {
        fprintf(stdout, " type: CONSTANT_GENERATOR node: %d\n", node_id);
    }
    void add_max_input_constraint(int node_id, int domain_id, float constraint) override {
        fprintf(stdout, " type: MAX_INPUT_CONSTRAINT node: %d domain: %d constraint: %g\n", node_id, domain_id, constraint);
    }
    void add_min_input_constraint(int node_id, int domain_id, float constraint) override {
        fprintf(stdout, " type: MIN_INPUT_CONSTRAINT node: %d domain: %d constraint: %g\n", node_id, domain_id, constraint);
    }
    void add_max_output_constraint(int node_id, int domain_id,  float constraint) override {
        fprintf(stdout, " type: MAX_OUTPUT_CONSTRAINT node: %d domain: %d constraint: %g\n", node_id, domain_id, constraint);
    }
    void add_min_output_constraint(int node_id, int domain_id,  float constraint) override {
        fprintf(stdout, " type: MIN_OUTPUT_CONSTRAINT node: %d domain: %d constraint: %g\n", node_id, domain_id, constraint);
    }
    void add_setup_constraint(int src_domain_id, int sink_domain_id, int capture_node, float constraint) override {
        fprintf(stdout, " type: SETUP_CONSTRAINT src_domain: %d sink_domain: %d capture_node: %d constraint: %g\n", src_domain_id, sink_domain_id, capture_node, constraint);
    }
    void add_hold_constraint(int src_domain_id, int sink_domain_id, int capture_node, float constraint) override {
        fprintf(stdout, " type: HOLD_CONSTRAINT src_domain: %d sink_domain: %d capture_node: %d constraint: %g\n", src_domain_id, sink_domain_id, capture_node, constraint);
    }
    void add_setup_uncertainty(int src_domain_id, int sink_domain_id, float uncertainty) override {
        fprintf(stdout, " type: SETUP_UNCERTAINTY src_domain: %d sink_domain: %d uncertainty: %g\n", src_domain_id, sink_domain_id, uncertainty);
    }
    void add_hold_uncertainty(int src_domain_id, int sink_domain_id, float uncertainty) override {
        fprintf(stdout, " type: HOLD_UNCERTAINTY src_domain: %d sink_domain: %d uncertainty: %g\n", src_domain_id, sink_domain_id, uncertainty);
    }
    void add_early_source_latency(int domain_id, float latency) override {
        fprintf(stdout, " type: EARLY_SOURCE_LATENCY domain: %d latency: %g\n", domain_id, latency);
    }
    void add_late_source_latency(int domain_id, float latency) override {
        fprintf(stdout, " type: LATE_SOURCE_LATENCY domain: %d latency: %g\n", domain_id, latency);
    }
    void finish_constraints() override { fprintf(stdout, "# end timing_constraints\n"); }

    void start_delay_model() override { fprintf(stdout, "delay_model:\n"); }
    void add_edge_delay(int edge_id, float min_delay, float max_delay) override { 
        fprintf(stdout, " edge_id: %d min_delay: %g max_delay: %g\n", edge_id, min_delay, max_delay); 
    }
    void add_edge_setup_hold_time(int edge_id, float setup_time, float hold_time) override {
        fprintf(stdout, " edge_id: %d setup_time: %g hold_time: %g\n", edge_id, setup_time, hold_time); 
    }
    void finish_delay_model() override { fprintf(stdout, "# end delay_model\n"); }

    void start_results() override { fprintf(stdout, "analysis_result:\n"); }
    void add_node_tag(tp::TagType type, int node_id, int launch_domain_id, int capture_domain_id, float time) override {
        fprintf(stdout, " type: ");
        switch(type) {
            case tp::TagType::SETUP_LAUNCH_CLOCK: fprintf(stdout, "SETUP_LAUNCH_CLOCK"); break;
            case tp::TagType::SETUP_CAPTURE_CLOCK: fprintf(stdout, "SETUP_CAPTURE_CLOCK"); break;
            case tp::TagType::HOLD_LAUNCH_CLOCK: fprintf(stdout, "HOLD_LAUNCH_CLOCK"); break;
            case tp::TagType::HOLD_CAPTURE_CLOCK: fprintf(stdout, "HOLD_CAPTURE_CLOCK"); break;
            default: assert(false);
        }
        fprintf(stdout, " node: %d launch_domain: %d capture_domain: %d time: %g\n", node_id, launch_domain_id, capture_domain_id, time);
    }
    void add_edge_slack(tp::TagType type, int edge_id, int launch_domain_id, int capture_domain_id, float slack) override {
        fprintf(stdout, " type: ");
        switch(type) {
            case tp::TagType::SETUP_SLACK: fprintf(stdout, "SETUP_SLACK"); break;
            case tp::TagType::HOLD_SLACK: fprintf(stdout, "HOLD_SLACK"); break;
            default: assert(false);
        }
        fprintf(stdout, " edge: %d launch_domain: %d capture_domain: %d time: %g\n", edge_id, launch_domain_id, capture_domain_id, slack);
    }
    void add_node_slack(tp::TagType type, int node_id, int launch_domain_id, int capture_domain_id, float slack) override {
        fprintf(stdout, " type: ");
        switch(type) {
            case tp::TagType::SETUP_SLACK: fprintf(stdout, "SETUP_SLACK"); break;
            case tp::TagType::HOLD_SLACK: fprintf(stdout, "HOLD_SLACK"); break;
            default: assert(false);
        }
        fprintf(stdout, " node: %d launch_domain: %d capture_domain: %d time: %g\n", node_id, launch_domain_id, capture_domain_id, slack);
    }
    void finish_results() override { fprintf(stdout, "# end analysis_results\n"); }

    void parse_error(const int curr_lineno, const std::string& near_text, const std::string& msg) override {
        fprintf(stderr, "%s:%d: Parse error: %s", filename_.c_str(), curr_lineno, msg.c_str());
        if(!near_text.empty()) {
            fprintf(stderr, " near '%s'", near_text.c_str());
            fprintf(stderr, "\n");
        } else {
            if(msg.find('\n') == std::string::npos) {
                fprintf(stderr, "\n");
            }
        }
    }

    private:
        std::string filename_;
        int line_no_;
};

class NopCallback : public tp::Callback {
    public:
        //Start of parsing
        void start_parse() override {}

        //Sets current filename
        void filename(std::string fname) override { filename_ = fname; }

        //Sets current line number
        void lineno(int /*line_num*/) override {}

        void start_graph() override {}
        void add_node(int /*node_id*/, tp::NodeType /*type*/, std::vector<int> /*in_edge_ids*/, std::vector<int> /*out_edge_ids*/) override {}
        void add_edge(int /*edge_id*/, tp::EdgeType /*type*/, int /*src_node_id*/, int /*sink_node_id*/, bool /*disabled*/) override {}
        void finish_graph() override {}

        void start_constraints() override {}
        void add_clock_domain(int /*domain_id*/, std::string /*name*/) override {}
        void add_clock_source(int /*node_id*/, int /*domain_id*/) override {}
        void add_constant_generator(int /*node_id*/) override {}
        void add_max_input_constraint(int /*node_id*/, int /*domain_id*/, float /*constraint*/) override {}
        void add_min_input_constraint(int /*node_id*/, int /*domain_id*/, float /*constraint*/) override {}
        void add_max_output_constraint(int /*node_id*/, int /*domain_id*/, float /*constraint*/) override {}
        void add_min_output_constraint(int /*node_id*/, int /*domain_id*/, float /*constraint*/) override {}
        void add_setup_constraint(int /*src_domain_id*/, int /*sink_domain_id*/, int /*capture_node*/, float /*constraint*/) override {}
        void add_hold_constraint(int /*src_domain_id*/, int /*sink_domain_id*/, int /*capture_node*/, float /*constraint*/) override {}
        void add_setup_uncertainty(int /*src_domain_id*/, int /*sink_domain_id*/, float /*uncertainty*/) override {}
        void add_hold_uncertainty(int /*src_domain_id*/, int /*sink_domain_id*/, float /*uncertainty*/) override {}
        void add_early_source_latency(int /*domain_id*/, float /*latency*/) override {}
        void add_late_source_latency(int /*domain_id*/, float /*latency*/) override {}
        void finish_constraints() override {}

        void start_delay_model() override {}
        void add_edge_delay(int /*edge_id*/, float /*min_delay*/, float /*max_delay*/) override {}
        void add_edge_setup_hold_time(int /*edge_id*/, float /*setup_time*/, float /*hold_time*/) override {}
        void finish_delay_model() override {}

        void start_results() override {}
        void add_node_tag(tp::TagType /*type*/, int /*node_id*/, int /*launch_domain_id*/, int /*capture_domain_id*/, float /*time*/) override {}
        void add_edge_slack(tp::TagType /*type*/, int /*edge_id*/, int /*launch_domain_id*/, int /*capture_domain_id*/, float /*slack*/) override {}
        void add_node_slack(tp::TagType /*type*/, int /*node_id*/, int /*launch_domain_id*/, int /*capture_domain_id*/, float /*slack*/) override {}
        void finish_results() override {}

        //End of parsing
        void finish_parse() override {}

        //Error during parsing
        void parse_error(const int curr_lineno, const std::string& near_text, const std::string& msg) override {
            fprintf(stderr, "%s:%d: Parse error: %s", filename_.c_str(), curr_lineno, msg.c_str());
            if(!near_text.empty()) {
                fprintf(stderr, " near '%s'", near_text.c_str());
                fprintf(stderr, "\n");
            } else {
                if(msg.find('\n') == std::string::npos) {
                    fprintf(stderr, "\n");
                }
            }
        }
    private:
        std::string filename_;
};

int main(int argc, char** argv) {

    //PrintCallback cb;
    NopCallback cb;
    if(argc == 2) {
        if(std::string(argv[1]) == "-") {
            tp::tatum_parse_file(stdin, cb);  
        } else {
            tp::tatum_parse_filename(argv[1], cb);  
        }
    } else {
        fprintf(stderr, "Invalid command line\n");
        return 1;
    }
}
