#include "dcipfilter.h"

#include <QFile>
#include <QDir>
#include <QTextStream>
#include <QMessageBox>

//#define _DEBUG_IPFILTER_

#ifdef _DEBUG_IPFILTER_
#include <QtDebug>
#endif

const QString signature = "$EISKALTDC IPFILTERLIST$";

DCIPFilter *DCIPFilter::pIPFilter = NULL;

DCIPFilter::DCIPFilter() {
}

DCIPFilter::~DCIPFilter() {
}

quint32 DCIPFilter::StringToUint32(const QString& ip){
    bool test;
    quint32 ret = ip.section('.',0,0).toULong(&test);
    ret <<= 8;
    ret |= ip.section('.',1,1).toULong(&test);
    ret <<= 8;
    ret |= ip.section('.',2,2).toULong(&test);
    ret <<= 8;
    ret |= ip.section('.',3,3).toULong(&test);

    return ret;
}

QString DCIPFilter::Uint32ToString(quint32 ip){
    return QString("%1.%2.%3.%4").arg((ip & 0xFF000000) >> 24)
                                 .arg((ip & 0x00FF0000) >> 16)
                                 .arg((ip & 0x0000FF00) >> 8)
                                 .arg((ip & 0x000000FF));
}

quint32 DCIPFilter::MaskToCIDR(quint32 mask){
#ifdef _DEBUG_IPFILTER_
    printf("DCIPFilter::MaskToCIDR(%x)\n", mask);
#endif
    if (mask == 0)
        return 0;
    else if (mask == 0xFFFFFFFF)
        return 32;

    quint32 shift_mask = 0x00000001;

    int j = 0;

    for (; ((mask & shift_mask) == 0) && (j <= 32); j++, shift_mask <<= 1);

    j = 32 - j;

    return j;
}

quint32 DCIPFilter::MaskForBits(quint32 bits){
    bits = 32 - (bits > 32?32:bits);

    quint32 mask = 0xFFFFFFFF;
    quint32 bit  = 0xFFFFFFFE;

    for (int i = 0; i < bits; i++, bit <<= 1)
        mask &= bit;

    return mask;
}

bool DCIPFilter::ParseString(QString exp, quint32 &ip, quint32 &mask, eTableAction &act){
    if (exp == "" || exp.isNull())
        return false;

    if (exp.indexOf("/0") >= 0){
        if (exp.indexOf("!") == 0)
            act = etaDROP;
        else
            act = etaACPT;

        ip = mask = 0x00000000;

        return true;
    }

    QString str_ip = "", str_mask = "";

    if (exp.indexOf("/") > 0){
        int c_pos = exp.indexOf("/");

        str_ip = exp.left(c_pos);
        str_mask = exp.mid(c_pos+1,exp.length()-c_pos-1);
    }
    else
        str_ip = exp;

    if (str_ip.indexOf("!") == 0){
        act = etaDROP;
        str_ip.replace("!", "");
    }
    else
        act = etaACPT;

    if (!isIP(str_ip))
        return false;

    if (str_mask != "")
        mask = MaskForBits(str_mask.toULong() > 32?32:str_mask.toULong());
    else
        mask = MaskForBits(32);

    ip = StringToUint32(str_ip);

    return true;
}

void DCIPFilter::addToRules(QString exp, eDIRECTION direction) {
    quint32 exp_ip, exp_mask;
    eTableAction act;

    if (!ParseString(exp, exp_ip, exp_mask, act))
        return;

    IPFilterElem *el = NULL;

    if (list_ip.contains(exp_ip)) {
#ifdef _DEBUG_IPFILTER_
    qDebug() << "\tIP already in list";
#endif
        QIPHash::const_iterator it = list_ip.find(exp_ip);

        while (it != list_ip.end() && it.key() == exp_ip){
            el = it.value();

            if ((el->direction != direction) && (el->action == act)){
#ifdef _DEBUG_IPFILTER_
                qDebug() << "\tChange direction of IP";
#endif
                emit ruleChanged(exp, el->direction, eDIRECTION_BOTH, act);

                el->direction = eDIRECTION_BOTH;

                return;
            }
            else if (el->direction == direction && el->action == act)
                return;

            ++it;
        }
    }

    el = new IPFilterElem;

    el->mask      = exp_mask;
    el->ip        = exp_ip;
    el->direction = direction;
    el->action    = act;

#ifdef _DEBUG_IPFILTER_
    qDebug() << "\tCreated new element:";
    printf("\t\tMASK: 0x%x\n"
           "\t\tIP  : %s\n"
           "\t\tD   : %i\n"
           "\t\tA   : %i\n",
           el->mask, DCIPFilter::Uint32ToString(el->ip).toAscii().constData(),
           (int)el->direction, (int)el->action
          );
#endif

    list_ip.insert(el->ip, el);
    rules.push_back(el);

    emit ruleAdded(exp, direction);
}

void DCIPFilter::remFromRules(QString exp, eTableAction act) {

    QString str_ip;
    quint32 exp_ip;

    if (exp.indexOf("/") > 0)
        str_ip = exp.left(exp.indexOf("/"));

    if (!isIP(str_ip))
        return;

    exp_ip = DCIPFilter::StringToUint32(str_ip);

    if (!list_ip.contains(exp_ip))
        return;

    QIPHash::const_iterator it = list_ip.find(exp_ip);
    IPFilterElem *el;

    while (it != list_ip.end() && it.key() == exp_ip){
        el = it.value();

        if (!el)//i know that it impossible..
            return;

        if (el->action == act){
            list_ip.remove(exp_ip, el);

            if (rules.contains(el))
                rules.removeAt(rules.indexOf(el));

            emit ruleRemoved(exp, el->direction, el->action);

            delete el;

            return;
        }

        ++ it;
    }
}

bool DCIPFilter::OK(const QString &exp, eDIRECTION direction){
#ifdef _DEBUG_IPFILTER_
    qDebug() << "DCIPFilter::OK(" << exp.toAscii().constData() << ", " << (int)direction << ")";
#endif
    QString str_src(exp);

    if (str_src.indexOf(":") > 0)
        str_src = str_src.left(exp.indexOf(":")); //XXX.XXX.XXX.XXX:PORT -> XXX.XXX.XXX.XXX

    if (!isIP(str_src))
        return false;

    quint32 src = DCIPFilter::StringToUint32(str_src);
    IPFilterElem *el;

    for (int i = 0; i < rules.size(); i++){
        el = rules.at(i);

#ifdef _DEBUG_IPFILTER_
    printf("\tel->ip & el->mask == %x\n"
           "\tsrc    & el->mask == %x\n",
           (el->ip & el->mask),
           (src    & el->mask)
          );
#endif

        if ((el->ip & el->mask) == (src & el->mask)){//Exact match
            bool exact_direction = ((el->direction == direction) || (el->direction == eDIRECTION_BOTH));
#ifdef _DEBUG_IPFILTER_
            printf("\tFound match... ");
#endif
            if      ((el->action == etaDROP) && exact_direction){
#ifdef _DEBUG_IPFILTER_
                printf("DROP.\n");
#endif
                return false;
            }
            else if ((el->action == etaACPT) && exact_direction){
#ifdef _DEBUG_IPFILTER_
                printf("ACEPT.\n");
#endif
                return true;
            }
#ifdef _DEBUG_IPFILTER_
            else
                printf("IGNORE.\n");
#endif
        }
    }

    return true;
}

bool DCIPFilter::isIP(QString &exp) {
    return (QRegExp("^(\\d{1,3}.){3,3}\\d{1,3}$").exactMatch(exp));
}

void DCIPFilter::loadList() {
    QFile file(QDir::homePath() + "/.eiskaltdc/ipfilter");

    if (!file.exists())
        return;

    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
        return;

    if (!list_ip.empty())
        clearRules();

    QTextStream in(&file);

    while (!in.atEnd()) {
        QString pattern = in.readLine();
        QString str_ip, str_mask = "";
        eDIRECTION direction = eDIRECTION_IN;

        pattern.replace("\n", "");
        pattern.replace(" ", "");

#ifdef _DEBUG_IPFILTER_
        qDebug() << pattern;
#endif

        if (pattern.indexOf("|D_IN|:") == 0){
            pattern = pattern.right(pattern.length() - 7);//delete "|D_IN|:"
        }
        else if (pattern.toUpper().indexOf("|D_OUT|:") == 0) {//delete "|D_OUT|:"
            pattern = pattern.right(pattern.length() - 8);
            direction = eDIRECTION_OUT;
        }
        else if (pattern.toUpper().indexOf("|D_BOTH|:") == 0) {//delete "|D_BOTH|:"
            pattern = pattern.right(pattern.length() - 9);
            direction = eDIRECTION_BOTH;
        } else
            continue;

#ifdef _DEBUG_IPFILTER_
        qDebug() << pattern;
#endif

        addToRules(pattern, direction);
    }

#ifdef _DEBUG_IPFILTER_
    qDebug() << rules;
#endif

    file.close();
}

void DCIPFilter::saveList(){
    QFile file(QDir::homePath() + "/.eiskaltdc/ipfilter");
    if (!file.isOpen() && !file.open(QIODevice::WriteOnly|QIODevice::Text))
        return;

    QTextStream out(&file);

    out << signature << "\n";

    out.flush();

    for (int i = 0; i < rules.size(); i++) {
        QString prefix = "|D_IN|:";
        IPFilterElem *el = rules.at(i);

        eDIRECTION direction = el->direction;
        eTableAction act = el->action;

        switch (direction) {
            case eDIRECTION_BOTH:
                prefix = "|D_BOTH|:";

                break;
            case eDIRECTION_OUT:
                prefix = "|D_OUT|:";

                break;
            default:
                break;
        }
        switch (act){
            case etaACPT:
                prefix += "";

                break;

            case etaDROP:
                prefix += "!";

                break;

            default:

                break;
        }

        out << prefix + DCIPFilter::Uint32ToString(el->ip) + "/" + QString().setNum(DCIPFilter::MaskToCIDR(el->mask)) + "\n";

        out.flush();
    }

    file.close();
}

void DCIPFilter::exportTo(QString path) {
    QFile file(QDir::homePath() + "/.eiskaltdc/ipfilter");
    QMessageBox msgBox;

    msgBox.setStandardButtons(QMessageBox::Ok);
    msgBox.setDefaultButton(QMessageBox::Ok);
    msgBox.setIcon(QMessageBox::Warning);

    saveList();

    if (!file.exists()) {
        msgBox.setText(tr("Nothing to export."));
        msgBox.exec();

        return;
    }

    QFile exp_file(path);

    if (exp_file.exists())
        exp_file.remove();

    if (!file.copy(path)) {
        msgBox.setText(tr("Unable to export settings."));
        msgBox.exec();

        return;
    }
}

void DCIPFilter::importFrom(QString path) {
    QFile file(path);
    QMessageBox msgBox;

    msgBox.setStandardButtons(QMessageBox::Ok);
    msgBox.setDefaultButton(QMessageBox::Ok);
    msgBox.setIcon(QMessageBox::Warning);

    if (!file.exists() || !file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        msgBox.setText(tr("Nothing to import."));
        msgBox.exec();
        file.close();

        return;
    }

    QTextStream in(&file);
    QString sign = in.readLine();

    sign.replace("\n", "");

    file.close();

    if (sign == signature) {
        QFile old_file(QDir::homePath() + "/.eiskaltdc/ipfilter");
        old_file.remove();

        file.copy(QDir::homePath() + "/.eiskaltdc/ipfilter");

        clearRules();

        loadList();
    } else {
        msgBox.setText(tr("Invalid signature."));
        msgBox.exec();
    }
}

void DCIPFilter::changeRuleDirection(QString exp, eDIRECTION direction, eTableAction act) {
    if (exp.indexOf("/") > 0)
        exp = exp.left(exp.indexOf("/"));

    if (!isIP(exp))
        return;

    quint32 exp_ip = DCIPFilter::StringToUint32(exp);
    QIPHash::const_iterator it = list_ip.find(exp_ip);

    while (it != list_ip.end() && it.key() == exp_ip){
        IPFilterElem *el = it.value();

        if (!el)
            return;

        if (el->action == act){
            emit ruleChanged(exp, el->direction, direction, act);

            el->direction = direction;

            return;
        }

        ++it;
    }
}

const QIPList &DCIPFilter::getRules() {
    return rules;
}

const QIPHash &DCIPFilter::getHash() {
    return list_ip;
}

void DCIPFilter::clearRules() {
    QIPHash::const_iterator it = list_ip.constBegin();

    while (it != list_ip.constEnd()){
        delete it.value();
    }

    list_ip.clear();
    rules.clear();

    emit ruleRemoved("*", eDIRECTION_BOTH, etaACPT);
}

#ifdef _DEBUG_

void DCIPFilter::printHash() {
}
#endif

