/////////////////////////////////////////////////////////////////////////////
//
// ATL Active Script Host Wrapper
// (C) Copyright 2001 VisionTech Limited. All rights reserved.
// http://www.visiontech.ltd.uk/
// [email protected]
//
// VisionTech Limited makes no warranties, either express or implied,
// with respect to this source code and any accompanying materials.
//
// In no event shall VisionTech Limited or its suppliers be liable for
// any damages whatsoever (including, without limitation, damages for
// loss of business profits, business interruption, loss of business
// information, or other percuniary loss) arising out of the use or
// inability to use this software.
//
// This source code may be used for any purpose, including commercial
// applications, and may be modified or redistributed subject to the
// following conditions:
//
// a) This notice may not be removed or changed in any source distribution.
//
// b) Altered source versions must be include a note to that effect,
//    and must not be misrepresented as the original.
//
// c) The origin of this software may not be misrepresented - you may
//    not claim to have written the original version. If you use this
//    source in a product, an acknowledgement in the documentation
//    would be appreciated, but is not required.
//
/////////////////////////////////////////////////////////////////////////////


#ifndef INC_SCRIPTSITE_H
#define INC_SCRIPTSITE_H

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include <activscp.h>

// If you want a different application title, declare SCRIPTSITE_APPNAME in
// you code about the reference to ScriptSite.h
#ifndef SCRIPTSITE_APPNAME
#define SCRIPTSITE_APPNAME  _T("ScriptSite")
#endif

#ifndef HR
#define HR(_ex) { HRESULT _hr = _ex; if(FAILED(_hr)) return _hr; }
#endif

/////////////////////////////////////////////////////////////////////////////
// IActiveScriptSite

class ATL_NO_VTABLE IActiveScriptSiteImpl : public IActiveScriptSite
{
public:
    STDMETHOD(GetLCID)(LCID *plcid)
    {
        *plcid = LOCALE_SYSTEM_DEFAULT;
        return NOERROR;
    };

    STDMETHOD(GetItemInfo)(LPCOLESTR pstrName,DWORD dwMask,LPUNKNOWN* ppunkItem,LPTYPEINFO* ppTypeInfo)
    {
        CComPtr<IUnknown> spUnk;
        HR(LookupNamedItem(pstrName,&spUnk));

        if(dwMask & SCRIPTINFO_ITYPEINFO) {
            CComPtr<ITypeInfo> spTI;
            CComQIPtr<IProvideClassInfo> spPCI = spUnk;
            if(!!spPCI) {
                // Got IProvideClassInfo interface so use it
                HR(spPCI->GetClassInfo(&spTI));
                spPCI.Release();
            } else {
                // Try for IDispatch::GetTypeInfo
                CComQIPtr<IDispatch> spDisp = spUnk;
                if(!!spDisp) {
                    HR(spDisp->GetTypeInfo(0,LOCALE_SYSTEM_DEFAULT,&spTI));
                    spDisp.Release();
                }
            }
            *ppTypeInfo = spTI.Detach();
        }

        if(dwMask & SCRIPTINFO_IUNKNOWN) {
            *ppunkItem = spUnk.Detach();
        }

        return NOERROR;
    }

    STDMETHOD(GetDocVersionString)(BSTR *pbstrversionString)
    {
        if(pbstrversionString==NULL)
            return E_POINTER;
        USES_CONVERSION;
        *pbstrversionString = ::SysAllocString(T2OLE(SCRIPTSITE_APPNAME));
        return NOERROR;
    };

    // This method is called when the script engine terminates
    STDMETHOD(OnScriptTerminate)(const VARIANT* /*pvarResult*/,const EXCEPINFO* /*pexcepinfo*/)
    {
        return NOERROR;
    };

    // This method is called when the script engine's state is changed
    STDMETHOD(OnStateChange)(SCRIPTSTATE /*ssScriptState*/)
    {
        return NOERROR;
    };

    // This method is called when the script engine wants to report an error to the user
    STDMETHOD(OnScriptError)(IActiveScriptError* pase)
    {
        if(pase==NULL)
            return E_POINTER;
        EXCEPINFO ei;
        HR(pase->GetExceptionInfo(&ei));
        if(ei.pfnDeferredFillIn!=NULL) {
            HR((*ei.pfnDeferredFillIn)(&ei));
        }
        DWORD dwContext;
        ULONG ulLine;
        LONG lPos;
        HR(pase->GetSourcePosition(&dwContext,&ulLine,&lPos));
        CComBSTR src;
        HR(pase->GetSourceLineText(&src));
        return HandleScriptError(&ei,ulLine,lPos,src);
    }

    // This method is called when (before) the script engine starts executing the script/event handler
    STDMETHOD(OnEnterScript)(void)
    {
        return NOERROR;
    };

    // This method is called when (after) the script engine finishes executing the script/event handler
    STDMETHOD(OnLeaveScript)(void)
    {
        return NOERROR;
    };

    // This is an implementation method.
    // Override this method in your implementation and return the desired object
    // or TYPE_E_ELEMENTNOTFOUND if the name doesn't match one of yours
    // (You must call CScriptSiteImpl::AddObject in the first place to tell
    // the script engine that your objects exist).
    STDMETHOD(LookupNamedItem)(LPCOLESTR pstrName,LPUNKNOWN* ppunkItem)
    {
        return TYPE_E_ELEMENTNOTFOUND;
    }

    // This is an implementation method.
    // Override this method in your implementation to handle error messages
    STDMETHOD(HandleScriptError)(EXCEPINFO* pei,ULONG ulLine,LONG lPos,BSTR src)
    {
        TCHAR buf[1024];
        ::wsprintf(buf,"Script Error:\n\nSource: %ws\nError: %08X\nDescription: %ws\nLine: %d Column: %d\n\nCode:\n%ws",
            pei->bstrSource,pei->scode,pei->bstrDescription,ulLine+1,lPos,src);
        ::MessageBox(::GetDesktopWindow(),buf,SCRIPTSITE_APPNAME,MB_OK);
        return NOERROR;
    }
};

/////////////////////////////////////////////////////////////////////////////
// IActiveScriptSiteWindow

class ATL_NO_VTABLE IActiveScriptSiteWindowImpl : public IActiveScriptSiteWindow
{
public:
    // The script engine uses the window which this method returns as a
    // parent window when the engine needs to show a window (e.g. MsgBox)
    STDMETHOD(GetWindow)(HWND *phWnd)
    {
        if(phWnd==NULL)
            return E_POINTER;
        *phWnd = ::GetDesktopWindow();
        return NOERROR;
    };

    STDMETHOD(EnableModeless)(BOOL /*fEnable*/)
    {
        return NOERROR;
    };
};

/////////////////////////////////////////////////////////////////////////////
// CScriptSiteImpl

class ATL_NO_VTABLE CScriptSiteImpl : public IActiveScriptSiteImpl, public IActiveScriptSiteWindowImpl
{
public:
    CScriptSiteImpl()
    {
        m_hWnd = NULL;
        m_bInit = false;
    }

    STDMETHOD(Initiate)(LPCTSTR pszLanguage,HWND hWnd)
    {
        if(!!m_spEngine)
            HR(Terminate());
        m_hWnd = hWnd;

        // Create new script engine
        USES_CONVERSION;
        HR(m_spEngine.CoCreateInstance(T2COLE(pszLanguage)));

        // Attach to site
        HR(m_spEngine->SetScriptSite(static_cast<IActiveScriptSite*>(this)));

        CComQIPtr<IActiveScriptParse> spParse = m_spEngine;
        if(!spParse) return E_NOINTERFACE;
        HR(spParse->InitNew());

        m_bInit = true;
        return NOERROR;
    }

    STDMETHOD(Run)()
    {
        if(!m_bInit) return E_FAIL;
        HR(m_spEngine->SetScriptState(SCRIPTSTATE_STARTED));
        // connect - this makes the script engine handle incoming events
        HR(m_spEngine->SetScriptState(SCRIPTSTATE_CONNECTED));
        return NOERROR;
    }

    STDMETHOD(Terminate)()
    {
        if(m_bInit) {
            // Disconnect the host application from the engine. This will prevent
            // the further firing of events. Event sinks that are in progress will
            // be completed before the state changes.
            m_spEngine->SetScriptState(SCRIPTSTATE_DISCONNECTED);

            // Call to InterruptScriptThread to abandon any running scripts and
            // force cleanup of all script elements.
            m_spEngine->InterruptScriptThread(SCRIPTTHREADID_ALL,NULL,0);

            m_bInit = false;
        }

        if(!!m_spEngine) {
            // Always call prior to release
            m_spEngine->Close();
            m_spEngine.Release();
        }

        return NOERROR;
    }

    STDMETHOD(AddScript)(LPCTSTR pszScript,LPCTSTR pszContext=NULL)
    {
        if(!m_bInit) return E_FAIL;

        CComQIPtr<IActiveScriptParse> spParse = m_spEngine;
        if(!spParse) return E_NOINTERFACE;

        USES_CONVERSION;
        const DWORD dwFlags = SCRIPTTEXT_ISVISIBLE;
        EXCEPINFO einfo;
        return spParse->ParseScriptText(T2COLE(pszScript),pszContext!=NULL ? T2COLE(pszContext) : OLESTR(""),NULL,NULL,0,0,dwFlags,NULL,&einfo);
    }

    STDMETHOD(AddObject)(LPCTSTR pszName,BOOL bGlobalCollection=FALSE)
    {
        if(!m_bInit) return E_FAIL;

        DWORD dwFlags = SCRIPTITEM_ISVISIBLE;
        if(bGlobalCollection)
            dwFlags |= SCRIPTITEM_GLOBALMEMBERS;

        USES_CONVERSION;
        return m_spEngine->AddNamedItem(T2COLE(pszName),dwFlags);
    }

    STDMETHOD(GetWindow)(HWND *phWnd)
    {
        if(phWnd==NULL)
            return E_POINTER;
        *phWnd = m_hWnd;
        return NOERROR;
    };

protected:
    ~CScriptSiteImpl()
    {
        if(!!m_spEngine)
            Terminate();
    }

protected:
    HWND                    m_hWnd;
    bool                    m_bInit;
    CComPtr<IActiveScript>  m_spEngine;
};

/////////////////////////////////////////////////////////////////////////////
// CScriptSiteBasic
//
// This is the minimum code needed to run a script engine. It has no support
// for your own object model, and displays errors as a messagebox. To
// overcome this, you'll need to write your own version and override
// LookupNamedItem and/or HandleScriptError.
//
// Use it like this:
//
//   LPCTSTR strScriptCode; // this points to the script code we want to run
//   ...
//   CComObject<CScriptSiteBasic>* pBasic;
//   CComQIPtr<IActiveScriptSite> spUnk;
//   HR(CComObject<CScriptSiteBasic>::CreateInstance(&pBasic));
//   spUnk = pBasic; // let CComQIPtr tidy up for us
//   HR(pBasic->Initiate(_T("jscript"),GetDesktopWindow()));
//   HR(pBasic->AddScript(strScriptCode));
//   HR(pBasic->Run());
//   HR(pBasic->Terminate());

class ATL_NO_VTABLE CScriptSiteBasic :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CScriptSiteImpl
{
public:
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CScriptSiteBasic)
    COM_INTERFACE_ENTRY(IActiveScriptSite)
    COM_INTERFACE_ENTRY(IActiveScriptSiteWindow)
END_COM_MAP()
};

#endif // !INC_SCRIPTSITE_H