// OpenSTA, Static Timing Analyzer
// Copyright (c) 2026, Parallax Software, Inc.
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// 
// The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software.
// 
// Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 
// This notice may not be removed or altered from any source distribution.

#include "FilterObjects.hh"

#include <cstddef>
#include <map>
#include <memory>
#include <regex>
#include <set>
#include <stack>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "Clock.hh"
#include "ContainerHelpers.hh"
#include "Format.hh"
#include "GraphClass.hh"
#include "GraphCmp.hh"
#include "Liberty.hh"
#include "LibertyClass.hh"
#include "Network.hh"
#include "NetworkClass.hh"
#include "PathEnd.hh"
#include "PatternMatch.hh"
#include "Property.hh"
#include "SdcClass.hh"
#include "SearchClass.hh"
#include "Sta.hh"
#include "StringUtil.hh"

namespace sta {

class FilterExpr
{
public:
  struct Token
  {
    virtual ~Token() = default;
    enum class Kind {
      skip = 0,
      predicate,
      op_lparen,
      op_rparen,
      op_or,
      op_and,
      op_inv,
      defined,
      undefined
    };
        
    Token(std::string_view text,
          Kind kind);
    const std::string &text() const { return  text_; }
    Kind kind() const { return kind_; }

    std::string text_;
    Kind kind_;
  };
    
  struct PredicateToken : public Token
  {
    PredicateToken(std::string_view property,
                   std::string_view op,
                   std::string_view arg);
    ~PredicateToken() override = default;
    const std::string &property() const { return property_; }
    const std::string &op() const { return op_; }
    const std::string &arg() const { return arg_; }
      
    std::string property_;
    std::string op_;
    std::string arg_;
  };
    
  FilterExpr(std::string_view expression,
             Report *report);
    
  std::vector<std::unique_ptr<Token>> postfix();
private:
  std::vector<std::unique_ptr<Token>> lex();
  std::vector<std::unique_ptr<Token>> shuntingYard(std::vector<std::unique_ptr<Token>> &infix);
    
  std::string raw_;
  Report *report_;
};

FilterExpr::Token::Token(std::string_view text,
                         Token::Kind kind) :
  text_(text),
  kind_(kind)
{
}

FilterExpr::PredicateToken::PredicateToken(std::string_view property,
                                           std::string_view op,
                                           std::string_view arg) :
  Token(sta::format("{} {} {}", property, op, arg),
        Token::Kind::predicate),
  property_(property),
  op_(op),
  arg_(arg)
{
}

FilterExpr::FilterExpr(std::string_view expression,
                       Report *report) :
  raw_(expression),
  report_(report)
{
}

std::vector<std::unique_ptr<FilterExpr::Token>>
FilterExpr::postfix()
{
  auto infix = lex();
  return shuntingYard(infix);
}

std::vector<std::unique_ptr<FilterExpr::Token>>
FilterExpr::lex()
{
  std::vector<std::pair<std::regex, Token::Kind>> token_regexes = {
    {std::regex("^\\s+"), Token::Kind::skip},
    {std::regex("^defined\\(([a-zA-Z_]+)\\)"), Token::Kind::defined},
    {std::regex("^undefined\\(([a-zA-Z_]+)\\)"), Token::Kind::undefined},
    {std::regex("^@?([a-zA-Z_]+) *((==|!=|=~|!~) *([0-9a-zA-Z_\\/$\\[\\]*?.]+))?"),
     Token::Kind::predicate},
    {std::regex("^(&&)"), Token::Kind::op_and},
    {std::regex("^(\\|\\|)"), Token::Kind::op_or},
    {std::regex("^(!)"), Token::Kind::op_inv},
    {std::regex("^(\\()"), Token::Kind::op_lparen},
    {std::regex("^(\\))"), Token::Kind::op_rparen},
  };
    
  std::vector<std::unique_ptr<Token>> result;
  const char* ptr = raw_.data();
  bool match = false;
  while (*ptr != '\0') {
    match = false;
    for (auto& [regex, kind]: token_regexes) {
      std::cmatch token_match;
      if (std::regex_search(ptr, token_match, regex)) {
        if (kind == Token::Kind::predicate) {
          std::string property = token_match[1].str();
                    
          // The default operation on a predicate if an op and arg are
          // omitted is 'prop == 1'.
          std::string op = "==";
          std::string arg = "1";
                    
          if (token_match[2].length() != 0) {
            op = token_match[3].str();
            arg = token_match[4].str();
          }
          result.push_back(std::make_unique<PredicateToken>(property, op, arg));
        }
        else if (kind == Token::Kind::defined)
          result.push_back(std::make_unique<Token>(token_match[1].str(), kind));
        else if (kind == Token::Kind::undefined)
          result.push_back(std::make_unique<Token>(token_match[1].str(), kind));
        else if (kind != Token::Kind::skip)
          result.push_back(std::make_unique<Token>(std::string(ptr,token_match.length()),
                                                   kind));
        ptr += token_match.length();
        match = true;
        break;
      };
    }
    if (!match)
      report_->error(2600, "-filter parsing failed at '{}'.", ptr);
  }
  return result;
}

std::vector<std::unique_ptr<FilterExpr::Token>>
FilterExpr::shuntingYard(std::vector<std::unique_ptr<Token>> &infix)
{
  std::vector<std::unique_ptr<Token>> output;
  std::stack<std::unique_ptr<Token>> operator_stack;

  for (auto &token : infix) {
    switch (token->kind()) {
    case Token::Kind::predicate:
      output.push_back(std::move(token));
      break;
    case Token::Kind::op_or:
      [[fallthrough]];
    case Token::Kind::op_and:
      // The operators' enum values are ascending by precedence:
      //   inv > and > or
      while (!operator_stack.empty()
             && operator_stack.top()->kind() > token->kind()) {
        output.push_back(std::move(operator_stack.top()));
        operator_stack.pop();
      }
      operator_stack.push(std::move(token));
      break;
    case Token::Kind::op_inv:
      // Unary with highest precedence, no need for the while loop.
      operator_stack.push(std::move(token));
      break;
    case Token::Kind::defined:
      operator_stack.push(std::move(token));
      break;
    case Token::Kind::undefined:
      operator_stack.push(std::move(token));
      break;
    case Token::Kind::op_lparen:
      operator_stack.push(std::move(token));
      break;
    case Token::Kind::op_rparen:
      if (operator_stack.empty())
        report_->error(2601, "-filter extraneous ).");
      while (!operator_stack.empty()
             && operator_stack.top()->kind() != Token::Kind::op_lparen) {
        output.push_back(std::move(operator_stack.top()));
        operator_stack.pop();
        if (operator_stack.empty())
          report_->error(2602, "-filter extraneous ).");
      }
      // Guaranteed to be lparen at this point.
      operator_stack.pop();
      break;
    default:
      // Unhandled/skip.
      break;
    }
  }

  while (!operator_stack.empty()) {
    if (operator_stack.top()->kind() == Token::Kind::op_lparen)
      report_->error(2603, "-filter unmatched (.");
    output.push_back(std::move(operator_stack.top()));
    operator_stack.pop();
  }

  return output;
}

////////////////////////////////////////////////////////////////

template <typename T> static std::set<T*>
filterObjects(std::string_view property,
              std::string_view op,
              std::string_view pattern,
              std::set<T*> &all,
              Sta *sta)
{
  Properties &properties = sta->properties();
  Network *network = sta->network();
  auto filtered_objects = std::set<T*>();
  bool exact_match = (op == "==");
  bool pattern_match = (op == "=~");
  bool not_match = (op == "!=");
  bool not_pattern_match = (op == "!~");
  for (T *object : all) {
    PropertyValue value = properties.getProperty(object, property);
    std::string prop = value.to_string(network);
    if (value.type() == PropertyValue::Type::bool_) {
      // Canonicalize bool true/false to 1/0.
      if (stringEqual(pattern, "true"))
        pattern = "1";
      else if (stringEqual(pattern, "false"))
        pattern = "0";
    }
    if ((exact_match && prop == pattern)
        || (not_match && prop != pattern)
        || (pattern_match && patternMatch(pattern, prop))
        || (not_pattern_match && !patternMatch(pattern, prop)))
      filtered_objects.insert(object);
  }
  return filtered_objects;
}

template <typename T> static std::vector<T*>
filterObjects(std::string_view filter_expression,
              const std::vector<T*> *objects,
              const std::function<bool (T *obj1, T *obj2)> &object_less,
              Sta *sta)
{
  Report *report = sta->report();
  Network *network = sta->network();
  Properties &properties = sta->properties();
  std::vector<T*> result;
  if (objects) {
    std::set<T*> all;
    for (auto object: *objects)
      all.insert(object);
    // Delete objects before parsing so errors to not leak them.
    delete objects;

    FilterExpr filter(filter_expression, report);
    auto postfix = filter.postfix();
    std::stack<std::set<T*>> eval_stack;
    for (auto &token : postfix) {
      if (token->kind() == FilterExpr::Token::Kind::op_or) {
        if (eval_stack.size() < 2)
          report->error(2604, "-filter logical OR requires at least two operands.");
        auto arg0 = eval_stack.top();
        eval_stack.pop();
        auto arg1 = eval_stack.top();
        eval_stack.pop();
        auto union_result = std::set<T*>();
        std::set_union(arg0.cbegin(), arg0.cend(), arg1.cbegin(), arg1.cend(),
                       std::inserter(union_result, union_result.begin()));
        eval_stack.push(union_result);
      }
      else if (token->kind() == FilterExpr::Token::Kind::op_and) {
        if (eval_stack.size() < 2) {
          report->error(2605, "-filter logical AND requires two operands.");
        }
        auto arg0 = eval_stack.top();
        eval_stack.pop();
        auto arg1 = eval_stack.top();
        eval_stack.pop();
        auto intersection_result = std::set<T*>();
        std::set_intersection(arg0.cbegin(), arg0.cend(),
                              arg1.cbegin(), arg1.cend(),
                              std::inserter(intersection_result,
                                            intersection_result.begin()));
        eval_stack.push(intersection_result);
      }
      else if (token->kind() == FilterExpr::Token::Kind::op_inv) {
        if (eval_stack.size() < 1) {
          report->error(2606, "-filter NOT missing operand.");
        }
        auto arg0 = eval_stack.top();
        eval_stack.pop();
        
        auto difference_result = std::set<T*>();
        std::set_difference(all.cbegin(), all.cend(),
                            arg0.cbegin(), arg0.cend(),
                            std::inserter(difference_result,
                                          difference_result.begin()));
        eval_stack.push(difference_result);
      }
      else if (token->kind() == FilterExpr::Token::Kind::defined
               || token->kind() == FilterExpr::Token::Kind::undefined) {
        bool should_be_defined =
          (token->kind() == FilterExpr::Token::Kind::defined);
        auto result = std::set<T*>();
        for (auto object : all) {
          PropertyValue value = properties.getProperty(object, token->text());
          bool is_defined = false;
          switch (value.type()) {
          case PropertyValue::Type::float_:
            is_defined = value.floatValue() != 0;
            break;
          case PropertyValue::Type::bool_:
            is_defined = value.boolValue();
            break;
          case PropertyValue::Type::string:
          case PropertyValue::Type::liberty_library:
          case PropertyValue::Type::liberty_cell:
          case PropertyValue::Type::liberty_port:
          case PropertyValue::Type::library:
          case PropertyValue::Type::cell:
          case PropertyValue::Type::port:
          case PropertyValue::Type::instance:
          case PropertyValue::Type::pin:
          case PropertyValue::Type::net:
          case PropertyValue::Type::clk:
            is_defined = !value.to_string(network).empty();
            break;
          case PropertyValue::Type::none:
            is_defined = false;
            break;
          case PropertyValue::Type::pins:
            is_defined = !value.pins()->empty();
            break;
          case PropertyValue::Type::clks:
            is_defined = !value.clocks()->empty();
            break;
          case PropertyValue::Type::paths:
            is_defined = !value.paths()->empty();
            break;
          case PropertyValue::Type::pwr_activity:
            is_defined = value.pwrActivity().isSet();
            break;
          }
          if (is_defined == should_be_defined) {
            result.insert(object);
          }
        }
        eval_stack.push(result);
      }
      else if (token->kind() == FilterExpr::Token::Kind::predicate) {
        auto *predicate_token =
          static_cast<FilterExpr::PredicateToken *>(token.get());
        auto result = filterObjects<T>(predicate_token->property(),
                                       predicate_token->op(),
                                       predicate_token->arg(),
                                       all, sta);
        eval_stack.push(result);
      }
    }
    if (eval_stack.empty())
      report->error(2607, "-filter expression is empty.");
    if (eval_stack.size() > 1)
      // huh?
      report->error(2608, "-filter expression evaluated to multiple sets.");
    auto result_set = eval_stack.top();
    result.resize(result_set.size());
    std::copy(result_set.begin(), result_set.end(), result.begin());
    sort(result, [object_less] (T *obj1, T *obj2) {
      return object_less(obj1, obj2);
    });
  }
  return result;
}

PortSeq
filterPorts(std::string_view filter_expression,
            PortSeq *ports,
            Sta *sta)
{
  Network *network = sta->network();
  return filterObjects<const Port>(filter_expression, ports,
                                   [network] (const Port *port1,
                                              const Port *port2) {
                                     return network->name(port1) < network->name(port2);
                                   }, sta);
}

InstanceSeq
filterInstances(std::string_view filter_expression,
                InstanceSeq *insts,
                Sta *sta)
{
  Network *network = sta->network();
  return filterObjects<const Instance>(filter_expression, insts,
                                   [network] (const Instance *inst1,
                                              const Instance *inst2) {
                                     return network->name(inst1) < network->name(inst2);
                                   }, sta);
}

PinSeq
filterPins(std::string_view filter_expression,
           PinSeq *pins,
           Sta *sta)
{
  Network *network = sta->network();
  return filterObjects<const Pin>(filter_expression, pins,
                                  [network] (const Pin *pin1,
                                             const Pin *pin2) {
                                     return network->pathName(pin1) < network->pathName(pin2);
                                   }, sta);
}

NetSeq
filterNets(std::string_view filter_expression,
           NetSeq *nets,
           Sta *sta)
{
  Network *network = sta->network();
  return filterObjects<const Net>(filter_expression, nets,
                                   [network] (const Net *net1,
                                              const Net *net2) {
                                     return network->pathName(net1) < network->pathName(net2);
                                   }, sta);
}

ClockSeq
filterClocks(std::string_view filter_expression,
             ClockSeq *clks,
             Sta *sta)
{
  return filterObjects<Clock>(filter_expression, clks,
                                   [] (const Clock *clk1,
                                       const Clock *clk2) {
                                     return clk1->name() < clk2->name();
                                   }, sta);
}

LibertyCellSeq
filterLibCells(std::string_view filter_expression,
               LibertyCellSeq *cells,
               Sta *sta)
{
  return filterObjects<LibertyCell>(filter_expression, cells,
                                    [] (const LibertyCell *cell1,
                                        const LibertyCell *cell2) {
                                      return cell1->name() < cell2->name();
                                    }, sta);
}

LibertyPortSeq
filterLibPins(std::string_view filter_expression,
              LibertyPortSeq *ports,
              Sta *sta)
{
  return filterObjects<LibertyPort>(filter_expression, ports,
                                    [] (const LibertyPort *port1,
                                        const LibertyPort *port2) {
                                      return port1->name() < port2->name();
                                    }, sta);
}

LibertyLibrarySeq
filterLibertyLibraries(std::string_view filter_expression,
                       LibertyLibrarySeq *libs,
                       Sta *sta)
{
  return filterObjects<LibertyLibrary>(filter_expression, libs,
                                       [] (const LibertyLibrary *lib1,
                                           const LibertyLibrary *lib2) {
                                         return lib1->name() < lib2->name();
                                       }, sta);
}

EdgeSeq
filterTimingArcs(std::string_view filter_expression,
                 EdgeSeq *edges,
                 Sta *sta)
{
  Network *network = sta->network();
  Graph *graph = sta->graph();
  EdgeLess edge_less(network, graph);
  return filterObjects<Edge>(filter_expression, edges,
                             [edge_less] (const Edge *edge1,
                                          const Edge *edge2) {
                               return edge_less.operator()(edge1, edge2);
                             }, sta);
}

PathEndSeq
filterPathEnds(std::string_view filter_expression,
               PathEndSeq *ends,
               Sta *sta)
{
  PathEndLess end_less(true, sta);
  return filterObjects<PathEnd>(filter_expression, ends,
                                [end_less] (const PathEnd *end1,
                                            const PathEnd *end2) {
                               return end_less.operator()(end1, end2);
                             }, sta);
}

StringSeq
filterExprToPostfix(std::string_view expr,
                    Report *report)
{
  FilterExpr filter(expr, report);
  auto postfix = filter.postfix();
  StringSeq result;
  for (auto &token : postfix)
    result.push_back(token->text());
  return result;
}

} // namespace sta
