# Python package for the Plastic Logic SDK - test.py
#
# Copyright (C) 2013 Plastic Logic Limited
#
#     Guillaume Tucker <guillaume.tucker@plasticlogic.com>
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import os
import sys
import time
import optparse
import threading
import Queue
import tempfile

try:
    import _plsdk
except ImportError:
    print("Failed to import _plsdk")
    sys.exit(1)

try:
    import plsdk
except ImportError:
    print("Failed to import plsdk")
    sys.exit(1)

options = None
auto_id = {}

CAT_GENERAL = 0
CAT_BUFFER = 10
CAT_EPDC = 20
CAT_DISPLAY = 30
CAT_INPUT = 40

# -----------------------------------------------------------------------------
# Common functions

def func_test_name(func):
    return func.func_name.split('test_')[1]

def set_id(f, cat):
    if cat not in auto_id:
        auto_id[cat] = cat
    else:
        auto_id[cat] += 1
    f.test_id = auto_id[cat]

def make_fb_kw(**kw):
    if options.fbdev is not None:
        kw['device'] = options.fbdev
    return kw

def make_buffer(**kw):
    return _plsdk.Buffer(**make_fb_kw(**kw))

def make_ep_kw(**kw):
    if options.epdev is not None:
        kw['device'] = options.epdev
    if options.epmode is not None:
        kw['mode'] = options.epmode
    return kw

def make_epdc(**kw):
    return _plsdk.Epdc(**make_ep_kw(**kw))

def make_display():
    return plsdk.Display(fb_kw=make_fb_kw(), ep_kw=make_ep_kw())

def make_keyboard(**kw):
    if options.kbddev is not None:
        kw['device'] = options.kbddev
    else:
        kw['regex'] = "Dummy keyboard"
    return _plsdk.Input(**kw)

def make_touchscreen(**kw):
    if options.tsdev is not None:
        kw['device'] = options.tsdev
    else:
        kw['regex'] = "Dummy touch"
    return _plsdk.Input(**kw)

def make_temp_config(config):
    tmp = tempfile.NamedTemporaryFile(delete=False)
    for section, opts in config.iteritems():
        tmp.write("[%s]\n" % section)
        for key, value in opts.iteritems():
            tmp.write("%s=%s\n" % (key, value))
    tmp.close()
    return tmp.name


# -----------------------------------------------------------------------------
# Test functions

def test_exception():
    got_except = False
    print("Raising _plsdk.error...")
    try:
        raise _plsdk.error("Exception test")
    except Exception:
        print("...got it.")
        got_except = True
    return got_except
set_id(test_exception, CAT_GENERAL)

def test_buffer_class():
    print("Creating _plsdk.Buffer")
    b = make_buffer()
    print("device: %s" % b.dev)
    print("resolution: %dx%d" % (b.xres, b.yres))
    print("rotation: %d" % b.rotation)
    print("palettes:")
    for name, data in b.palettes.iteritems():
        print("  %s: %s" % (name, data))
    return True
set_id(test_buffer_class, CAT_BUFFER)

def test_buffer_grey_rgb565():
    print("Testing _plsdk.Buffer.get_grey")
    grey_ref = 0xDEFB
    grey = make_buffer(cfa=_plsdk.Buffer.CFA_none).get_grey(0xD9)
    return grey == grey_ref
set_id(test_buffer_grey_rgb565, CAT_BUFFER)

def test_buffer_colour_rgb565():
    print("Testing _plsdk.Buffer.get_color")
    col_ref = 0x041F
    col = make_buffer(cfa=_plsdk.Buffer.CFA_none).get_color(0x00, 0x80, 0xFF)
    return col == col_ref
set_id(test_buffer_colour_rgb565, CAT_BUFFER)

def test_buffer_gl():
    print("Testing _plsdk.Buffer.gl instance list")
    b = make_buffer()
    for i, grey in enumerate(b.palettes['default_grey_16']):
        gl_ref = b.get_grey(grey)
        gl = b.gl[i]
        print("gl%d: %04X" % (i, gl))
        if gl != gl_ref:
            return False
    return True
set_id(test_buffer_gl, CAT_BUFFER)

def test_buffer_rotation():
    print("Testing _plsdk.Buffer.rotation")
    b = make_buffer()
    print("angle=%d xres=%d yres=%d" % (b.rotation, b.xres, b.yres))
    xres, yres = b.xres, b.yres
    rot = b.rotation + (-90 if b.rotation % 180 else 90)
    print("rotation: %d -> %d" % (b.rotation, rot))
    b.rotation = rot
    print("angle=%d xres=%d yres=%d" % (b.rotation, b.xres, b.yres))
    if (b.rotation != rot) or (b.xres != yres) or (b.yres != xres):
        print("Failed to change the rotation by +/- 90")
        return False
    return True
set_id(test_buffer_rotation, CAT_BUFFER)

# ToDo: split into separate tests for each method and check resulting fb data
def test_buffer_drawing():
    print("Drawing things in frame buffer")
    b = make_buffer()
    b.fill_screen(b.get_grey(0xD9))
    b.set_pixel(b.black, (10, 10))
    b.fill_rect(b.white, (50, 60, 100, 120))
    b.draw_line(b.black, (10, 50, 100, 60))
    b.draw_text(b.black, b.white, (5, 20), "This is a test.")
    return True
set_id(test_buffer_drawing, CAT_BUFFER)

def test_buffer_cfa():
    print("Checking colour filter array options")
    b = make_buffer(cfa=_plsdk.Buffer.CFA_none)
    if b.cfa != b.CFA_none:
        print("Wrong initial CFA option")
        return False
    print("Original resolution: %dx%d" % (b.xres, b.yres))
    xres, yres = b.xres, b.yres
    b.cfa = b.CFA_GR_BW
    print("CFA enabled resolution: %dx%d" % (b.xres, b.yres))
    if (b.xres != (xres / 2)) or (b.yres != (yres / 2)):
        print("Wrong resolution with CFA_GR_BW enabled")
        return False
    return True
set_id(test_buffer_cfa, CAT_BUFFER)

def test_buffer_config():
    rotation = 90
    config = {
        'libpldraw': {
            'rotation': str(rotation),
            },
        }
    config_path = make_temp_config(config)
    b = make_buffer(config=config_path)
    if b.rotation != rotation:
        print("Failed to read rotation from config file")
        result = False
    else:
        result = True
    os.unlink(config_path)
    return result
set_id(test_buffer_config, CAT_BUFFER)

def test_epdc_class():
    print("Creating _plsdk.Epdc")
    ep = make_epdc()
    print("device: %s" % ep.dev)
    print("mode: %s" % ep.mode)
    print("description: %s" % ep.description)
    print("refresh id: %d" % ep.refresh)
    print("delta id: %d" % ep.delta)
    print("mono id: %d" % ep.mono)
    return True
set_id(test_epdc_class, CAT_EPDC)

def test_epdc_update():
    b = make_buffer()
    ep = make_epdc()
    b.fill_screen(b.white)
    ep.update(ep.refresh, (0, 0, b.xres, b.yres), synchro=True)
    return True
set_id(test_epdc_update, CAT_EPDC)

def test_epdc_temperature():
    ep = make_epdc(mode='noep')
    print("initial temperature: %d" % ep.temperature)
    for t in [10, 30, 28, 20, 0, -12, 57, 123]:
        ep.temperature = t
        if t != ep.temperature:
            print("Failed to set the temperature %s" % ("on" if t else "off"))
            return False
    for mode in [True, False]:
        ep.temperature_auto = mode
        if ep.temperature_auto is not mode:
            print("Failed to set the auto temperature %s" %
                  ("on" if mode else "off"))
            return False
    return True
set_id(test_epdc_temperature, CAT_EPDC)

def test_display_class():
    print("Creating plsdk.Display")
    d = make_display()
    print("full screen: %s" % str(d.full))
    print("e-paper description: %s" % d.ep.description)
    return True
set_id(test_display_class, CAT_DISPLAY)

def test_display_attr():
    d = make_display()
    print("foreground: 0x%04X, background: 0x%04X" % (d.fg, d.bg))
    return (d.fg == d.fb.black) and (d.bg == d.fb.white)
set_id(test_display_attr, CAT_DISPLAY)

def test_display_rectangle():
    print("Drawing a rectangle...")
    d = make_display()
    area = ((d.fb.xres / 4), (d.fb.yres / 4),
            (d.fb.xres * 3 / 4), (d.fb.yres * 3 / 4))
    d.rectangle(area)
    return True
set_id(test_display_rectangle, CAT_DISPLAY)

def test_display_clear():
    print("Clearing the screen...")
    make_display().clear()
    return True
set_id(test_display_clear, CAT_DISPLAY)

def test_display_pixel():
    print("Showing a few pixels in the middle of the screen...")
    d = make_display()
    center_x = d.fb.xres / 2
    center_y = d.fb.yres / 2
    step = 4
    delta = step * 20
    delta2 = delta + step
    d.pixel((center_x, (center_y + delta2)))
    d.pixel((center_x, (center_y - delta2)))
    d.pixel(((center_x + delta2), center_y))
    d.pixel(((center_x - delta2), center_y))
    time.sleep(1.0)
    d.ep.synchro = True
    d.auto_update = False
    for dx in range(-delta, delta + step, step):
        for dy in range(-delta, delta + step, step):
            d.pixel(((center_x + dx), (center_y + dy)))
    d.update(((center_x - delta), (center_y - delta),
              (center_x + delta + 1), (center_y + delta + 1)))
    return True
set_id(test_display_pixel, CAT_DISPLAY)

def test_display_line():
    print("Drawing a few lines...")
    d = make_display()
    d.wf = d.ep.mono
    d.ep.partial = True
    off_x = d.fb.xres / 8
    off_y = d.fb.yres / 8
    lines = [(off_x, off_y, (d.fb.xres - off_x), (d.fb.yres - off_y)),
             (off_x, (d.fb.yres - off_y), (d.fb.xres - off_x), off_y)]
    for line in lines:
        line_area = (line[0], line[1], (line[2] + 1), (line[3] + 1))
        area = d.line(line)
        print(area)
        if area != line_area:
            print("Wrong area returned (%s instead of %s)" % (area, line_area))
            return False
    return True
set_id(test_display_line, CAT_DISPLAY)

def test_display_text():
    print("Drawing some text...")
    d = make_display()
    d.wf = d.ep.mono
    area = d.text("This is a sample piece of text.", (10, 10))
    print(area)
    return True
set_id(test_display_text, CAT_DISPLAY)

def test_input_class():
    print("Creating _plsdk.Input...");
    print("Keyboard")
    i = make_keyboard()
    print("  name: %s" % i.name)
    print("  keycodes: %d" % len(i.keycodes))
    home_keycode = i.keycodes['KEY_HOME']
    if home_keycode != 102:
        print("Wrong KEY_HOME keycode: %d instead of 102" % home_keycode)
        return False
    print("Touchscreen")
    i = make_touchscreen()
    print("  name: %s" % i.name)
    print("  resolution: %dx%d" % (i.xres, i.yres))
    print("  rotation: %d" % i.rotation)
    return True
set_id(test_input_class, CAT_INPUT)

def test_input_keyboard():
    def input_func(q):
        try:
            kbd = plsdk.DummyKeyboard()
        except Exception, e:
            print("ERROR: %s" % e)
        else:
            kbd.grab(True)
            q.put(kbd.read_key())

    q = Queue.Queue()
    t = threading.Thread(target=input_func, args=(q,))
    t.start()
    kbd = plsdk.DummyKeyboard()
    kbd.key = keycode = kbd.keycodes['KEY_HOME']
    loops = 20
    while q.empty() and loops:
        loops -= 1
        kbd.on = True
        kbd.sync()
        time.sleep(0.05)
        kbd.on = False
        kbd.sync()
        time.sleep(0.05)

    if not loops:
        print("Failed to get keyboard input")
        return False

    t.join()
    key = q.get()
    print("Key: %s" % key)
    if key['code'] != keycode:
        print("Wrond keycode: %d instead of %d" %(key['code'], keycode))
        return False

    return True
set_id(test_input_keyboard, CAT_INPUT)

def test_input_touchscreen():
    def input_func(q):
        try:
            ts = plsdk.DummyTouchscreen()
        except Exception, e:
            print("ERROR: %s" % e)
        else:
            ts.grab(True)
            on = True
            while on:
                on = ts.read_point()['on']
            while not on:
                pt = ts.read_point()
                print(pt)
                on = pt['on']
            q.put(pt)

    q = Queue.Queue()
    t = threading.Thread(target=input_func, args=(q,))
    t.start()
    ts = plsdk.DummyTouchscreen()
    x = ts.xres / 4
    y = ts.yres / 4
    loops = 20
    coords = []
    while q.empty() and loops:
        loops -= 1
        x += 1
        y += 1
        xy = x, y
        coords.append(xy)
        ts.x, ts.y = xy
        ts.on = True
        ts.sync()
        time.sleep(0.05)
        ts.on = False
        ts.sync()
        time.sleep(0.05)

    if not loops:
        print("Failed to get touchscreen input")
        return False

    t.join()
    pt = q.get()
    xy = pt['x'], pt['y']
    if xy not in coords:
        print("Wrong coordinates: (%d, %d) instead of (%d, %d)" %
              (pt['x'], pt['y'], x, y))
        return False

    return True
set_id(test_input_touchscreen, CAT_INPUT)

# -----------------------------------------------------------------------------
# Main function

def main(argv):
    global options
    parser = optparse.OptionParser()
    parser.add_option("--list", action="store_true", default=False,
                      help="List all tests with their ids")
    parser.add_option("--fbdev", help="Frame buffer device")
    parser.add_option("--epdev", help="E-Paper device")
    parser.add_option("--epmode", help="E-Paper mode")
    parser.add_option("--kbddev", help="Input keyboard device")
    parser.add_option("--tsdev", help="Input touchscreen device")
    options, args = parser.parse_args(argv[1:])

    all_tests = {}

    for name, value in globals().iteritems():
        if name.startswith('test_') and hasattr(value, '__call__'):
            all_tests[func_test_name(value)] = value

    sorted_tests = sorted(all_tests.values(),
                          lambda t1, t2: cmp(t1.test_id, t2.test_id))

    if options.list is True:
        for f in sorted_tests:
            print("%03d: %s" % (f.test_id, func_test_name(f)))
        return True

    if len(args) == 0:
        tests = sorted_tests
    else:
        tests = []
        for test_name in args:
            test = all_tests.get(test_name, None)
            if test is None:
                print("Invalid test name: %s" % test_name)
                return False
            else:
                tests.append(test)

    n_tests = len(tests)
    n_passed = 0

    print("Number of tests: %d" % n_tests)
    print("--------------------------------------------------")

    for test in tests:
        print("%03d: %s" % (test.test_id, func_test_name(test)))
        try:
            if test() is True:
                n_passed += 1
                print("OK")
            else:
                print("FAILED")
        except Exception, e:
            print("ERROR: %s" % e)
        print("--------------------------------------------------")

    print("Result: %d/%d" % (n_passed, n_tests))

    if (n_tests != 0) and (n_tests == n_passed):
        print("All tests passed.")
        return True
    else:
        print("SOME TESTS FAILED")
        return False

if __name__ == '__main__':
    if main(sys.argv):
        sys.exit(0)
    else:
        sys.exit(1)
