/*
 *  Baltisot
 *  Copyright (C) 1999-2008 Nicolas "Pixel" Noble
 *
 *  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 2 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, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "Domain.h"
#include "LuaHttp.h"
#include "LuaHandle.h"
#include "LuaTask.h"
#include "Base64.h"
#include "HashFunction.h"
#include "RandISAAC.h"

class LuaDomain : public Domain {
  public:
      LuaDomain(Lua * _L, String r);
      virtual ~LuaDomain();
    virtual void Do(const HttpRequest & req, HttpResponse * res) throw (GeneralException);
  private:
    Lua * L;
    static int max_id;
    int id;
};

class LuaLuaDomain : public LuaObject {
  public:
    static void pushstatics(Lua *) throw (GeneralException);
      LuaLuaDomain(LuaDomain *);
  protected:
    virtual void pushmembers(Lua *);
    LuaDomain * d;
};

LuaLuaDomain::LuaLuaDomain(LuaDomain * _d) : d(_d) { }

enum LuaDomain_methods_t {
    LUADOMAIN_ONTOP = 0,
    LUADOMAIN_GETPATTERN,
};

enum LuaDomain_functions_t {
    LUADOMAIN_NEWDOMAIN = 0,
    LUADOMAIN_IDOMAIN,
    LUADOMAIN_NDOMAIN,
};

struct lua_functypes_t LuaDomain_methods[] = {
    { LUADOMAIN_ONTOP,	    "OnTop",      0, 0, { } },
    { LUADOMAIN_GETPATTERN, "GetPattern", 0, 0, { } },
    { -1, 0, 0, 0, 0 },
};

struct lua_functypes_t LuaDomain_functions[] = {
    { LUADOMAIN_NEWDOMAIN,	"Domain",	2, 2, { BLUA_STRING, BLUA_FUNCTION } },
    { LUADOMAIN_IDOMAIN,        "iDomain",      0, 0, { } },
    { LUADOMAIN_NDOMAIN,        "nDomain",      2, 2, { BLUA_USERDATA, BLUA_STRING | BLUA_NIL } },
    { -1, 0, 0, 0, 0 },
};

class sLua_LuaDomain : public Base {
  public:
    DECLARE_METHOD(LuaDomain, LUADOMAIN_ONTOP);
    DECLARE_METHOD(LuaDomain, LUADOMAIN_GETPATTERN);
    
    DECLARE_FUNCTION(LuaDomain, LUADOMAIN_NEWDOMAIN);
    DECLARE_FUNCTION(LuaDomain, LUADOMAIN_IDOMAIN);
    DECLARE_FUNCTION(LuaDomain, LUADOMAIN_NDOMAIN);

  private:
    static int LuaDomain_proceed(Lua * L, int n, LuaDomain * obj, int caller);
    static int LuaDomain_proceed_statics(Lua * L, int n, int caller);
};

void LuaLuaDomain::pushmembers(Lua * L) {
    pushme(L, d, "LuaDomain");
    
    PUSH_METHOD(LuaDomain, LUADOMAIN_ONTOP);
    PUSH_METHOD(LuaDomain, LUADOMAIN_GETPATTERN);
}

void LuaLuaDomain::pushstatics(Lua * L) throw (GeneralException) {
    CHECK_METHODS(LuaDomain);
    CHECK_FUNCTIONS(LuaDomain);
    
    PUSH_FUNCTION(LuaDomain, LUADOMAIN_NEWDOMAIN);
    PUSH_FUNCTION(LuaDomain, LUADOMAIN_IDOMAIN);
    PUSH_FUNCTION(LuaDomain, LUADOMAIN_NDOMAIN);
}

#define DOMAIN_REGISTRY "DOMAINS_KEYS"

#define export_enum(L, n) \
  L->push(#n); \
  L->push((lua_Number) HttpResponse::n); \
  L->settable(LUA_GLOBALSINDEX);

LuaHttpResponse::LuaHttpResponse(HttpResponse * _r) : r(_r) { }

enum HttpResponse_method_t {
    HTTPRESPONSE_INDEX = 0,
    HTTPRESPONSE_NEWINDEX,
};

enum HttpResponse_functions_t {
    HTTPRESPONSE_NEWHTTPRESPONSE = 0,
    HTTPRESPONSE_BASE64_ENCODE,
    HTTPRESPONSE_BASE64_DECODE,
    HTTPRESPONSE_BASE64_DECODE_BIN,
    HTTPRESPONSE_MD5,
    HTTPRESPONSE_SHA1,
    HTTPRESPONSE_SHA256,
    HTTPRESPONSE_GENTICKET,
};

struct lua_functypes_t HttpResponse_methods[] = {
    { HTTPRESPONSE_INDEX,    "index",    1, 1, { BLUA_STRING } },
    { HTTPRESPONSE_NEWINDEX, "newindex", 2, 2, { BLUA_STRING, BLUA_ANY } },
    { -1, 0, 0, 0, 0 }
};

struct lua_functypes_t HttpResponse_functions[] = {
    { HTTPRESPONSE_NEWHTTPRESPONSE,   "HttpResponse",    0, 0, { } },
    { HTTPRESPONSE_BASE64_ENCODE,     "Base64Encode",    1, 1, { BLUA_STRING | BLUA_OBJECT } },
    { HTTPRESPONSE_BASE64_DECODE,     "Base64Decode",    1, 1, { BLUA_STRING } },
    { HTTPRESPONSE_BASE64_DECODE_BIN, "Base64DecodeBin", 2, 2, { BLUA_STRING, BLUA_OBJECT } },
    { HTTPRESPONSE_MD5,               "MD5",             1, 1, { BLUA_STRING | BLUA_OBJECT } },
    { HTTPRESPONSE_SHA1,              "SHA1",            1, 1, { BLUA_STRING | BLUA_OBJECT } },
    { HTTPRESPONSE_SHA256,            "SHA256",          1, 1, { BLUA_STRING | BLUA_OBJECT } },
    { HTTPRESPONSE_GENTICKET,         "GenTicket",       0, 1, { BLUA_NUMBER } },
    { -1, 0, 0, 0, 0 }
};

class sLua_HttpResponse : public Base {
  public:
    DECLARE_METHOD(HttpResponse, HTTPRESPONSE_INDEX);
    DECLARE_METHOD(HttpResponse, HTTPRESPONSE_NEWINDEX);
    
    DECLARE_FUNCTION(HttpResponse, HTTPRESPONSE_NEWHTTPRESPONSE);
    DECLARE_FUNCTION(HttpResponse, HTTPRESPONSE_BASE64_ENCODE);
    DECLARE_FUNCTION(HttpResponse, HTTPRESPONSE_BASE64_DECODE);
    DECLARE_FUNCTION(HttpResponse, HTTPRESPONSE_BASE64_DECODE_BIN);
    DECLARE_FUNCTION(HttpResponse, HTTPRESPONSE_MD5);
    DECLARE_FUNCTION(HttpResponse, HTTPRESPONSE_SHA1);
    DECLARE_FUNCTION(HttpResponse, HTTPRESPONSE_SHA256);
    DECLARE_FUNCTION(HttpResponse, HTTPRESPONSE_GENTICKET);
  private:
    static int HttpResponse_proceed(Lua * L, int n, HttpResponse * obj, int caller);
    static int HttpResponse_proceed_statics(Lua * L, int n, int caller);
};

void LuaHttpResponse::pushmembers(Lua * L) {
    pushme(L, r, "HttpResponse");
    
    PUSH_METAMETHOD(HttpResponse, HTTPRESPONSE_INDEX);
    PUSH_METAMETHOD(HttpResponse, HTTPRESPONSE_NEWINDEX);
}

void LuaHttpResponse::pushstatics(Lua * L) throw (GeneralException) {
    CHECK_METHODS(HttpResponse);
    CHECK_FUNCTIONS(HttpResponse);
    
    PUSH_FUNCTION(HttpResponse, HTTPRESPONSE_NEWHTTPRESPONSE);
    PUSH_FUNCTION(HttpResponse, HTTPRESPONSE_BASE64_ENCODE);
    PUSH_FUNCTION(HttpResponse, HTTPRESPONSE_BASE64_DECODE);
    PUSH_FUNCTION(HttpResponse, HTTPRESPONSE_BASE64_DECODE_BIN);
    PUSH_FUNCTION(HttpResponse, HTTPRESPONSE_MD5);
    PUSH_FUNCTION(HttpResponse, HTTPRESPONSE_SHA1);
    PUSH_FUNCTION(HttpResponse, HTTPRESPONSE_SHA256);
    PUSH_FUNCTION(HttpResponse, HTTPRESPONSE_GENTICKET);
    
    export_enum(L, HTTP_200_OK);
    export_enum(L, HTTP_301_PERM_MOVED);
    export_enum(L, HTTP_302_FOUND);
    export_enum(L, HTTP_400_BAD_REQUEST);
    export_enum(L, HTTP_401_UNAUTHORIZED);
    export_enum(L, HTTP_403_FORBIDDEN);
    export_enum(L, HTTP_404_NOT_FOUND);
    export_enum(L, HTTP_500_INTERNAL_ERROR);
    export_enum(L, HTTP_503_SERVICE_UNAVAILABLE);
    
    L->push(DOMAIN_REGISTRY);
    L->newtable();
    L->settable(LUA_REGISTRYINDEX);
    
    LuaLuaDomain::pushstatics(L);
}

int sLua_HttpResponse::HttpResponse_proceed(Lua * L, int n, HttpResponse * res, int caller) {
    String i;
    int r = 0;
    
    switch (caller) {
    case HTTPRESPONSE_INDEX:
        i = L->tostring(2);
        if (i == "mime_type") {
            L->push(res->mime_type);
        } else if (i == "location") {
            L->push(res->location);
        } else if (i == "domain") {
            L->push(res->domain);
        } else if (i == "file_name") {
            L->push(res->file_name);
        } else if (i == "server_name") {
            L->push(res->server_name);
        } else if (i == "return_code") {
            L->push((lua_Number) res->return_code);
        } else if (i == "last_modified") {
            L->push((lua_Number) res->last_modified);
        } else if (i == "contents") {
            {
                LuaBuffer b(&res->contents);
                b.push(L);
            }
        } else if (i == "cache") {
            L->push(res->cache);
        } else if (i == "cookies_path") {
            L->push(res->cookies_path);
        } else if (i == "cookies") {
            L->error("Can't read cookies (for now)");
        } else {
            L->error("Unknow field in HttpResponse object: " + i);
        }
        r = 1;
        break;
    case HTTPRESPONSE_NEWINDEX:
        i = L->tostring(2);
        if (i == "mime_type") {
            res->mime_type = L->tostring(3);
        } else if (i == "location") {
            res->location = L->tostring(3);
        } else if (i == "domain") {
            res->domain = L->tostring(3);
        } else if (i == "file_name") {
            res->file_name = L->tostring(3);
        } else if (i == "server_name") {
            res->server_name = L->tostring(3);
        } else if (i == "return_code") {
            res->return_code = (HttpResponse::return_code_t)(int)L->tonumber(3);
        } else if (i == "last_modified") {
            res->last_modified = L->tonumber(3);
        } else if (i == "contents") {
            L->error("Can't alter field buffer in HttpResponse.");
        } else if (i == "cache") {
            res->cache = L->toboolean(3);
        } else if (i == "cookies_path") {
            res->cookies_path = L->tostring(3);
        } else if (i == "cookies") {
            String cookie = L->tostring(3), key, value;
            int pos = cookie.strchr('=');
            if (pos < 0) {
                L->error("Malformed cookie.");
            }
            key = cookie.extract(0, pos - 1).trim();
            value = cookie.extract(pos + 1).trim();
            std::pair<String, String> e(key, value);
            res->cookies.push_back(e);
        } else {
            L->error("Unknow field in HttpResponse object: " + i);
        }
        break;
    }
    
    return r;
}

Random rand_gen;

static const char ticket_chars[] =
    "Q52VHu0JfkgQP2IjCpWIOldzwiGU1Ery49H7Z6tgYB8UpGuM4P@rFrBL_qvf3utE"
    "dJRaXT8WVcvD6JSjcTNW9mqbnNRcoAlh7naMZJqDBRRgs@Tep_2p3@vcw1Bk7hex"
    "D2Y_syYIWUbFyfXAiSN9xXn15evI4zXM9r1VHKbkAKfs8POd0nQ5Ch4adqumOhig"
    "SlVTmQFC0AGMFY33ZENE6@CjlKatLox6xZ07jw5LePGy_LoHKbi8zStwOUkmoDsz";

int sLua_HttpResponse::HttpResponse_proceed_statics(Lua * L, int n, int caller) {
    int r = 0, l, i, size = 32;
    String enc, dec;
    char * enc_t;
    const char * dec_t;
    
    switch (caller) {
    case HTTPRESPONSE_NEWHTTPRESPONSE:
        {
            LuaHttpResponse r(new HttpResponse());
            r.pushdestruct(L);
        }
        r = 1;
        break;
    case HTTPRESPONSE_BASE64_ENCODE:
        if (L->isstring()) {
            dec = L->tostring();
            dec_t = dec.to_charp();
            L->push(Base64::encode(dec_t, dec.strlen()));
        } else {
            Handle * hdata = L->recast<Handle>(1)
            int size = hdata->GetSize();
            char * data = (char *) malloc(size);
            hdata->read(data, size);
            L->push(Base64::encode(data, size));
            free(data);
        }
        r = 1;
        break;
    case HTTPRESPONSE_BASE64_DECODE:
        enc_t = (char *) Base64::decode(L->tostring(), &l);
        enc_t[l] = 0;
        L->push(enc_t, l);
        free(enc_t);
        r = 1;
        break;
    case HTTPRESPONSE_BASE64_DECODE_BIN:
        {
            Handle * out = lua_recast<Handle>(L, 2);
            if (!out)
                L->error("Need an output handle to Base64DecodeBin");
            enc_t = (char *) Base64::decode(L->tostring(1), &l);
            out->write(enc_t, l);
            free(enc_t);
        }
        break;
    case HTTPRESPONSE_MD5:
        if (L->isstring()) {
            dec = L->tostring();
            MD5 h;
            h.Update(dec);
            L->push(h.Finish());
        } else {
            Handle * hdata = L->recast<Handle>(1)
            MD5 h;
            h.Update(hdata);
            L->push(h.Finish());
        }
        r = 1;
        break;
    case HTTPRESPONSE_SHA1:
        if (L->isstring()) {
            dec = L->tostring();
            SHA1 h;
            h.Update(dec);
            L->push(h.Finish());
        } else {
            Handle * hdata = L->recast<Handle>(1)
            SHA1 h;
            h.Update(hdata);
            L->push(h.Finish());
        }
        r = 1;
        break;
    case HTTPRESPONSE_SHA256:
        if (L->isstring()) {
            dec = L->tostring();
            SHA256 h;
            h.Update(dec);
            L->push(h.Finish());
        } else {
            Handle * hdata = L->recast<Handle>(1)
            SHA256 h;
            h.Update(hdata);
            L->push(h.Finish());
        }
        r = 1;
        break;
    case HTTPRESPONSE_GENTICKET:
        if (n == 1) 
            size = L->tonumber();
        enc_t = (char *) malloc(size + 1);
        for (i = 0; i < size; i++) {
            enc_t[i] = ticket_chars[rand_gen.get() % 256];
        }
        enc_t[size] = 0;
        L->push(String(enc_t));
        r = 1;
        break;
    }
    
    return r;
}

LuaDomain::LuaDomain(Lua * _L, String r) : Domain(r), L(_L->Father()) {
    id = max_id++; /***FIXME***/
          
    L->push(DOMAIN_REGISTRY);
    L->gettable(LUA_REGISTRYINDEX);
    L->push((lua_Number) id);
    L->copy(2);
    L->settable();
    L->pop();
}

LuaDomain::~LuaDomain() {
    L->push(DOMAIN_REGISTRY);
    L->gettable(LUA_REGISTRYINDEX);
    L->push((lua_Number) id);
    L->push();
    L->settable();
    L->pop();
}

void LuaDomain::Do(const HttpRequest & req, HttpResponse * res) throw (GeneralException) {
    int i, nmatches = 1;

    Lua * oldL = L;
    Lua * L = oldL->thread(true);
    oldL->pop();
    
    L->push(DOMAIN_REGISTRY);
    L->gettable(LUA_REGISTRYINDEX);
    L->push((lua_Number) id);
    L->gettable();
    L->remove();
    
    L->newtable();

    L->push("vars");
    L->newtable();
    for (i = 0; i < req.vars->GetNb(); i++) {
        char * v = (*(req.vars))[i].strdup(), * p;
        
        p = strchr(v, '=');
        if (!p) {
            L->push(v);
            L->push(true);
        } else {
            *p = 0;
            p++;
        
            L->push(v);
            L->push(p);
        }

        L->settable();        
        free(v);
    }
    L->settable();
    
    L->push("headers");
    L->newtable();
    for (i = 0; i < req.headers->GetNb(); i++) {
        char * v = (*(req.headers))[i].strdup(), * p;
        
        p = strchr(v, '=');
        if (!p) {
            free(v);
            continue;
        }
        *p = 0;
        p++;
        
        L->push(v);
        L->push(p);
        L->settable();
        
        free(v);
    }
    L->settable();

    L->push("method");
    L->push(req.method);
    L->settable();
    
    L->push("uri");
    L->push(req.uri);
    L->settable();
    
    L->push("login");
    L->push(req.login);
    L->settable();
    
    L->push("password");
    L->push(req.password);
    L->settable();

    L->push("lip");
    L->push((lua_Number) req.lip);
    L->settable();
    
    L->push("dip");
    L->push((lua_Number) req.dip);
    L->settable();
    
    L->push("lport");
    L->push((lua_Number) req.lport);
    L->settable();
    
    L->push("dport");
    L->push((lua_Number) req.dport);
    L->settable();

    L->push("matches");
    L->newtable();
    for (i = 1; i < MAX_MATCHES; i++) {
        if (req.pmatches[i].rm_so == -1)
            continue;
        L->push((lua_Number) nmatches++);
        L->newtable();
        L->push("start");
        L->push((lua_Number) req.pmatches[i].rm_so + 1);
        L->settable();
        L->push("size");
        L->push((lua_Number) req.pmatches[i].rm_eo - req.pmatches[i].rm_so);
        L->settable();
        L->settable();
    }
    L->settable();
    
    LuaHttpResponse r(res);
    r.push(L);
    
    res->builder = new LuaTask(L, 2, true);
}

int LuaDomain::max_id = 1;

int sLua_LuaDomain::LuaDomain_proceed(Lua * L, int n, LuaDomain * obj, int caller) {
    int r = 0;
    
    switch (caller) {
    case LUADOMAIN_ONTOP:
	obj->OnTop();
	break;
    case LUADOMAIN_GETPATTERN:
        L->push(obj->GetPattern());
        r = 1;
        break;
    }
    
    return r;
}

int sLua_LuaDomain::LuaDomain_proceed_statics(Lua * L, int n, int caller) {
    int r = 0;
    Domain ** p;
    
    switch (caller) {
    case LUADOMAIN_NEWDOMAIN:
	{
	    LuaLuaDomain lld(new LuaDomain(L, L->tostring(1)));
	    lld.pushdestruct(L);
	    r = 1;
	}
	break;
    case LUADOMAIN_IDOMAIN:
        L->getglobal("nDomain");
        p = (Domain **) L->newuser(sizeof(Domain *));
        *p = 0;
        L->push();
        r = 3;
        break;
    case LUADOMAIN_NDOMAIN:
        p = (Domain **) L->touserdata(1);
        if (*p) {
            *p = (*p)->Next();
        } else {
            *p = Domain::First();
        }
        if (*p) {
            L->push((*p)->GetPattern());
        } else {
            L->push();
        }
        r = 1;
        break;
    }
    
    return r;
}