#!/usr/bin/env python
#
# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""
Real-world test (courtesy Leanid Nazdrynau) of the multi-step
capabilities of the various Java Builders.

TODO: the whole Applet facility is deprecated, need a new test.
"""

import os
import pathlib

import TestSCons

test = TestSCons.TestSCons()

where_javac, java_version = test.java_where_javac()
where_javah = test.java_where_javah()
where_java_include=test.java_where_includes()

swig = test.where_is('swig')
if not swig:
    test.skip_test('Can not find installed "swig", skipping test.\n')

# Skip this test on AppVeyor

# Skip this test as SCons doesn't (currently) predict the generated
# inner/anonymous class generated .class files generated by gcj
# and so will always fail
if test.javac_is_gcj:
    test.skip_test('Test not valid for gcj (gnu java); skipping test(s).\n')

# TODO rework for 'javac -h', for now skip
# The logical test would be:  if java_version > 9:
# but java_where_javah() roots around and will find from an older version
if not test.Environment().WhereIs('javah'):
    test.skip_test("No Java javah for version > 9, skipping test.\n")

# On some systems, the alternatives system does not remove javah even if the
# preferred Java doesn't have it, so try another check
javacdir = pathlib.Path(where_javac).parent
javahdir = pathlib.Path(where_javah).parent
if javacdir != javahdir:
    test.skip_test("Cannot find Java javah matching javac, skipping test.\n")

test.subdir(
    ['src'],
    ['src', 'HelloApplet'],
    ['src', 'HelloApplet', 'com'],
    ['src', 'javah'],
    ['src', 'jni'],
    ['src', 'server'],
    ['src', 'server', 'JavaSource'],
    ['src', 'server', 'JavaSource', 'com'],
    ['src', 'server', 'JavaSource', 'com', 'gnu'],
    ['src', 'server', 'JavaSource', 'com', 'gnu', 'scons'],
    ['src', 'server', 'JavaSource', 'com', 'gnu', 'scons', 'web'],
    ['src', 'server', 'JavaSource', 'com', 'gnu', 'scons', 'web', 'tools'],
    ['src', 'server', 'WebContent'],
    ['src', 'server', 'WebContent', 'META-INF'],
    ['src', 'server', 'WebContent', 'WEB-INF'],
    ['src', 'server', 'WebContent', 'WEB-INF', 'conf'],
    ['src', 'server', 'WebContent', 'WEB-INF', 'lib'],
    ['src', 'server', 'WebContent', 'theme'],
)

test.write(['SConstruct'], """\
import os, sys

if sys.platform == 'win32':
    # Ensure tests don't pick up link from mingw or cygwin
    tools = ['msvc', 'mslink', 'jar', 'javac', 'javah', 'swig']
else:
    tools = ['default', 'javac', 'javah', 'swig']

_ = DefaultEnvironment(tools=[])
env = Environment(tools=tools, CPPPATH=["$JAVAINCLUDES"])
Export('env')
# env.PrependENVPath('PATH',os.environ.get('PATH',[]))
env['INCPREFIX'] = '-I'
env.Append(SWIGFLAGS=['-c++', '$_CPPINCFLAGS'])

# this is for JNI
# env.Append(CCFLAGS=['/IN:/jdk/v1.3.1/include','/IN:/jdk/v1.3.1/include/win32'])

# this for windows only C++ build
# env.Append(CXXFLAGS='-GX')

env.Append(CPPPATH='.')

env.VariantDir('buildout', 'src', duplicate=0)

if sys.platform[:6] == 'darwin':
    env.Append(CPPPATH=['/System/Library/Frameworks/JavaVM.framework/Headers'])

# If you do not have swig on your system please remove
# 'buildout/jni/SConscript' line from next call
env.SConscript(
    [
        'buildout/server/JavaSource/SConscript',
        'buildout/HelloApplet/SConscript',
        'buildout/jni/SConscript',
        'buildout/javah/SConscript',
    ]
)
""")

test.write(['src', 'HelloApplet', 'Hello.html'], """\
<HTML>
<HEAD>
<TITLE> Applet Hello </TITLE>
</HEAD>
<BODY>
<CENTER>
<applet name="Hello" archive="HelloApplet.jar" code="com.Hello.Hello.class"
    width="800" height="286" MAYSCRIPT>
</applet>
</CENTER>
</BODY>
</HTML>

""")

test.write(['src', 'HelloApplet', 'SConscript'], """\
import os

Import("env")
denv = env.Clone()
classes = denv.Java(target='classes', source=['com'])
# set correct path for jar
denv['JARCHDIR'] = os.path.join(denv.Dir('.').get_abspath(), 'classes')
denv.Jar('HelloApplet', classes)

# To sign applet you have to create keystore before and made a calls like this

# keystore='/path/to/jarsignkey'
# denv['JARSIGNFLAGS']='-keystore '+keystore+' -storepass pass -keypass passkey'
# denv['JARSIGNALIAS']='ALIAS'
# denv['JARCOM']=[denv['JARCOM'],'$JARSIGNCOM']
""")

test.write(['src', 'HelloApplet', 'com', 'Hello.java'], """\
package com.Hello;
import java.awt.*;
import java.applet.*;

public class Hello extends Applet {
    static final long serialVersionUID = 0;
    public void paint(Graphics g)
    {
        g.drawString("Hello from SCons signed applet",250,150);
    }
}
""")

test.write(['src', 'javah', 'MyID.cc'], """\
#include "MyID.h"
#ifdef _MSC_VER
__declspec(dllexport)
#endif
int getMyID()
{
   return 0;
}
""")

test.write(['src', 'javah', 'MyID.java'], """\
public class MyID
{
    static private long current = System.currentTimeMillis();
    static public String get()
    {
        current++;
        return new Long( current ).toString();
    }
}
""")

test.write(['src', 'javah', 'SConscript'], """\
Import('env')
denv = env.Clone()
denv['JARCHDIR'] = denv.Dir('.').get_abspath()
denv.Jar('myid', 'MyID.java')
denv.JavaH(denv.Dir('.').get_abspath(), 'MyID.java')
denv.SharedLibrary('myid', 'MyID.cc')
""")

test.write(['src', 'jni', 'A.java'], """\
package web.jni;

public class A
{
class C
{
    void echo2( String text )
    {
        System.out.println( text+"aa" );

    }
}
class B
{
    void echo( String text )
    {
        System.out.println( text );
        C c = new C();
        c.echo2("from B callin C");
    }
}
    public void main( String[] x)
    {
        B b = new B();
        b.echo("123");
        C c = new C();
        c.echo2("456");
    }
}
""")

test.write(['src', 'jni', 'JniWrapper.cc'], """\
#include <vector>
#include <iostream>

#include "JniWrapper.h"

JniWrapper::JniWrapper( JNIEnv *pEnv )
    : mpEnv( pEnv )
{
}

JniWrapper::JniWrapper( const JniWrapper& rJniWrapper )
    : mpEnv( rJniWrapper.mpEnv )
{
}

JniWrapper::~JniWrapper()
{
}

JniWrapper& JniWrapper::operator=( const JniWrapper& rJniWrapper )
{
    if ( this != &rJniWrapper )
    {
        mpEnv = rJniWrapper.mpEnv;
    }
    return *this;
}

std::string JniWrapper::unmarshalString( jstring value )
{
    std::string result;
    if( value )
    {
        const char *pStr = mpEnv->GetStringUTFChars( value, 0 );
        result = pStr;
        mpEnv->ReleaseStringUTFChars( value, pStr );
    }
    return result;
}

jobject JniWrapper::marshalDouble( double value )
{
    jclass classObject = mpEnv->FindClass( "java/lang/Double" );
    jmethodID constructorId = mpEnv->GetMethodID( classObject, "<init>", "(D)V" );
    jobject result = mpEnv->NewObject( classObject, constructorId, value );

    return result;
}

jobject JniWrapper::getVectorElement( jobject values, int i )
{
    jclass vectorClass = mpEnv->FindClass( "java/util/Vector" );
    jmethodID methodID = mpEnv->GetMethodID( vectorClass,
                                             "elementAt",
                                             "(I)Ljava/lang/Object;" );
    jobject result = mpEnv->CallObjectMethod( values, methodID, i );

    return result;
}

jobject JniWrapper::newVector()
{
    jclass vectorClass = mpEnv->FindClass( "java/util/Vector" );
    jmethodID   constructorID = mpEnv->GetMethodID( vectorClass, "<init>", "()V" );
    jobject result = mpEnv->NewObject( vectorClass, constructorID );

    return result;
}

void JniWrapper::addElement( jobject vector, jobject element )
{
    jclass vectorClass = mpEnv->FindClass( "java/util/Vector" );
    jmethodID addElementMethodID = mpEnv->GetMethodID( vectorClass,
                                                       "addElement",
                                                       "(Ljava/lang/Object;)V" );

    mpEnv->CallVoidMethod( vector, addElementMethodID, element );
}

jobject JniWrapper::marshalDoubleVector( const std::vector<double>& rVector )
{
    jobject result = newVector();

    for ( int i = 0; i < rVector.size(); i++ )
    {
          addElement( result, marshalDouble( rVector[i] ) );
    }

    return result;
}

std::pair<std::string, std::string> JniWrapper::unmarshalPairString( jobject vector )
{
    std::pair<std::string, std::string> result;
    result.first  = unmarshalString( (jstring)getVectorElement( vector, 0 ) );
    result.second = unmarshalString( (jstring)getVectorElement( vector, 1 ) );

    return result;
}
""")

test.write(['src', 'jni', 'JniWrapper.h'], """\
#ifndef JniWrapper_h
#define JniWrapper_h

#include <jni.h>
/**
 * Provides routines for dealing with JNI translation etc.
 */

class JniWrapper
{
public:
    JniWrapper( JNIEnv *pEnv );
    JniWrapper( const JniWrapper& rJniWrapper );
    virtual ~JniWrapper();
    JniWrapper& operator=( const JniWrapper& rJniWrapper );


    std::string unmarshalString( jstring value );

    jstring marshalString( const std::string& value );

    jbyteArray marshalByteArray( const std::string& value );

    double unmarshalDouble( jobject value );

    jobject marshalDouble( double value );
    jobject marshallStringVector( const std::vector<std::string>& rMap );

    jobject marshalDoubleVector( const std::vector<double>& rVector );
    std::pair<std::string, std::string> unmarshalPairString( jobject );

protected:
    JNIEnv *mpEnv;

private:
    JniWrapper();
    jobject newVector();
    void addElement( jobject vector, jobject element );
    jobject getVectorElement( jobject values, int i );

};

#endif // JniWrapper_h


""")

test.write(['src', 'jni', 'SConscript'], """\
Import("env")
denv = env.Clone()

denv.Append(SWIGFLAGS=['-java'])
denv.SharedLibrary('scons', ['JniWrapper.cc', 'Sample.i'])
denv['JARCHDIR'] = denv.Dir('.').get_abspath()
denv.Jar(['Sample.i', 'A.java'])
""")

test.write(['src', 'jni', 'Sample.h'], """\
#include <vector>
#include <iostream>

class SampleTest
{
public:
    std::vector<double> test1( std::pair<std::string, std::string> param )
    {
        std::vector<double> result;
        result.push_back(10);
        return result;
    }
};

""")

test.write(['src', 'jni', 'Sample.i'], """\
%module Sample

%{
#include "Sample.h"
#include "JniWrapper.h"
%}

%typemap(jni)    std::vector<double>, std::vector<double>& "jobject"
%typemap(jtype)  std::vector<double>, std::vector<double>& "java.util.Vector"
%typemap(jstype) std::vector<double>, std::vector<double>& "java.util.Vector"

// return a Vector of Doubles
%typemap(javaout) std::vector<double> {
    return $jnicall;
}
%typemap(out) std::vector<double> {
    JniWrapper JniWrapper(jenv);
    $result = JniWrapper.marshalDoubleVector( $1 );
}

/*********************************************************************
 *
 * Pairs of String (IN/OUT)
 *
 *********************************************************************/
%typemap(jni)     std::pair<std::string, std::string>, const std::pair<std::string, std::string>& "jobject"
%typemap(jtype)   std::pair<std::string, std::string>, const std::pair<std::string, std::string>&  "java.util.Vector"
%typemap(jstype)  std::pair<std::string, std::string>, const std::pair<std::string, std::string>&  "java.util.Vector"
%typemap(javain)  std::pair<std::string, std::string>, const std::pair<std::string, std::string>&  "$javainput"

// pass in by reference a Vector of std::string
%typemap(in) const std::pair<std::string, std::string>&   {
    $1 = new std::pair<std::string, std::string>();
    JniWrapper JniWrapper(jenv);
    *($1) = JniWrapper.unmarshalPairString( $input );
}

//cleanup
%typemap(freearg) const std::pair<std::string, std::string>& {
    delete $1;
}

%include "Sample.h"

// generate:Sample.java
// generate:SampleJni.java
// generate:SampleTest.java
""")

test.write(['src', 'server', 'JavaSource', 'SConscript'], """\
import os
Import ("env")
classes=env.Java(target='classes',source=['com'])

HelloApplet=os.path.join('#/buildout/HelloApplet/HelloApplet.jar')
env['WARXFILES']=['SConscript','.cvsignore']
env['WARXDIRS']=['CVS']
#env.War('scons',[classes,Dir('../WebContent'),HelloApplet])
""")

test.write(['src', 'server', 'JavaSource', 'com', 'gnu', 'scons', 'web', 'tools', 'Bool.java'], """\
package com.gnu.scons.web.tools;
public class Bool {
    boolean flag;

    public Bool()
    {
        flag = false;
    }

    public Bool( boolean aFlag )
    {
        flag = aFlag;
    }

    public boolean booleanValue()
    {
        return flag;
    }
}
""")

test.write(['src', 'server', 'JavaSource', 'com', 'gnu', 'scons', 'web', 'tools', 'StringUtils.java'], """\
package com.gnu.scons.web.tools;

public class StringUtils
{
    public static String toPercent( String value )
    {
        if ( value.equals("") )
        {
            return "";
        }
        else
        {
            return value + "%";
        }
    }

}
""")

test.write(['src', 'server', 'WebContent', 'index.html'], """\
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<HTML>
<HEAD>
<META http-equiv="refresh" content="0;URL=go?action=home">
<TITLE>index.html</TITLE>
</HEAD>
<BODY>
<P><a href="go?action=home">go?action=home</a></P>
</BODY>
</HTML>
""")

test.write(['src', 'server', 'WebContent', 'META-INF', 'MANIFEST.MF'], """\
Manifest-Version: 1.0
Class-Path:

""")

test.write(['src', 'server', 'WebContent', 'WEB-INF', 'web.xml'], """\
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app id="WebExample">
    <display-name>scons</display-name>
    <servlet>
        <servlet-name>WebExample</servlet-name>
        <display-name>WebExample</display-name>
        <servlet-class>com.gnu.scons.web.tool.WebExample</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>WebExample</servlet-name>
        <url-pattern>/go</url-pattern>
    </servlet-mapping>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
    </welcome-file-list>
</web-app>
""")

test.write(['src', 'server', 'WebContent', 'WEB-INF', 'conf', 'app.properties'], """\
logging = webExample.Example
""")

test.write(['src', 'server', 'WebContent', 'theme', 'Master.css'], """\
body
{
    font-family: Helvetica,Sans-Serif;
    font-size: 11pt;
}
""")

test.run(arguments = '.')

test.must_exist(['buildout', 'javah', 'myid.jar'])
test.must_exist(['buildout', 'javah', 'MyID', 'MyID.class'])

test.must_exist(['buildout', 'jni', 'Sample.class'])
test.must_exist(['buildout', 'jni', 'Sample.java'])
test.must_exist(['buildout', 'jni', 'SampleJNI.class'])
test.must_exist(['buildout', 'jni', 'SampleJNI.java'])
test.must_exist(['buildout', 'jni', 'SampleTest.java'])

# Some combinations of Java + SWIG apparently don't actually generate
# a SampleTest.class file, while others do.  Only issue a warning if
# it doesn't exist.
p = test.workpath('buildout', 'jni', 'SampleTest.class')
if not os.path.exists(p):
    print('Warning:  %s does not exist' % p)

test.up_to_date(arguments = '.')

test.pass_test()

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:
