#!/usr/bin/env python3
"""unit test which aims to cover as much code as possible in mcpl.py (as per coverage.py)"""

#coverage recipe:
#
#  coverage2 run --source=MCPL `which sb_mcplpytests_testpyc`
#
#or:
#
#  coverage3 run --source=MCPL `which sb_mcplpytests_testpyc`
#
#then get nice html results with:
#
#coverage html -d somedir/
#

import os
import glob
import sys
import shutil
import contextlib
from io import open#py2's open is now like py3's.
from Utils.printnumpy import format_numpy_1darray_asfloat as npfmt

os.mkdir('./fakepypath')
with open('./fakepypath/numpy.py','tw') as f:
    f.write(u'raise ImportError("fake error")\n')#u'' prefix intended, to appease io.open in py2
    f.close()
oldsyspath=sys.path
sys.path=[os.path.abspath('./fakepypath')]+sys.path
try:
    import MCPL as mcpl
except ImportError as e:
    print("Caught expected error: %s"%str(e))
    mcpl = None
shutil.rmtree('./fakepypath')
sys.path = oldsyspath

if mcpl is None:
    import MCPL as mcpl #noqa E402

#import Core.System as Sys

try:
    from shlex import quote
except ImportError:
    from pipes import quote

datadir = os.getenv('SBLD_DATA_DIR')
file1 = os.path.join(datadir,'MCPLTests/reffile_12.mcpl')
file2 = os.path.join(datadir,'MCPLTests/reffile_1.mcpl')
file3 = os.path.join(datadir,'MCPLTests/miscphys.mcpl.gz')
file4 = os.path.join(datadir,'MCPLTestsFMT2/reffile_8.mcpl')
filecrash = os.path.join(datadir,'MCPLTests/reffile_crash.mcpl')
badfiles = sorted(glob.glob(os.path.join(datadir,'MCPLTests/reffile_bad*.mcpl*')))
assert len(badfiles)==8

file1q = quote(file1)

def testtool(args,testnone=False):
    try:
        mcpl.app_pymcpltool(['mcpltool']+args if not testnone else None)
    except mcpl.MCPLError as e:
        print("===> mcpltool ended with MCPLError exception: %s"%str(e))
        pass
    except SystemExit as e:
        print("===> mcpltool ended with exit code %s"%str(e))
        pass

testtool([])
testtool(['-l3'])
testtool([file1q,'-nl3'])
testtool([file1q,'-jn'])
testtool([file1q,'-jl1'])
testtool([file1q,'-jl1','-l2'])
testtool([file1q,'-js1'])
testtool([file1q,'-js1','-s2'])
testtool([file1q,'--help'])
testtool(['-v'])
testtool(['-vy'])
testtool(['-v',file1q])
testtool(['-h'])
testtool([file1q,quote(file2)])
testtool(['-vj'])
testtool(['-j',file1q,'-bLala'])
testtool([file1q,'-b'])
testtool([file1q,'-bLala','-bbla'])
testtool(['--lala'])
testtool([],testnone=True)
testtool([file1q,'-j'])
testtool([quote(filecrash)])
testtool([quote(file3)])
testtool([quote(file4)])

testtool([quote(os.path.join(datadir,'MCPLTests/reffile_uw.mcpl.gz'))])
testtool([quote(os.path.join(datadir,'MCPLTests/reffile_empty.mcpl.gz'))])
testtool([quote(os.path.join(datadir,'MCPLTests/reffile_empty.mcpl'))])
testtool([quote(os.path.join(datadir,'MCPLTests/reffile_truncated.mcpl'))])
testtool([quote(os.path.join(datadir,'MCPLTests/reffile_truncated.mcpl.gz'))])
testtool([quote(os.path.join(datadir,'MCPLTests/reffile_encodings.mcpl.gz')),'-basciidata'])
testtool([quote(os.path.join(datadir,'MCPLTests/reffile_encodings.mcpl.gz')),'-butf8data'])

@contextlib.contextmanager
def stdout_buffer_write_hexvalues():
    #Hack needed to prevent the nasty stuff inside the "binarydata" blob cause
    #test irreproducibilities (seen in a particular github workflow).
    import sys
    _orig_buffer_write = sys.stdout.buffer.write
    def bufwrite( b ):
        res = b''
        for e in b:
            res += hex(e).encode('ascii')
        _orig_buffer_write(res)
    try:
        sys.stdout.flush()
        sys.stdout.buffer.flush()
        sys.stdout.buffer.write = bufwrite
        yield
    finally:
        sys.stdout.buffer.write = _orig_buffer_write
        sys.stdout.buffer.flush()
        sys.stdout.flush()

with stdout_buffer_write_hexvalues():
    testtool([quote(os.path.join(datadir,'MCPLTests/reffile_encodings.mcpl.gz')),'-bbinarydata'])

testtool([quote(os.path.join(datadir,'MCPLTests/reffile_empty.mcpl')),'--stats'])
testtool([quote(os.path.join(datadir,'MCPLTests/reffile_uw.mcpl.gz')),'--stats'])
testtool([file1q,'--stats'])
testtool([quote(file3),'--stats'])

def loadbad(fn):
    try:
        mcpl.MCPLFile(fn)
    except mcpl.MCPLError as e:
        print('MCPL ERROR: %s'%str(e))
    except IOError as e:
        print(e)
loadbad('notfound.mcpl')
loadbad('bla.txt')
loadbad(123)
loadbad(None)
for bf in badfiles:
    loadbad(bf)

from numpy import asarray as np_asarray # noqa E402

for fn in (file1,file2,file3):
    print ("---> testing array access")
    with mcpl.MCPLFile(fn,blocklength=2) as f:
        for p in f.particles:
            assert (p.position==np_asarray((p.x,p.y,p.z))).all()
            assert (p.direction==np_asarray((p.ux,p.uy,p.uz))).all()
            assert (p.polarisation==np_asarray((p.polx,p.poly,p.polz))).all()
            print('position: (%g, %g, %g), %s, %s, (%g, %g, %g)'%(p.x,p.y,p.z,npfmt(p.position),
                   str(type(p.position)).replace('class','type'),p.position[0],p.position[1],p.position[2]))
            print('polarisation: (%g, %g, %g), %s, %s, (%g, %g, %g)'%(p.polx,p.poly,p.polz,npfmt(p.polarisation),
                   str(type(p.polarisation)).replace('class','type'),p.polarisation[0],p.polarisation[1],p.polarisation[2]))
            print('direction: (%g, %g, %g), %s, %s, (%g, %g, %g)'%(p.ux,p.uy,p.uz,npfmt(p.direction),
                   str(type(p.direction)).replace('class','type'),p.direction[0],p.direction[1],p.direction[2]))

with mcpl.MCPLFile(file1,blocklength=3) as f:
    print(f.sourcename)
    print(f.blocklength)
    for i,c in enumerate(f.comments):
        print("comment #%i: %s"%(i,c))
    assert set(f.blobs.keys()) == set(f.blob_storage_order)
    print(','.join('%s[%i]'%(k,len(f.blobs[k])) for k in f.blob_storage_order))

    print('indices in file: %s'%(','.join(str(p.file_index) for p in f.particles)))
    for ib,pb in enumerate(f.particle_blocks):
        pb.uy if ib%2 else pb.uz
        assert pb[len(pb)] is None
        print('indices in block starting at %i a: %s'%(pb.file_offset,','.join(   str(pb[i].file_index) for i in range(len(pb)))))
        print('indices in block starting at %i b: %s'%(pb.file_offset,','.join(   str(p.file_index) for p in pb.particles)))
    f.rewind()
    p=f.read()
    assert p.file_index==0
    f.skip_forward(1)
    p=f.read()
    assert p.file_index==2
    f.skip_forward(1)
    p=f.read()
    assert p.file_index==4
    f.rewind()
    f.skip_forward(4)
    p=f.read()
    assert p.file_index==4
    try:
        f.skip_forward(-1)
        p=f.read()
    except mcpl.MCPLError as e:
        print(str(e))
    f.skip_forward(999999)
    p=f.read()
    assert p is None

def tostr(a):
    """convert bytes/unicode to str in both py2 or py3 (assuming ascii chars
    only). Other objects are simply converted via str(..)."""
    #the amount of bullshit we have to deal with in order to support both py2
    #and py3 is rather wild...
    if isinstance(a,str):
        return a#bytes in py2, unicode in py3
    elif isinstance(a,bytes):
        return a.decode('ascii')#got bytes in py3
    elif str==bytes and isinstance(a,unicode): #noqa F821 ("unicode" not in py3)
        return a.encode('ascii')#got unicode in py2 # noqa f821
    else:
        return str(a)#neither str/bytes/unicode

def test_stats(*args,**kwargs):
    def fmtkw(k,v):
        return '%s=%s'%(tostr(k),[tostr(e) for e in v] if isinstance(v,list) else tostr(v))
    print('======================> Test stats(%s)'%(
          ','.join([tostr(a) for a in ['MCPLFILE' if isinstance(args[0],mcpl.MCPLFile) else os.path.basename(args[0])]
                    +list(args[1:])]+[fmtkw(k,v) for k,v in sorted(kwargs.items())])))
    try:
        stats=mcpl.collect_stats(*args,**kwargs)
        if stats!={}:
            mcpl.dump_stats(stats)
    except mcpl.MCPLError as e:
        print('MCPL ERROR: %s'%e)
        return {}
    return {}

test_stats(file1,stats='all',bin_data=True)
test_stats(mcpl.MCPLFile(file1,blocklength=1),stats='all',bin_data=False)
fewstats=test_stats(os.path.join(datadir,'MCPLTests/reffile_userflags_is_pos.mcpl.gz'),stats=['polx','userflags'],bin_data=True)
test_stats(file1,stats=['userflags'],bin_data=True)
test_stats(os.path.join(datadir,'MCPLTests/reffile_empty.mcpl'),stats=['x','y'],bin_data=True)
mcpl.plot_stats(fewstats,pdf='lala.pdf',set_backend='agg')
test_stats(os.path.join(datadir,'MCPLTests/reffile_uw.mcpl.gz'))
test_stats(file1,stats=[])
test_stats(file1,stats=['blabla'])
test_stats(os.path.join(datadir,'MCPLTests/miscphys.mcpl.gz'))
