/*
 * The MIT License (MIT)
 * 
 * Copyright (c) 2014 Krzysztof Narkiewicz <krzysztof.narkiewicz@STLRom.com>
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 * 
 */

#ifndef STLDRIVER_H
#define STLDRIVER_H

#include <vector>
#include <map>


#include "scanner.h"
#include "transducer.h"
#include "stl_monitor.h"

// autogenerated by Bison, don't panic
// if your IDE can't resolve it - call make first
#include "parser.hpp"

typedef STLRom::Parser::token token;
typedef STLRom::Parser::token_type token_type;
typedef STLRom::Parser::location_type location_type;


namespace STLRom {

// forward declare our simplistic AST node class so we
// can declare container for it without the header
class transducer;

/** A class encapsulating an STL formula plus feedback messages to be written in
     * the report based on its satisfaction or violation status.  */
    class stl_test
    {
    public:
        string test_id;
        map<string, double> param_map;
        transducer *formula;
        string report_positive;
        string report_negative;
        bool is_error;

        stl_test()
        {
            is_error = false;
            formula = nullptr;
        };

        stl_test(const string &_test_id, const map<string, double> &_param_map, transducer *_formula, const string &_report_pos,
                 const string &_report_neg, bool _is_err) : test_id(_test_id),
                                                            param_map(_param_map),
                                                            formula(_formula),
                                                            report_positive(_report_pos),
                                                            report_negative(_report_neg),
                                                            is_error(_is_err) {
                                                            };

        ~stl_test()
        {
            delete formula;
        }
    };

    struct trace_test
    {
        string id;
        string env;
        double sim_time;
        bool visu;
        deque<stl_test> tests;
    };



/**
 * This class is the interface for our scanner/lexer. The end user
 * is expected to use this. It drives scanner/lexer, keeps
 * parsed AST and generally is a good place to store additional
 * context data. Both parser and lexer have access to it via internal 
 * references.
 * 
 * I know that the AST is a bit too strong word for a simple
 * vector with nodes, but this is only an example. Get off me.
 */
class STLDriver
{
public:

    Semantics semantics;  /**< semantics to use */
    Interpol  interpol;   /**< interpolation method */

    /** enable debug output in the flex scanner */
    bool trace_scanning;

    /** enable debug output in the bison parser */
    bool trace_parsing;

    /** enable verbose mode for parser */
    bool verbose_parser;

    /** stream name (file or input stream) used for error messages */
    std::string streamname;

    /** parameters in formulas */
    map<string, double> param_map;

    /** signals in formulas */
    map<string, int> signal_map;

    /** formulas defined by the driver */
    map<string, transducer*> formula_map;

    /** data array - time is first column */
    trace_data data;

    /** append new sample to data */
    void add_sample(vector <double> sample);

    // /** tests (sets of formulas) */
    // map<string, stl_test> stl_test_map;
    // deque<trace_test> trace_test_queue;

    // string report;
    // string test_log;
    // int nb_test_pos;
    // int nb_test_total;
    // bool error_flag;

    string get_signals_names() const;

    /// CONSTRUCTORS

    /** construct a new parser driver context */
    STLDriver();

    /** construct a parser driver with data */
    STLDriver(trace_data _trace);

    ~STLDriver();

    /** Copy constructor */
    STLDriver(const STLDriver &other);

    /** Copy assignment operator */
    STLDriver &operator=(const STLDriver &other);

    /** Move constructor */
    STLDriver(STLDriver &&other) noexcept;

    /** Move assignment operator */
    STLDriver &operator=(STLDriver &&other) noexcept;

    inline void set_semantics(const std::string &sem)
    {
        if (sem == "SPACE")
        {
            semantics = Semantics::SPACE;
        }
        else if (sem == "LEFT_TIME")
        {
            semantics = Semantics::LEFT_TIME;
        }
        else if (sem == "RIGHT_TIME")
        {
            semantics = Semantics::RIGHT_TIME;
        }
        else if (sem == "BOOLEAN")
        {
            semantics = Semantics::BOOLEAN;
        }
        else
        {
            throw std::invalid_argument("Invalid semantics string");
        };
    };

    inline std::string get_semantics() const
    {
        switch (semantics)
        {
        case Semantics::SPACE:
            return "SPACE";
        case Semantics::LEFT_TIME:
            return "LEFT_TIME";
        case Semantics::RIGHT_TIME:
            return "RIGHT_TIME";
        case Semantics::BOOLEAN:
            return "BOOLEAN";
        default:
            throw std::invalid_argument("Invalid semantics value");
        }
    }

    inline void set_interpol(const std::string &interp)
    {
        if (interp == "PREVIOUS")
        {
            interpol = Interpol::PREVIOUS;
        }
        else if (interp == "LINEAR")
        {
            interpol = Interpol::LINEAR;
        }
        else
        {
            throw std::invalid_argument("Invalid interpol string");
        }
    }

    inline std::string get_interpol()
    {
        switch (interpol)
        {
        case Interpol::PREVIOUS:
            return "PREVIOUS";
        case Interpol::LINEAR:
            return "LINEAR";
        default:
            throw std::invalid_argument("Invalid interpol value");
        }
    }
    

    // TODO: add get_param, set_param, get_online_rob, set_online_rob.


    /// PARSER
    /**
     * Run parser. Results are stored inside.
     * \returns 0 on success, 1 on failure
     */
    int parse();

    /** Invoke the scanner and parser for a stream.
     * @param in	input stream
     * @param sname	stream name for error messages
     * @return		true if successfully parsed
     */
    bool parse_stream(std::istream &in);

    /** Invoke the scanner and parser on an input string.
     * @param input	input string
     * @param sname	stream name for error messages
     * @return		true if successfully parsed
     */
    bool parse_string(const std::string &input);

    /** Invoke the scanner and parser on a file. Use parse_stream with a
     * std::ifstream if detection of file reading errors is required.
     * @param filename	input file name
     * @return		true if successfully parsed
     */
    bool parse_file(const std::string &filename);

    /** Invoke the scanner and parser on a file and print file content. Use parse_stream with a
     * std::ifstream if detection of file reading errors is required.
     * @param filename	input file name
     * @param verbose   enable verbose output
     * @return		true if successfully parsed
     */
    bool parse_file(const std::string &filename, bool verbose);

    // Creates an independant monitor with its own data
    STLMonitor get_monitor(const string &id) const;
    
    /**
     * Clear AST
     */
    void clear();
    
    /**
     * Print AST
     */
    std::string str() const;

    // To demonstrate pure handling of parse errors, instead of
    // simply dumping them on the standard error output, we will pass
    // them to the driver using the following two member functions.

    /** Error handling with associated line number. This can be modified to
     * output the error e.g. to a dialog box. */
    void error(const class location &l, const std::string &m);

    /** General error handling. This can be modified to output the error
     * e.g. to a dialog box. */
    void error(const std::string &m);
    
    /** returns value of parameter param */
    double get_param(const string &param);
    void set_param(const string &param, double n);

    /** monitor a single formula requires data is not empty */
    vector<double> get_online_rob(const string &phi_in);
    vector<double> get_online_rob(const string &phi_in, double);


    /**
     * Switch scanner input stream. Default is standard input (std::cin).
     * It will also reset AST.
     */
    void switchInputStream(std::istream *is);
    
    /**
     * This is needed so that Scanner and Parser can call some
     * methods that we want to keep hidden from the end user.
     */
    friend class Parser;
    friend class Scanner;

    // more utility functions
    /** Prints driver to stream output */
    void print(ostream &out) const;

    /** Prints driver to standard output */
    inline void print() const { print(cout); };
    inline void disp() { print(cout); };

    /** Read a trace file */
    inline bool read_trace_file(string trace_file_name)
    {
        data.clear();
        return read_trace(trace_file_name, data);
    }


    /** dump all assigned formulas satisfaction function to a file */
    void dump();
    void dump_trace_file(const string &);
    // bool dump_test_log_file(const string &);
    void print_trace(ostream &os);
    void print_trace();
    


private:
    Scanner m_scanner;
    Parser m_parser;
};

}

#endif // STLDRIVER_H
