/*
 * FnordlichtConnection.h: class to access a FnordlichtLight Hardware
 * - the description could be found
 * here: http://github.com/fd0/fnordlicht/raw/master/doc/PROTOCOL
 *
 * (C) Kai Lauterbach (klaute at gmail.com)
 *
 *  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 2 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 * $Id: 4f026e5cd6d338f260b94458b5eb56a43c82402c $
 */


#include "AtmoDefs.h"
#include "FnordlichtConnection.h"

#if !defined(_ATMO_VLC_PLUGIN_)
# include "FnordlichtConfigDialog.h"
#endif

#include <stdio.h>
#include <fcntl.h>

#if !defined(WIN32)
#include <termios.h>
#include <unistd.h>
#endif

CFnordlichtConnection::CFnordlichtConnection(CAtmoConfig *cfg)
    : CAtmoConnection(cfg)
{
    m_hComport = INVALID_HANDLE_VALUE;
}

CFnordlichtConnection::~CFnordlichtConnection()
{
}

ATMO_BOOL CFnordlichtConnection::OpenConnection()
{
#if defined(_ATMO_VLC_PLUGIN_)
    char *serdevice = m_pAtmoConfig->getSerialDevice();
    if ( !serdevice )
        return ATMO_FALSE;
#else
    int portNummer = m_pAtmoConfig->getComport();
    m_dwLastWin32Error = 0;
    if ( portNummer < 1 )
        return ATMO_FALSE; // make no real sense;-)
#endif

    CloseConnection();

#if !defined(_ATMO_VLC_PLUGIN_)
    char serdevice[16];  // com4294967295
    sprintf(serdevice,"com%d",portNummer);
#endif

#if defined(WIN32)

    m_hComport = CreateFile(serdevice,
                    GENERIC_WRITE, 0, NULL,
                    OPEN_EXISTING, 0, NULL);
    if ( m_hComport == INVALID_HANDLE_VALUE )
    {
        // we have a problem here can't open com port...
        // somebody else may use it?
        // m_dwLastWin32Error = GetLastError();
        return ATMO_FALSE;
    }
    /* change serial settings (Speed, stopbits etc.) */
    DCB dcb; // für comport-parameter
    dcb.DCBlength = sizeof(DCB);
    GetCommState(m_hComport, &dcb); // ger current serialport settings
    dcb.BaudRate = 19200;       // set speed
    dcb.ByteSize = 8;          // set databits
    dcb.Parity   = NOPARITY;    // set parity
    dcb.StopBits = ONESTOPBIT;   // set one stop bit
    SetCommState(m_hComport, &dcb);    // apply settings

#else

    int bconst = B19200;
    m_hComport = open(serdevice,O_RDWR |O_NOCTTY);
    if ( m_hComport < 0 )
        return ATMO_FALSE;

    struct termios tio;
    memset(&tio,0,sizeof(tio));
    tio.c_cflag = (CS8 | CREAD | HUPCL | CLOCAL);
    tio.c_iflag = (INPCK | BRKINT);
    cfsetispeed(&tio, bconst);
    cfsetospeed(&tio, bconst);
    if( ! tcsetattr(m_hComport, TCSANOW, &tio) )
        tcflush(m_hComport, TCIOFLUSH);
    else {
        // can't change parms
        close(m_hComport);
        m_hComport = -1;
        return false;
    }

#endif

    // sync fnordlicht
    if ( sync() )
        // stop fading on all devices
        if ( stop(255) )
            return true; // fnordlicht initialized...

    return false; // something is going wrong...
}

void CFnordlichtConnection::CloseConnection()
{
    if ( m_hComport != INVALID_HANDLE_VALUE )
    {
        reset(255);

#if defined(WIN32)
        CloseHandle(m_hComport);
#else
        close(m_hComport);
#endif
        m_hComport = INVALID_HANDLE_VALUE;
    }
}

ATMO_BOOL CFnordlichtConnection::isOpen(void)
{
    return (m_hComport != INVALID_HANDLE_VALUE);
}

ATMO_BOOL CFnordlichtConnection::HardwareWhiteAdjust(int global_gamma,
                                           int global_contrast,
                                           int contrast_red,
                                           int contrast_green,
                                           int contrast_blue,
                                           int gamma_red,
                                           int gamma_green,
                                           int gamma_blue,
                                           ATMO_BOOL storeToEeprom)
{
    return ATMO_FALSE; //no hardware adjust required
}

/*
    def fade_rgb(addr, r, g, b, step, delay)
       $dev.write addr.chr
       $dev.write "\x01"
       $dev.write step.chr
       $dev.write delay.chr
       $dev.write r.chr
       $dev.write g.chr
       $dev.write b.chr
       $dev.write "\x00\x00\x00\x00\x00"
       $dev.write "\x00\x00\x00"
       $dev.flush
    end
*/
ATMO_BOOL CFnordlichtConnection::SendData(pColorPacket data)
{
    if ( m_hComport == INVALID_HANDLE_VALUE )
        return ATMO_FALSE;

    int amount = getAmountFnordlichter();
    unsigned char buffer[15];
    memset(&buffer, 0, sizeof(buffer) ); // zero buffer
    int iBytesWritten;

    Lock();

    buffer[1] = 0x01; // fade to rgb color
    buffer[2] = 0x80; // in two steps
    buffer[3] = 0x01; // 1ms pause between steps

    // send all packages to all fnordlicht's
    for( unsigned char i=0; i < amount; i++ )
    {
        int idx;
        if ( m_ChannelAssignment && i < m_NumAssignedChannels )
            idx = m_ChannelAssignment[i];
        else
            idx = -1; // no channel assigned to fnordlicht[i]

        if( idx >= 0 && idx <= data->numColors )
        {
            // fnordlicht address equals to a MoMo Channel
            buffer[0] = i; // fnordlicht address (0..254, 255 = broadcast)
            buffer[4] = data->zone[idx].r;
            buffer[5] = data->zone[idx].g;
            buffer[6] = data->zone[idx].b;
        }

#if defined(WIN32)
        // send to COM-Port
        WriteFile( m_hComport, buffer, sizeof(buffer),
                    (DWORD*)&iBytesWritten, NULL );
#else
        iBytesWritten = write(m_hComport, buffer, sizeof(buffer));
        tcflush(m_hComport, TCIOFLUSH);
        tcdrain(m_hComport); // flush buffer
#endif

        if (iBytesWritten != sizeof(buffer))
        {
            Unlock();
            return ATMO_FALSE; // shouldn't be...
        }

    }

    Unlock();

    return ATMO_TRUE;
}


ATMO_BOOL CFnordlichtConnection::CreateDefaultMapping(CAtmoChannelAssignment *ca)
{
    if ( !ca )
        return ATMO_FALSE;
    ca->setSize( getAmountFnordlichter() );  // oder 4 ? depending on config!
    ca->setZoneIndex(0, 0); // Zone 5
    ca->setZoneIndex(1, 1);
    ca->setZoneIndex(2, 2);
    ca->setZoneIndex(3, 3);
    return ATMO_TRUE;
}

int CFnordlichtConnection::getAmountFnordlichter()
{
   return m_pAtmoConfig->getFnordlicht_Amount();
}

/*
    def sync(addr = 0)
       1.upto(15) do
          $dev.write "\e"
       end
       $dev.write addr.chr
       $dev.flush
    end
*/
ATMO_BOOL CFnordlichtConnection::sync(void)
{
    if ( m_hComport == INVALID_HANDLE_VALUE )
        return ATMO_FALSE;

    unsigned char buffer[16];

    int iBytesWritten;

    Lock();

    // fill buffer with 15 escape character
    memset(&buffer, 0x1b, sizeof(buffer)-1);

    buffer[sizeof(buffer)-1] = 0x00; // append one zero byte

#if defined(WIN32)
        // send to COM-Port
        WriteFile( m_hComport, buffer, sizeof(buffer),
                    (DWORD*)&iBytesWritten, NULL );
#else
        iBytesWritten = write(m_hComport, buffer, sizeof(buffer));
        tcflush(m_hComport, TCIOFLUSH);
        tcdrain(m_hComport); // flush buffer
#endif

    Unlock();

    return (iBytesWritten == sizeof(buffer)) ? ATMO_TRUE : ATMO_FALSE;

}

/*
    def stop(addr, fading = 1)
       $dev.write addr.chr
       $dev.write "\x08"
       $dev.write fading.chr
       $dev.write "\x00\x00\x00\x00"
       $dev.write "\x00\x00\x00\x00\x00"
       $dev.write "\x00\x00\x00"
       $dev.flush
    end
*/
ATMO_BOOL CFnordlichtConnection::stop(unsigned char addr)
{
    if(m_hComport == INVALID_HANDLE_VALUE)
        return ATMO_FALSE;

    unsigned char buffer[15];
    memset(&buffer, 0, sizeof(buffer)); // zero buffer
    int iBytesWritten;

    Lock();

    buffer[0] = addr; // fnordlicht address (255 = broadcast)
    buffer[1] = 0x08; // stop command
    buffer[2] = 1;    // fading

#if defined(WIN32)
        // send to COM-Port
        WriteFile( m_hComport, buffer, sizeof(buffer),
                    (DWORD*)&iBytesWritten, NULL );
#else
        iBytesWritten = write(m_hComport, buffer, sizeof(buffer));
        tcflush(m_hComport, TCIOFLUSH);
        tcdrain(m_hComport); // flush buffer
#endif

    Unlock();

    return (iBytesWritten == sizeof(buffer)) ? ATMO_TRUE : ATMO_FALSE;
}

/*
*/
ATMO_BOOL CFnordlichtConnection::reset(unsigned char addr)
{
    if(m_hComport == INVALID_HANDLE_VALUE)
        return ATMO_FALSE;

    stop(255);

    if ( sync() && start_bootloader(addr) )
    {
#if defined(_ATMO_VLC_PLUGIN_)
        do_sleep(200000); // wait 200ms
#else
        do_sleep(200); // wait 200ms
#endif
        if ( sync() && boot_enter_application(addr) )
                return ATMO_TRUE;
    }

    return ATMO_FALSE;
}

/*
    def start_bootloader(addr)
        $dev.write(addr.chr)
        $dev.write("\x80")
        $dev.write("\x6b\x56\x27\xfc")
        $dev.write("\x00\x00\x00\x00\x00\x00\x00\x00\x00")
        $dev.flush
    end
*/
ATMO_BOOL CFnordlichtConnection::start_bootloader(unsigned char addr)
{
    if(m_hComport == INVALID_HANDLE_VALUE)
        return ATMO_FALSE;

    unsigned char buffer[15];
    memset(&buffer, 0, sizeof(buffer)); // zero buffer
    int iBytesWritten;

    Lock();

    buffer[0] = addr; // fnordlicht address (255 = broadcast)
    buffer[1] = 0x80; // start_bootloader
    buffer[2] = 0x6b;
    buffer[3] = 0x56;
    buffer[4] = 0x27;
    buffer[5] = 0xfc;

#if defined(WIN32)
        // send to COM-Port
        WriteFile( m_hComport, buffer, sizeof(buffer),
                    (DWORD*)&iBytesWritten, NULL );
#else
        iBytesWritten = write(m_hComport, buffer, sizeof(buffer));
        tcflush(m_hComport, TCIOFLUSH);
        tcdrain(m_hComport); // flush buffer
#endif

    Unlock();

    return (iBytesWritten == sizeof(buffer)) ? ATMO_TRUE : ATMO_FALSE;
}

/*
    def boot_enter_application(addr)
        $dev.write(addr.chr)
        $dev.write("\x87")
        $dev.write("\x00"*13)
        $dev.flush
    end
*/
ATMO_BOOL CFnordlichtConnection::boot_enter_application(unsigned char addr)
{
    if(m_hComport == INVALID_HANDLE_VALUE)
        return ATMO_FALSE;

    unsigned char buffer[15];
    memset(&buffer, 0, sizeof(buffer)); // zero buffer
    int iBytesWritten;

    Lock();

    buffer[0] = addr; // fnordlicht address (255 = broadcast)
    buffer[1] = 0x87; // boot_ender_application command

#if defined(WIN32)
        // send to COM-Port
        WriteFile( m_hComport, buffer, sizeof(buffer),
                    (DWORD*)&iBytesWritten, NULL );
#else
        iBytesWritten = write(m_hComport, buffer, sizeof(buffer));
        tcflush(m_hComport, TCIOFLUSH);
        tcdrain(m_hComport); // flush buffer
#endif

    Unlock();

    return (iBytesWritten == sizeof(buffer)) ? ATMO_TRUE : ATMO_FALSE;
}

#if !defined(_ATMO_VLC_PLUGIN_)

char *CFnordlichtConnection::getChannelName(int ch)
{
    char buf[60];
    if( ch < 0 ) return NULL;
    if( ch >= getAmountFnordlichter() ) return NULL;

    sprintf(buf,"Number [%d]",ch);
    return strdup(buf);
    // sorry asprintf is not defined on Visual Studio :)
    // return (asprintf(&ret, "Number [%d]", ch) != -1) ? ret : NULL;

}

ATMO_BOOL CFnordlichtConnection::ShowConfigDialog(HINSTANCE hInst, HWND parent,
                                                    CAtmoConfig *cfg)
{
    CFnordlichtConfigDialog *dlg = new CFnordlichtConfigDialog(hInst,
                                                                parent,
                                                                cfg);

    INT_PTR result = dlg->ShowModal();

    delete dlg;

    if(result == IDOK)
        return ATMO_TRUE;
    else
        return ATMO_FALSE;
}

#endif
