// Copyright 2008 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//     * Neither the name of Google Inc. nor the names of its
//       contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// Copyright (c) 2008 Mocchi, All rights reserved.
// oekakilib用にカスタマイズ
// ＋いろいろ削ったり並べ替えたり・・・

#include <v8.h>
#include "oekakilib.h"

#include <string>
#include <iostream>

void ReportException(v8::TryCatch* try_catch) {
	v8::HandleScope handle_scope;
	v8::String::Utf8Value exception(try_catch->Exception());
	v8::Handle<v8::Message> message = try_catch->Message();
	if (message.IsEmpty()) {
		// V8 didn't provide any extra information about this error; just
		// print the exception.
		printf("%s\n", *exception);
	} else {
		// Print (filename):(line number): (message).
		v8::String::Utf8Value filename(message->GetScriptResourceName());
		int linenum = message->GetLineNumber();
		printf("%s:%i: %s\n", *filename, linenum, *exception);
		// Print line of source code.
		v8::String::Utf8Value sourceline(message->GetSourceLine());
		printf("%s\n", *sourceline);
		// Print wavy underline (GetUnderline is deprecated).
		int start = message->GetStartColumn();
		for (int i = 0; i < start; i++) {
			printf(" ");
		}
		int end = message->GetEndColumn();
		for (int i = start; i < end; i++) {
			printf("^");
		}
		printf("\n");
	}
}

v8::Handle<v8::Value> Version(const v8::Arguments& args) {
  return v8::String::New(v8::V8::GetVersion());
}


// Reads a file into a v8 string.
v8::Handle<v8::String> ReadFile(const char* name) {
  FILE* file = fopen(name, "rb");
  if (file == NULL) return v8::Handle<v8::String>();

  fseek(file, 0, SEEK_END);
  int size = ftell(file);
  rewind(file);

  char* chars = new char[size + 1];
  chars[size] = '\0';
  for (int i = 0; i < size;) {
    int read = fread(&chars[i], 1, size - i, file);
    i += read;
  }
  fclose(file);
  v8::Handle<v8::String> result = v8::String::New(chars, size);
  delete[] chars;
  return result;
}

// Executes a string within the current v8 context.
bool ExecuteString(v8::Handle<v8::String> source,
                   v8::Handle<v8::Value> name,
                   bool print_result,
                   bool report_exceptions) {
	v8::HandleScope handle_scope;
	v8::TryCatch try_catch;
	v8::Handle<v8::Script> script = v8::Script::Compile(source, name);
	if (script.IsEmpty()) {
		// Print errors that happened during compilation.
		if (report_exceptions)
			ReportException(&try_catch);
		return false;
	} else {
		v8::Handle<v8::Value> result = script->Run();
		if (result.IsEmpty()) {
			// Print errors that happened during execution.
			if (report_exceptions)
				ReportException(&try_catch);
			return false;
		} else {
			if (print_result && !result->IsUndefined()) {
				// If all went well and the result wasn't undefined then print
				// the returned value.
				v8::String::Utf8Value str(result);
				printf("%s\n", *str);
			}
			return true;
		}
	}
}

// The callback that is invoked by v8 whenever the JavaScript 'load'
// function is called.  Loads, compiles and executes its argument
// JavaScript file.
v8::Handle<v8::Value> Load(const v8::Arguments& args) {
  for (int i = 0; i < args.Length(); i++) {
    v8::HandleScope handle_scope;
    v8::String::Utf8Value file(args[i]);
    v8::Handle<v8::String> source = ReadFile(*file);
    if (source.IsEmpty()) {
      return v8::ThrowException(v8::String::New("Error loading file"));
    }
    if (!ExecuteString(source, v8::String::New(*file), false, false)) {
      return v8::ThrowException(v8::String::New("Error executing  file"));
    }
  }
  return v8::Undefined();
}

// The read-eval-execute loop of the shell.
void RunShell(v8::Handle<v8::Context> context) {
	printf("V8 version %s\n", v8::V8::GetVersion());
	static const int kBufferSize = 256;
	while (true) {
		char buffer[kBufferSize];
		printf("> ");
		char* str = fgets(buffer, kBufferSize, stdin);
		if (str == NULL) break;
		v8::HandleScope handle_scope;
		ExecuteString(v8::String::New(str),
			v8::String::New("(shell)"),
			true,
			true);
	}
	printf("\n");
}

v8::Handle<v8::Value> Print(const v8::Arguments& args) {
	v8::String::AsciiValue str(args[0]);
	printf("%s\n",*str);
	return v8::Undefined();
}

v8::Handle<v8::Value> Quit(const v8::Arguments& args) {
	// If not arguments are given args[0] will yield undefined which
	// converts to the integer value 0.
	int exit_code = args[0]->Int32Value();
	exit(exit_code);
	return v8::Undefined();
}

namespace Oekaki{
	// oekakilibの関数を呼び出すJavascriptの関数群
	v8::Handle<v8::Value> Flush(const v8::Arguments& args){
		flush();
		return v8::Undefined();
	}
	v8::Handle<v8::Value> Screen(const v8::Arguments& args){
		if (args.Length() < 2) return v8::ThrowException(v8::String::New("Error : this function requires 2 arguments(width,height)>"));
		if (!args[0]->IsInt32() || !args[1]->IsInt32()) return v8::ThrowException(v8::String::New("the arguments should be integer."));

		return v8::Integer::New(screen(args[0]->Int32Value(), args[1]->Int32Value()));
	}
	template <typename void func(int, int, int, int, unsigned char, unsigned char, unsigned char)>
	v8::Handle<v8::Value> func7(const v8::Arguments& args){
		if (args.Length() < 7) return v8::ThrowException(v8::String::New("Error : this function requires 7 arguments(x1,y1,x2,y2,r,g,b)."));
		for (int i = 0; i < 4; ++i){
			if (!args[i]->IsInt32()) return v8::ThrowException(v8::String::New("Error : the arguments (x1,y1,x2,y2) should be integer."));
		}
		int rgb[3];
		for (int i = 4; i < 7; ++i){
			if (args[i]->IsInt32()){
				rgb[i-4] = args[i]->Int32Value();
				if (rgb[i-4] >= 0 && rgb[i-4] < 256) continue; 
			}
			return v8::ThrowException(v8::String::New("Error : the arguments (r,g,b) should be integer from 0 to 255. "));
		}
		func(args[0]->Int32Value(), args[1]->Int32Value(), args[2]->Int32Value(), args[3]->Int32Value(),
			static_cast<unsigned char>(rgb[0]), static_cast<unsigned char>(rgb[1]), static_cast<unsigned char>(rgb[2]));
		return v8::Undefined();
	}
	v8::Handle<v8::Value> Line(const v8::Arguments& args){
		return func7<line>(args);
	}
	v8::Handle<v8::Value> Point(const v8::Arguments& args){
		if (args.Length() < 5) return v8::ThrowException(v8::String::New("Error : this function requires 7 arguments(x,y,r,g,b)."));
		for (int i = 0; i < 2; ++i){
			if (!args[i]->IsInt32()) return v8::ThrowException(v8::String::New("Error : the arguments (x,y) should be integer."));
		}
		int rgb[3];
		for (int i = 2; i < 5; ++i){
			if (args[i]->IsInt32()){
				rgb[i-2] = args[i]->Int32Value();
				if (rgb[i-2] >= 0 && rgb[i-2] < 256) continue; 
			}
			return v8::ThrowException(v8::String::New("Error : the arguments (r,g,b) should be integer from 0 to 255."));
		}
		point(args[0]->Int32Value(), args[1]->Int32Value(), 
			static_cast<unsigned char>(rgb[0]), static_cast<unsigned char>(rgb[1]), static_cast<unsigned char>(rgb[2]));
		return v8::Undefined();
	}
	v8::Handle<v8::Value> FillRect(const v8::Arguments& args){
		return func7<fillrect>(args);
	}
	v8::Handle<v8::Value> Rect(const v8::Arguments& args){
		return func7<rect>(args);
	}
	v8::Handle<v8::Value> Delay(const v8::Arguments& args){
		if (args.Length() < 1) return v8::ThrowException(v8::String::New("Error : this function requires an argument."));
		if (!args[0]->IsInt32()) return v8::ThrowException(v8::String::New("Error : the argument should be integer."));
		delay(args[0]->Int32Value());
		return v8::Undefined();
	}
	v8::Handle<v8::Value> Print_Version(const v8::Arguments& args){
		print_version();
		return v8::Undefined();
	}
	v8::Handle<v8::Value> Mouse_State(const v8::Arguments& args){
		return v8::Integer::New(mouse_state());
	}
	v8::Handle<v8::Value> Mouse_X(const v8::Arguments& args){
		return v8::Integer::New(mouse_x());
	}
	v8::Handle<v8::Value> Mouse_Y(const v8::Arguments& args){
		return v8::Integer::New(mouse_y());
	}
}

int main(int argc, char* argv[]) {

	v8::HandleScope handle_scope;
	v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New();

	// globalオブジェクトへ関数を登録
	{
		global->Set(v8::String::New("print"), v8::FunctionTemplate::New(Print));
		global->Set(v8::String::New("load"), v8::FunctionTemplate::New(Load));
		global->Set(v8::String::New("version"), v8::FunctionTemplate::New(Version));
		global->Set(v8::String::New("quit"), v8::FunctionTemplate::New(Quit));
	}

	// oekakilibオブジェクトの作成、登録
	{
		using namespace Oekaki;
		v8::Handle<v8::ObjectTemplate> oekaki = v8::ObjectTemplate::New();
		global->Set(v8::String::New("Oekaki"), oekaki);
		oekaki->Set(v8::String::New("flush"), v8::FunctionTemplate::New(Flush));
		oekaki->Set(v8::String::New("screen"), v8::FunctionTemplate::New(Screen));
		oekaki->Set(v8::String::New("line"), v8::FunctionTemplate::New(Line));
		oekaki->Set(v8::String::New("point"), v8::FunctionTemplate::New(Point));
		oekaki->Set(v8::String::New("fillrect"), v8::FunctionTemplate::New(FillRect));
		oekaki->Set(v8::String::New("rect"), v8::FunctionTemplate::New(Rect));
		oekaki->Set(v8::String::New("delay"), v8::FunctionTemplate::New(Delay));
		oekaki->Set(v8::String::New("print_version"), v8::FunctionTemplate::New(Print_Version));
		oekaki->Set(v8::String::New("mouse_state"), v8::FunctionTemplate::New(Mouse_State));
		oekaki->Set(v8::String::New("mouse_x"), v8::FunctionTemplate::New(Mouse_X));
		oekaki->Set(v8::String::New("mouse_y"), v8::FunctionTemplate::New(Mouse_Y));
	}

	v8::Handle<v8::Context> context = v8::Context::New(NULL, global);
	v8::Context::Scope context_scope(context);

	RunShell(context);

	// 終了
	return 0;
}

