// 
//   Copyright (C) 2010, 2011 Free Software Foundation, Inc
// 
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//

#ifdef HAVE_CONFIG_H
#include "gnashconfig.h"
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "GnashSleep.h"
#include "log.h"
#include "InputDevice.h"

namespace gnash {

static const char *MOUSE_DEVICE = "/dev/input/mice";

MouseDevice::MouseDevice()
{
    GNASH_REPORT_FUNCTION;
}

MouseDevice::MouseDevice(Gui *gui)
{
    // GNASH_REPORT_FUNCTION;

    _gui = gui;
}

std::vector<boost::shared_ptr<InputDevice> >
MouseDevice::scanForDevices(Gui *gui)
{
    // GNASH_REPORT_FUNCTION;

    struct stat st;

    std::vector<boost::shared_ptr<InputDevice> > devices;

    // Look for these files for mouse input
    struct mouse_types {
        InputDevice::devicetype_e type;
        const char *filespec;
    };

    // Debug strings to make output more readable
    const char *debug[] = {
        "UNKNOWN",
        "Keyboard",
        "Mouse",
        "Touchscreen",
        "Touchscreen Mouse",
        "Power Button",
        "Sleep Button",
        "Serial-USB Adapter",
        "Infrared Receiver"
    };    

    struct mouse_types mice[] = {
        {InputDevice::MOUSE, "/dev/input/mice"},      // PS/2 Mouse
#ifdef MULTIPLE_DEVICES
        {InputDevice::MOUSE, "/dev/input/mouse0"},
        {InputDevice::MOUSE, "/dev/input/mouse1"},
        {InputDevice::MOUSE, "/dev/usb/tkpanel0"},    // eTurboTouch touchscreen
#endif
        {InputDevice::UNKNOWN, 0}
    };

    int i = 0;
    while (mice[i].type != InputDevice::UNKNOWN) {
        int fd = 0;
        if (stat(mice[i].filespec, &st) == 0) {
            // Then see if we can open it
            if ((fd = open(mice[i].filespec, O_RDWR)) < 0) {
                log_error("You don't have the proper permissions to open %s",
                          mice[i].filespec);
                i++;
                continue;
            }
            close(fd);
            log_debug("Found a %s device for mouse input using %s",
                      debug[mice[i].type], mice[i].filespec);
            
            boost::shared_ptr<InputDevice> dev;
#if defined(USE_MOUSE_PS2) || defined(USE_MOUSE_ETT)
            dev = boost::shared_ptr<InputDevice>(new MouseDevice(gui));
            if (dev->init(mice[i].filespec, DEFAULT_BUFFER_SIZE)) {
                devices.push_back(dev);
            }
//            dev->dump();
#endif
        }     // stat()
        i++;
    }         // while()
    
    return devices;
}

bool
MouseDevice::init()
{
    GNASH_REPORT_FUNCTION;

    return init(MOUSE_DEVICE, DEFAULT_BUFFER_SIZE);
}

bool
MouseDevice::init(const std::string &filespec, size_t size)
{
    GNASH_REPORT_FUNCTION;

    _type = MOUSE;
    _filespec = filespec;
    _buffer.reset(new boost::uint8_t[size]);

    // see http://www.computer-engineering.org/ps2mouse/ 
    
    // Try to open mouse device, be error tolerant (FD is kept open all the time)
    _fd = open(filespec.c_str(), O_RDWR);
    
    if (_fd < 0) {
        log_debug("Could not open %s: %s", filespec, strerror(errno));
        return false;
    }
    
    if (fcntl(_fd, F_SETFL, fcntl(_fd, F_GETFL) | O_NONBLOCK) < 0) {
        log_error("Could not set non-blocking mode for mouse device: %s", strerror(errno));
        close(_fd);
        _fd = -1;
        return false; 
    }
    
    // Clear input buffer
    unsigned char buf[10], byte;
    while (read(_fd, buf, sizeof buf) > 0 ) { }

    // A touchscreen works similar to a Mouse, but not exactly...
    if (_type == InputDevice::MOUSE) {
        // Reset mouse
        if ((!command(0xFF, buf, 3)) || (buf[0] != 0xFA)) {
            log_debug(_("Mouse reset failed"));
            close(_fd);
            _fd = -1;
            return false; 
        }
        
        // Get Device ID (not crucial, debug only)
        if ((!command(0xF2, buf, 2)) || (buf[0] != 0xFA)) {
            log_debug(_("WARNING: Could not detect mouse device ID"));
        } else {
            unsigned char devid = buf[1];
            if (devid != 0)
                log_debug(_("WARNING: Non-standard mouse device ID %d"), devid);
        }
        
        // Enable mouse data reporting
        if ((!command(0xF4, &byte, 1)) || (byte != 0xFA)) {
            log_debug(_("Could not activate Data Reporting mode for mouse"));
            close(_fd);
            _fd = -1;
            return false; 
        }  
        
        log_debug("Mouse enabled for %s on fd #%d", _filespec, _fd);
    }
      
    return true;
}

// From http://www.computer-engineering.org/ps2mouse
//
// PS/2 Mouse mouse data is always in a 3 byte packet that looks like this:
//
//  	       Bit 7       Bit 6         Bit 5        Bit 4      Bit 3     Bit 2    Bit 1   Bit 0
// Byte 1 | Y overflow | X overflow | Y sign bit | X sign bit | Always 1 | Middle | Right | Left
// Byte 2                               X movement
// Byte 3                               Y movement
//
// The movement values are 9-bit 2's complement integers, where the
// most significant bit appears as a "sign" bit in byte 1 of the
// movement data packet. Their value represents the mouse's offset
// relative to its position when the previous packet was sent, in
// units determined by the current resolution. The range of values
// that can be expressed is -255 to +255. If this range is exceeded,
// the appropriate overflow bit is set.
//
// Note that reporting is disabled by default. The mouse will not
// actually issue any movement data packets until it receives the
// "Enable Data Reporting" (0xF4) command. 
//
// Stream mode is the default operating mode, and is otherwise set
// using the "Set Stream Mode" (0xEA) command.
//
// In remote mode the mouse reads its inputs and updates its
// counters/flags at the current sample rate, but it does not
// automatically issue data packets when movement has
// occured. Instead, the host polls the mouse using the "Read Data"
// (0xEB) command. Upon receiving this command the mouse will issue a
// single movement data packet and reset its movement counters. 

// The mouse enters remote mode upon receiving the "Set Remote Mode"
// (0xF0) command.
bool
MouseDevice::check()
{
    GNASH_REPORT_FUNCTION;

    if (_fd < 0) {
        return false;   // no mouse available
    }
    
    int xmove, ymove, btn;
    boost::shared_array<boost::uint8_t> buf;
    if (_type == InputDevice::TOUCHMOUSE) {
        // The eTurboTouch has a 4 byte packet
        buf = readData(4);
    } else {
        // PS/2 Mouse packets are always 3 bytes
        buf = readData(3);
    }
    
    if (!buf) {
        return false;
    }
    
    // resync
    if (!buf[0] & 8) { // bit 3 us always set in the first byte
        log_error("No sync in first byte!");
        return false;
    }

    // A Touchscreen works similar to a Mouse, but not exactly.
    // At least for the eTurboTouch, it has a different layout
    // in the packet for the location as it has an additional byte
    btn   = buf[0] & 1;
    if (_type == InputDevice::TOUCHMOUSE) {
        xmove = (buf[1] << 7) | (buf[2]);
        ymove = (buf[3] << 7) | (buf[4]);
        /*
          printf("touchscreen: %02x %02x %02x %02x %02x | status %d, pos: %d/%d\n",
          mouse_buf[0], mouse_buf[1], mouse_buf[2], mouse_buf[3], mouse_buf[4],   
          new_btn, new_x, new_y);
        */    
        
        xmove = static_cast<int>(((static_cast<double>(xmove)- 355) / (1702 - 355)
                                  * 1536 + 256));
        ymove = static_cast<int>(((static_cast<double>(ymove) - 482) / (1771 - 482)
                                  * 1536 + 256));
        
        xmove = xmove * _gui->getStage()->getStageWidth() / 2048;
        ymove = (2048-ymove) * _gui->getStage()->getStageHeight() / 2048;
    } else {                    // end of InputDevice::MOUSE
        xmove = buf[1];
        ymove = buf[2];
    
        if (buf[0] & 0x10) {
            xmove = -(256-xmove);
        }
        if (buf[0] & 0x20) {
            ymove = -(256-ymove);
        }
        
        ymove *= -1; // vertical movement is upside-down
        
        log_debug(_("x/y %d/%d button %d"), xmove, ymove, btn);
        
        // movement    
        _x += xmove;
        _y += ymove;
        
        if (_x < 0) {
            _x = 0;
        }
        if (_y < 0) {
            _y = 0;
        }
        if (_x > static_cast<int>(_gui->getStage()->getStageWidth())) {
            _x = static_cast<int>(_gui->getStage()->getStageWidth());
        }
        if (_y > static_cast<int>(_gui->getStage()->getStageHeight())) {
            _y = static_cast<int>(_gui->getStage()->getStageHeight());
        }
    } // end of InputDevice::MOUSE
    
    log_debug(_("read mouse @ %d / %d, btn %d"), _x, _y, _button);
    _gui->notifyMouseMove(_x, _y);
    
    // button
    if (btn != _button) {
        _button = btn;
        log_debug("clicked: %d", btn); 
        _gui->notifyMouseClick(btn); 
        log_debug(_("mouse click! %d"), btn);
    }
    
    return true;
}

bool
MouseDevice::command(unsigned char cmd, unsigned char *buf, int count)
{
    GNASH_REPORT_FUNCTION;

    int n;
    
    // flush input buffer
    char trash[16];
    do {
        n = read(_fd, trash, sizeof trash);
        if (n > 0) 
            log_debug(_("mouse_command: discarded %d bytes from input buffer"), n);
    } while (n > 0);
    
    // send command
    if ( -1 == write(_fd, &cmd, 1) ) {
        return false;
    }

    // read response (if any)
    while (count > 0) {
        gnashSleep(250*1000); // 250 ms inter-char timeout (simple method)
        // TODO: use select() instead
        
        n = read(_fd, buf, count);
        if (n <= 0) return false;
        count -= n;
        buf += n;
    }
    
    return true;
    
} // command()

// end of namespace
}

// local Variables:
// mode: C++
// indent-tabs-mode: nil
// End:
