﻿#include <iostream>
#include <locale>

#include <windows.h>
#include <tchar.h>

#include <intrin.h>
#include <ActivScp.h>
#include <boost/implicit_cast.hpp>

#define __IActiveScriptParse_INTERFACE_DEFINED__
#include <comdef.h>

_COM_SMARTPTR_TYPEDEF(IActiveScriptProperty, __uuidof (IActiveScriptProperty));

#define ENSURE_SUCCEEDED(hr) do {if (FAILED(hr)) _com_raise_error(hr);} while (false)

using boost::implicit_cast;

class STAThread
{
public:
	STAThread()
	{
		ENSURE_SUCCEEDED(CoInitialize(nullptr));
	}

	~STAThread()
	{
		CoUninitialize();
	}
private:
	STAThread(STAThread const&) /* = delete */;
	STAThread& operator =(STAThread const&) /* = delete */;
};

class ScriptSite : public IActiveScriptSite
{
	LONG refCount;
public:
	// ここではカウント0なので、newしたら即座に参照カウントを増やすこと
	ScriptSite() : refCount() {}

	virtual ULONG STDMETHODCALLTYPE AddRef() override
	{
		_InterlockedIncrement(&refCount);
		return refCount;
	}

	virtual ULONG STDMETHODCALLTYPE Release() override
	{
		auto old = _InterlockedDecrement(&refCount);
		if (old == 0)
		{
			delete this;
		}
		return old;
	}

	virtual HRESULT STDMETHODCALLTYPE QueryInterface(
		/* [in] */ REFIID riid,
		/* [iid_is][out] */ __RPC__deref_out void __RPC_FAR *__RPC_FAR *ppvObject) override
	{
		if (ppvObject == nullptr) return E_POINTER;

		if (riid == __uuidof (IUnknown))
		{
			*ppvObject = implicit_cast<IUnknown*>(this);
		}
		else if (riid == __uuidof (IActiveScriptSite))
		{
			*ppvObject = implicit_cast<IActiveScriptSite*>(this);
		}
		else
		{
			return E_NOINTERFACE;
		}
		AddRef();
		return S_OK;
	}

	virtual HRESULT STDMETHODCALLTYPE GetLCID(
		/* [out] */ __RPC__out LCID* plcid) override
	{
		if (plcid == nullptr) return E_POINTER;

		*plcid = LOCALE_USER_DEFAULT;
		return S_OK;
	}

	virtual HRESULT STDMETHODCALLTYPE GetItemInfo(
		/* [in] */ __RPC__in LPCOLESTR pstrName,
		/* [in] */ DWORD dwReturnMask,
		/* [out] */ __RPC__deref_out_opt IUnknown **ppiunkItem,
		/* [out] */ __RPC__deref_out_opt ITypeInfo **ppti) override
	{
		if (ppiunkItem != nullptr)
		{
			*ppiunkItem = nullptr;
		}
		if (ppti != nullptr)
		{
			*ppti = nullptr;
		}
		if (ppiunkItem == nullptr) return E_POINTER;
		if (ppti == nullptr) return E_POINTER;

		return TYPE_E_ELEMENTNOTFOUND;
	}

	virtual HRESULT STDMETHODCALLTYPE GetDocVersionString(
		/* [out] */ __RPC__deref_out_opt BSTR *pbstrVersion) override
	{
		if (pbstrVersion == nullptr) return E_POINTER;
		*pbstrVersion = nullptr;
		return E_NOTIMPL;
	}

	virtual HRESULT STDMETHODCALLTYPE OnScriptTerminate(
		/* [in] */ __RPC__in const VARIANT *pvarResult,
		/* [in] */ __RPC__in const EXCEPINFO *pexcepinfo) override
	{
		return S_OK;
	}

	virtual HRESULT STDMETHODCALLTYPE OnStateChange(
		/* [in] */ SCRIPTSTATE ssScriptState) override
	{
		return S_OK;
	}

	virtual HRESULT STDMETHODCALLTYPE OnScriptError(
		/* [in] */ __RPC__in_opt IActiveScriptError *pscripterror)
	{
		// JScript 5.7以下の場合、JSONオブジェクトが無いので、
		// エラーになってここにやってくる。
		EXCEPINFO ei = {};
		auto hr = pscripterror->GetExceptionInfo(&ei);
		if (FAILED(hr))
		{
			return hr;
		}
		std::wcerr << ei.bstrDescription << std::endl;
		return S_OK;
	}

	virtual HRESULT STDMETHODCALLTYPE OnEnterScript() override
	{
		return S_OK;
	}

	virtual HRESULT STDMETHODCALLTYPE OnLeaveScript() override
	{
		PostQuitMessage(0);
		return S_OK;
	}

private:
	ScriptSite(ScriptSite const&) /* = delete */;
	ScriptSite& operator =(ScriptSite const&) /* = delete */;
};

const WCHAR JavascirptSource[] = L"JSON.parse('{}');";

int _tmain(int argc, _TCHAR* argv[])
{
	try
	{
		std::locale::global(std::locale(""));

		STAThread staThread;
		IActiveScriptPtr js(L"JScript");

		IActiveScriptSitePtr site(new ScriptSite);
		ENSURE_SUCCEEDED(js->SetScriptSite(site));
		IActiveScriptParsePtr parse(js);
		IActiveScriptPropertyPtr jsProperty(js);

		// http://msdn.microsoft.com/en-us/library/cc512774(VS.94).aspx
		_variant_t v(implicit_cast<long>(SCRIPTLANGUAGEVERSION_5_8), VT_I4); // ATL::CComVarinatでも可。

		// JScript 5.8の指定。
		// 0この行をコメントアウトすると、JScript 5.7モードで動く。
		ENSURE_SUCCEEDED(jsProperty->SetProperty(SCRIPTPROP_INVOKEVERSIONING, nullptr, &v));

		ENSURE_SUCCEEDED(parse->InitNew());
		EXCEPINFO ei = {};
		ENSURE_SUCCEEDED(parse->ParseScriptText(JavascirptSource, nullptr, nullptr, nullptr, 0, 0, SCRIPTTEXT_ISPERSISTENT, nullptr, &ei));

		ENSURE_SUCCEEDED(js->SetScriptState(SCRIPTSTATE_CONNECTED));

		MSG msg;
		for (;;)
		{
			auto ret = GetMessage(&msg, nullptr, 0, 0);
			if (ret == -1) return -1;
			if (ret == 0) return static_cast<int>(msg.wParam);
		}
	}
	catch (_com_error const& e)
	{
		std::cout << "CAtlException: " << std::hex << e.Error() << std::endl;
	}
	catch (...)
	{
		std::cout << "Unknown exception" << std::endl;
		return -1;
	}
}

