/*
 *  this processor eliminates duplicate atomic routing rules in one routing table
 */
bool RoutingCompiler_openbsd::eliminateDuplicateRules::processNext()
{
    RoutingCompiler_openbsd *bsd_comp = dynamic_cast<RoutingCompiler_openbsd*>(compiler);
    RoutingRule *rule = getNext(); if (rule==NULL) return false;

    if (rule->isFallback() || rule->isHidden())
    {
        tmp_queue.push_back(rule);
        return true;
    }

    assert (bsd_comp->printRule!=NULL);
    
    string thisRule = bsd_comp->printRule->RoutingRuleToString(rule, false);
    
    map<string, string>::iterator rules_it = rules_seen_so_far.find(thisRule);
            
    if (rules_it != rules_seen_so_far.end())
    {
        QString msg = QObject::tr("Two of the routing commands created from the gui "
                                  "routing rules %1 and %2 "
                                  "are identical, skipping the second. "
                                  "Revise them to avoid this warning");
        compiler->warning(
            rule,
            msg.arg(rules_it->second.c_str()).arg(rule->getLabel().c_str()).toStdString());
        return true;
    }

    tmp_queue.push_back(rule);
    rules_seen_so_far[thisRule] = rule->getLabel();
    return true;
}
string RoutingCompiler::debugPrintRule(Rule *r)
{
    RoutingRule *rule = RoutingRule::cast(r);

    RuleElementRDst *dstrel = rule->getRDst();
    RuleElementRItf *itfrel = rule->getRItf();
    RuleElementRGtw *gtwrel = rule->getRGtw();

    ostringstream str;

//    str << setw(70) << setfill('-') << "-";

    string dst, itf, gtw;
   
    FWObject *obj = FWReference::getObject(itfrel->front());
    itf = (obj) ? obj->getName() : "NULL";

    obj = FWReference::getObject(gtwrel->front());
    gtw = (obj) ? obj->getName() : "NULL";
     
    
    int no = 0;
    FWObject::iterator i1 = dstrel->begin();
    while ( i1!=dstrel->end())
    {
        str << endl;

        dst = " ";

        if (i1 != dstrel->end())
        {
            FWObject *o = FWReference::getObject(*i1);
            dst = (o) ? o->getName() : "NULL";
        }

        int w = 0;
        if (no==0)
        {
            str << rule->getLabel();
            w = rule->getLabel().length();
        }
        
        str <<  setw(10-w)  << setfill(' ') << " ";

        str <<  setw(18) << setfill(' ') << dst.c_str() << " ";
        str <<  setw(18) << setfill(' ') << itf.c_str() << " ";
        str <<  setw(18) << setfill(' ') << gtw.c_str() << " ";
        str <<  setw(18) << setfill(' ') << " ";

        ++no;

        if ( i1 != dstrel->end() ) ++i1;
    }
    return str.str();
}
/*
 * Call this after converting to atomic rules by DST to be sure there
 * is just one object in DST.
 */
bool RoutingCompiler::sameDestinationDifferentGateways::processNext()
{
    slurp();
    if (tmp_queue.size()==0) return false;

    // map destination to gateway.
    std::map<string, string> dst_to_gw;
    std::map<string, string> dst_to_rule;

    for (deque<Rule*>::iterator k=tmp_queue.begin(); k!=tmp_queue.end(); ++k)
    {
        RoutingRule *rule = RoutingRule::cast( *k );
        
        RuleElementRDst *dstrel = rule->getRDst();
        Address *dst = Address::cast(FWReference::getObject(dstrel->front()));
        const InetAddr* dst_addr = dst->getAddressPtr();
        const InetAddr* dst_netm = dst->getNetmaskPtr();
        string key = dst_addr->toString() + "/" + dst_netm->toString();

        // RuleElementRItf *itfrel = rule->getRItf();
        // FWObject *itf = FWReference::cast(itfrel->front())->getPointer();
    
        RuleElementRGtw *gtwrel = rule->getRGtw();
        Address *gtw = Address::cast(FWReference::getObject(gtwrel->front()));
        const InetAddr* gtw_addr = gtw->getAddressPtr();
        const InetAddr* gtw_netm = gtw->getNetmaskPtr();
        string val = gtw_addr->toString() + "/" + gtw_netm->toString();

        if (!dst_to_gw[key].empty() && dst_to_gw[key] != val)
        {
            compiler->abort(
                rule,
                "Rules " + dst_to_rule[key] + " and " + rule->getLabel() +
                " define routes to the same destination " + key +
                " via different gateways. This configuration is not supported"
                " for " + compiler->fw->getStr("host_OS"));
        } else
        {
            dst_to_gw[key] = val;
            dst_to_rule[key] = rule->getLabel();
        }
    }

    return true;
}
bool RoutingCompiler_iosacl::PrintRule::processNext()
{
    RoutingRule *rule = getNext(); if (rule == nullptr) return false;
    tmp_queue.push_back(rule);
    
    string rl = rule->getLabel();
    string comm = rule->getComment();
    string::size_type c1, c2;
    c1 = 0;
    
    if (!compiler->inSingleRuleCompileMode() && rl != current_rule_label)
    {
        compiler->output << "! " << endl;
        compiler->output << "! Rule " << rl << endl;
        compiler->output << "! " << endl;
    }
   
//    string err = rule->getCompilerMessage();
//    if (!err.empty()) compiler->output << "# " << err << endl;

    if( rule->getRuleType() != RoutingRule::MultiPath )
    {
        if (!compiler->inSingleRuleCompileMode() && rl != current_rule_label)
        {
            while ( (c2 = comm.find('\n',c1)) != string::npos )
            {
                compiler->output << "! " << comm.substr(c1,c2-c1) << endl;
                c1 = c2 + 1;
            }
            compiler->output << "! " << comm.substr(c1) << endl;
            compiler->output << "! " << endl;

            string err = compiler->getErrorsForRule(rule, "! ");
            if (!err.empty()) compiler->output << err << endl;

            current_rule_label = rl;
        }
        
        string command_line = RoutingRuleToString(rule);
        compiler->output << command_line;
    
    } else
    {
        string err = compiler->getErrorsForRule(rule, "! ");
        if (!err.empty()) compiler->output << err << endl;

        compiler->abort(rule, "MultiPath routing not supported by platform");
    }
    return true;
}
/*
 *  this processor eliminates duplicate routing rules, generated from the same
 *  rule in the GUI
 */
bool RoutingCompiler_openbsd::optimize3::processNext()
{
    RoutingCompiler_openbsd *bsd_comp = dynamic_cast<RoutingCompiler_openbsd*>(compiler);
    RoutingRule *rule = getNext(); if (rule==NULL) return false;

    if (rule->isFallback() || rule->isHidden())
    {
        tmp_queue.push_back(rule);
        return true;
    }

    assert (bsd_comp->printRule!=NULL);
    
    string thisRule = rule->getLabel() + " " +
        bsd_comp->printRule->RoutingRuleToString(rule, false);
    
    if (rules_seen_so_far.count(thisRule)!=0) return true;

    tmp_queue.push_back(rule);
    rules_seen_so_far[thisRule] = true;

    return true;
}
bool RoutingCompiler::competingRules::processNext()
{
    RoutingRule *rule = getNext(); if (rule==NULL) return false;
        
    RuleElementRItf *itfrel = rule->getRItf();
    FWObject *itf = FWReference::cast(itfrel->front())->getPointer();
    
    RuleElementRGtw *gtwrel = rule->getRGtw();
    FWObject *gtw = FWReference::cast(gtwrel->front())->getPointer();
     
    string metric = rule->getMetricAsString();
    string label  = rule->getSortedDstIds();
    ostringstream ostr;
    ostr << gtw->getId() << "_" << itf->getId();
    string combiId = ostr.str();
    
    if( label == "") compiler->abort(
        
            rule,         
            "Place 'createSortedDstIdsLabel()' before 'competingRules()' "
            "in the rule processor chain");
    
    dest_it = rules_seen_so_far.find(label);
    if( dest_it != rules_seen_so_far.end()) {
        
        // a rule with the same destination was already seen
        ///std::cout << "NO NEW DEST" << std::endl;
        
        gtwitf_it = dest_it->second.find(combiId);
        if( gtwitf_it != dest_it->second.end() )
        {
            // ... this gateway and interface combination were already
            // seen for this destination
            ///std::cout << "NO NEW GTWITF" << std::endl;
            
            if( gtwitf_it->second.first == metric) {
                
                // ... and same metric => rule already exists, skip
                ///std::cout << "SAME METRIC" << std::endl;
                
                string msg;
                msg = "Routing rules " + gtwitf_it->second.second +
                    " and " + rule->getLabel() +
                    " are identical, skipping the second one. " +
                    "Delete one of them to avoid this warning";
                compiler->warning(rule,  msg.c_str());
            } else {
                
                // ... but different metric => what metric should I use? => abort
                ///std::cout << "DIFFERENT METRIC" << std::endl;
                
                string msg;
                msg = "Routing rules " + gtwitf_it->second.second +
                    " and " + rule->getLabel() +
                    " are identical except for the metric, " +
                    "please delete one of them";
                compiler->abort(rule,  msg.c_str());
            }
        
        } else
        {
            // ... this gateway and interface combination is new for
            // this destination
            ///std::cout << "NEW GTWITF" << std::endl;///
            
            if(false)
            {
                // TODO_lowPrio: if (
                // !compiler->fw->getOptionsObject()->getBool
                // ("equal_cost_multi_path") ) ...If multipath is
                // turned off, perform this check.

                // iterate all gtwitf combis in the map
                // dest_it->second and search for the current metric
                
                // ... but has the same metric => what route should I
                // use for this destination? => abort
                    
                string msg;
                msg = "Routing rules " + gtwitf_it->second.second + " and " +
                    rule->getLabel() +
                    " have the same destination and same metric,"
                    "but different gateway and interface combination. "
                    "Set the metrics to different values or "
                    "enable ECMP (Equal Cost MultiPath) routing";
                compiler->abort( msg.c_str() );
            
            } else
            {
                // ... and different metric OR equal_cost_multi_path enabled => OK
                tmp_queue.push_back(rule);
            }
            
            dest_it->second[combiId] =
                pair< string, string>( metric, rule->getLabel());
        }
        
    } else {
        
        // this destination is new
        //std::cout << "NEW DEST" << std::endl;
        ///
        
        //ruleinfo tmpRuleInfo = { gtw->getStr("id") + itf->getStr("id"), metric, rule->getLabel()};
        //rules_seen_so_far[label] = tmpRuleInfo;
        
        map< string, pair< string, string> > gtw_itf_tmp;
        gtw_itf_tmp[combiId] = pair< string, string>( metric, rule->getLabel());
        
        rules_seen_so_far[label] = gtw_itf_tmp;
        
        
        tmp_queue.push_back(rule);
    }

    return true;
}
bool RoutingCompiler_openbsd::PrintRule::processNext()
{
    RoutingCompiler_openbsd *bsd_comp = 
        dynamic_cast<RoutingCompiler_openbsd*>(compiler);

    slurp();
    if (tmp_queue.size()==0) return false;


    if (!compiler->inSingleRuleCompileMode())
    {
        Configlet routing_functions(compiler->fw,
                                    compiler->fw->getStr("host_OS"),
                                    "routing_functions");

        // we should delete default route if we have a new one to
        // install. IF user did not define any routes that look like
        // default (i.e. where destination is "any"), then we should
        // preserve default so that we won't leave machine with no
        // default at all.
        QString route_pattern = "";
        if (bsd_comp->have_default_route)
        {
            // If we will install default route, delete it now
            route_pattern = "'lo0'";
        } else
        {
            // do not delete default if we won't install new one
            route_pattern = "'lo0|default'";
        }

        routing_functions.setVariable("route_filter", route_pattern);
        compiler->output << routing_functions.expand().toStdString();

        bsd_comp->defined_restore_script_output = true;
    }
    

    for (deque<Rule*>::iterator k=tmp_queue.begin(); k!=tmp_queue.end(); ++k) 
    {
        RoutingRule *rule = RoutingRule::cast( *k );

        string rl = rule->getLabel();
    
        if (!compiler->inSingleRuleCompileMode() && rl!=current_rule_label)
        {
            compiler->output << "# " << endl;
            compiler->output << "# Rule " << rl << endl;
            //compiler->output << "# " << rule->getRuleTypeAsString() << endl;
            compiler->output << "# " << endl;
            compiler->output << "echo \"Routing rule " << rl << "\"" << endl;
            compiler->output << "# " << endl;
        }
    
        if (rule->getRuleType() != RoutingRule::MultiPath )
        {
            if (!compiler->inSingleRuleCompileMode() && rl!=current_rule_label)
            {
                QStringList comment = QString::fromUtf8(
                    rule->getComment().c_str()).split("\n");
                int comment_lines = 0;
                foreach (QString str, comment)
                {
                    if (!str.isEmpty())
                    {
                        compiler->output << "# " << str.toUtf8().data() << endl;
                        ++comment_lines;
                    }
                }
                if (comment_lines) compiler->output << "#" << endl;

                string err = compiler->getErrorsForRule(rule, "# ");
                if (!err.empty()) compiler->output << err << endl;

                current_rule_label = rl;
            }
        
//            string err = rule->getCompilerMessage();
//            if (!err.empty()) compiler->output << "# " << err << endl;

            string  command_line = RoutingRuleToString(rule);
            compiler->output << command_line;
    
        }
    }