#include <node.h>
#include <uv.h>
#include "asyncobj.h"
#include "AWFastCheck.hpp"

namespace NAC 
{
	using v8::Function;
	using v8::FunctionCallbackInfo;
	using v8::FunctionTemplate;
	using v8::Isolate;
    using v8::Persistent;
	using v8::Local;
	using v8::Object;
	using v8::String;
    using v8::Number;
	using v8::Value;
    using v8::Array;
    
    enum CheckFlag
    {
        emText = 1,
        emFile = 2
    };
    
    void closeCallback(uv_handle_t* handle)
    {
        AsyncObj *asyncObj = (AsyncObj*)handle->data;
        delete asyncObj;
    }
    
    void asyncCallback(uv_async_t* handle)
    {
        AsyncObj *asyncObj = (AsyncObj*)handle->data;
        Isolate *isolate = asyncObj->isolate;
        v8::HandleScope handle_scope(isolate);
        v8::Context::Scope ctx_scope(isolate->GetCurrentContext());
        
        Local<Array> res = Array::New(isolate);
        size_t index = 0;
        for(auto & r : asyncObj->results)
        {
            Local<Object> item = Object::New(isolate);
            item->Set(Local<Value>(String::NewFromUtf8(isolate, "wrong")),
                      Local<Value>(String::NewFromUtf8(isolate, r.wrong.c_str())));
            item->Set(Local<Value>(String::NewFromUtf8(isolate, "right")),
                      Local<Value>(String::NewFromUtf8(isolate, r.right.c_str())));
            item->Set(Local<Value>(String::NewFromUtf8(isolate, "type")),
                      Local<Value>(String::NewFromUtf8(isolate, r.wrongType.c_str())));
            item->Set(Local<Value>(String::NewFromUtf8(isolate, "begin")),
                      Local<Value>(Number::New(isolate, r.begin)));
            item->Set(Local<Value>(String::NewFromUtf8(isolate, "end")),
                      Local<Value>(Number::New(isolate, r.end)));
            
            res->Set(index, item);
            index++;
        }

        Local<Value> argv = Local<Value>::Cast(res);
        asyncObj->jsCallbackObj.Get(asyncObj->isolate)->Call(v8::Null(asyncObj->isolate), 1, &argv);

        uv_close((uv_handle_t*)&asyncObj->mainAsync, closeCallback);
        
    }
    
    void asyncWorkThread(void* arg)
    {
        AsyncObj *asyncObj = (AsyncObj*)arg;

        if(asyncObj->flag == emText)
        {
            GetAWCheck().checkText(asyncObj->textOrPath.c_str(), asyncObj->results);
        }
        else if(asyncObj->flag == emFile)
        {
            GetAWCheck().checkTextFile(asyncObj->textOrPath.c_str(), asyncObj->results);
        }

        uv_async_send(&asyncObj->mainAsync);
    }

    void reset(const FunctionCallbackInfo<Value>& args)
    {
        Isolate* isolate = args.GetIsolate();
        if (args.Length() != 0)
        {
            isolate->ThrowException(v8::Exception::TypeError(String::NewFromUtf8(isolate,
                                                                                 "Wrong number of arguments")));
            return;
        }

        GetAWCheck().reset();
    }

	void addDictionary(const FunctionCallbackInfo<Value>& args) 
	{
        Isolate* isolate = args.GetIsolate();
        if (args.Length() < 1)
        {
            isolate->ThrowException(v8::Exception::TypeError(String::NewFromUtf8(isolate,
                                                                                 "Wrong number of arguments")));
            return;
        }
        
        if (!args[0]->IsString())
        {
            isolate->ThrowException(v8::Exception::TypeError(String::NewFromUtf8(isolate,
                                                                                 "Wrong arguments")));
        }
        
        v8::String::Utf8Value dicfile(args[0]->ToString());
        
        GetAWCheck().addDictionary(*dicfile);
	}

	void addCheckItem(const FunctionCallbackInfo<Value>& args) 
	{
        Isolate* isolate = args.GetIsolate();
        if (args.Length() < 3)
        {
            isolate->ThrowException(v8::Exception::TypeError(String::NewFromUtf8(isolate,
                                                                                 "Wrong number of arguments")));
            return;
        }
        
        if (!args[0]->IsString() || !args[1]->IsString() || !args[2]->IsString())
        {
            isolate->ThrowException(v8::Exception::TypeError(String::NewFromUtf8(isolate,
                                                                                 "Wrong arguments")));
        }
        
        v8::String::Utf8Value wrong(args[0]->ToString());
        v8::String::Utf8Value right(args[1]->ToString());
        v8::String::Utf8Value wrongType(args[2]->ToString());
        
        GetAWCheck().addCheckItem(*wrong, *right, *wrongType);
	}

	void buildChecker(const FunctionCallbackInfo<Value>& args) 
	{
        GetAWCheck().buildChecker();
	}

	void checkText(const FunctionCallbackInfo<Value>& args) 
	{
        Isolate* isolate = args.GetIsolate();
        if (args.Length() < 2)
        {
            isolate->ThrowException(v8::Exception::TypeError(String::NewFromUtf8(isolate,
                                                                                 "Wrong number of arguments")));
            return;
        }
        
        if (!args[0]->IsString() || !args[1]->IsFunction())
        {
            isolate->ThrowException(v8::Exception::TypeError(String::NewFromUtf8(isolate,
                                                                                 "Wrong arguments")));
        }
        
        AsyncObj *asyncObj = new AsyncObj();
        asyncObj->textOrPath = *String::Utf8Value(args[0]->ToString());
        asyncObj->jsCallbackObj.Reset(isolate, Local<Function>::Cast(args[1]));
        asyncObj->flag = emText;
        asyncObj->mainAsync.data = asyncObj;
        asyncObj->isolate = isolate;
        uv_async_init(uv_default_loop(), &asyncObj->mainAsync, asyncCallback);
        uv_thread_create(&asyncObj->workerThread, asyncWorkThread, asyncObj);
	}

	void checkTextFile(const FunctionCallbackInfo<Value>& args) 
	{
        Isolate* isolate = args.GetIsolate();
        if (args.Length() < 1)
        {
            isolate->ThrowException(v8::Exception::TypeError(String::NewFromUtf8(isolate,
                                                                                 "Wrong number of arguments")));
            return;
        }
        
        if (!args[0]->IsString())
        {
            isolate->ThrowException(v8::Exception::TypeError(String::NewFromUtf8(isolate,
                                                                                 "Wrong arguments")));
        }
        
        v8::EscapableHandleScope handle_scope(isolate);
        
        AsyncObj *asyncObj = new AsyncObj();
        asyncObj->textOrPath = *String::Utf8Value(args[0]->ToString());
        asyncObj->jsCallbackObj.Reset(isolate, Local<Function>::Cast(args[1]));
        asyncObj->flag = emFile;
        asyncObj->mainAsync.data = asyncObj;
        asyncObj->isolate = isolate;
        uv_async_init(uv_default_loop(), &asyncObj->mainAsync, asyncCallback);
        uv_thread_create(&asyncObj->workerThread, asyncWorkThread, asyncObj);
	}

	void Init(Local<Object> exports, Local<Object> module) {
        NODE_SET_METHOD(exports, "reset", reset);
		NODE_SET_METHOD(exports, "addDictionary", addDictionary);
		NODE_SET_METHOD(exports, "addCheckItem", addCheckItem);
		NODE_SET_METHOD(exports, "buildChecker", buildChecker);
		NODE_SET_METHOD(exports, "checkText", checkText);
		NODE_SET_METHOD(exports, "checkTextFile", checkTextFile);
	}

	NODE_MODULE(nodeac, Init)
}