#
# RTEMS Tools Project (http://www.rtems.org/)
# Copyright 2013-2014 Chris Johns (chrisj@rtems.org)
# All rights reserved.
#
# This file is part of the RTEMS Tools package in 'rtems-tools'.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#

#
# RTEMS Testing Consoles
#

import fcntl
import os
import sys
import termios

from rtemstoolkit import error
from rtemstoolkit import host
from rtemstoolkit import path


def save():
    if not host.is_windows:
        try:
            sin = termios.tcgetattr(sys.stdin)
            sout = termios.tcgetattr(sys.stdout)
            serr = termios.tcgetattr(sys.stderr)
            return sin, sout, serr
        except:
            pass
    return None


def restore(attributes):
    if attributes is not None:
        termios.tcsetattr(sys.stdin, termios.TCSANOW, attributes[0])
        termios.tcsetattr(sys.stdout, termios.TCSANOW, attributes[1])
        termios.tcsetattr(sys.stderr, termios.TCSANOW, attributes[2])


class tty(object):

    raw = 'B115200,~BRKINT,IGNBRK,IGNCR,~ICANON,~ISIG,~IEXTEN,~ECHO,CLOCAL,~CRTSCTS'

    def __init__(self, dev):
        if host.is_windows:
            raise error.general('termios not support on host')
        self.dev = dev
        self.default_attr = None
        self.fd = None
        self.if_on = False
        if host.is_windows:
            raise error.general('TTY consoles not supported on Windows.')
        if not path.exists(dev):
            raise error.general('dev not found: %s' % (dev))
        try:
            self.fd = open(dev, 'r')
        except IOError as ioe:
            raise error.general('opening tty dev: %s: %s' % (dev, ioe))
        except Exception as exp:
            raise error.general('opening tty dev: %s: %s' % (dev, exp))
        try:
            self.default_attr = termios.tcgetattr(self.fd)
        except:
            close(self.fd)
            raise error.general('cannot get termios attrs: %s' % (dev))
        try:
            fcntl.fcntl(self.fd, fcntl.F_SETFL,
                        fcntl.fcntl(self.fd, fcntl.F_GETFL) | os.O_NONBLOCK)
        except:
            close(self.fd)
            raise error.general('cannot make tty non-blocking: %s' % (dev))
        self.attr = self.default_attr

    def __del__(self):
        if self.fd and self.default_attr:
            try:
                fcntl.fcntl(
                    self.fd, fcntl.F_SETFL,
                    fcntl.fcntl(self.fd, fcntl.F_GETFL) & ~os.O_NONBLOCK)
                self.fd.close()
            except:
                pass

    def __str__(self):

        def _input(attr):
            s = ''
            if attr & termios.IGNBRK:
                s += ' IGNBRK'
            if attr & termios.BRKINT:
                s += ' BRKINT'
            if attr & termios.IGNPAR:
                s += ' IGNPAR'
            if attr & termios.PARMRK:
                s += ' PARMRK'
            if attr & termios.INPCK:
                s += ' INPCK'
            if attr & termios.ISTRIP:
                s += ' ISTRIP'
            if attr & termios.INLCR:
                s += ' INLCR'
            if attr & termios.IGNCR:
                s += ' IGNCR'
            if attr & termios.IXON:
                s += ' IXON'
            if attr & termios.IXOFF:
                s += ' IXOFF'
            if attr & termios.IXANY:
                s += ' IXANY'
            if attr & termios.IMAXBEL:
                s += ' IMAXBEL'
            return s

        def _output(attr):
            s = ''
            if attr & termios.OPOST:
                s += ' OPOST'
            if attr & termios.ONLCR:
                s += ' ONLCR'
            if attr & termios.OCRNL:
                s += ' OCRNL'
            if attr & termios.TABDLY:
                s += ' TABDLY'
            if attr & termios.TAB0:
                s += ' TAB0'
            if attr & termios.TAB3:
                s += ' TAB3'
            if attr & termios.ONOCR:
                s += ' ONOCR'
            if attr & termios.ONLRET:
                s += ' ONLRET'
            return s

        def _control(attr):
            s = ''
            if (attr & termios.CSIZE) == termios.CS5:
                s += ' CS5'
            if (attr & termios.CSIZE) == termios.CS6:
                s += ' CS6'
            if (attr & termios.CSIZE) == termios.CS7:
                s += ' CS7'
            if (attr & termios.CSIZE) == termios.CS8:
                s += ' CS8'
            if attr & termios.CSTOPB:
                s += ' CSTOPB'
            if attr & termios.CREAD:
                s += ' CREAD'
            if attr & termios.PARENB:
                s += ' PARENB'
            if attr & termios.PARODD:
                s += ' PARODD'
            if attr & termios.HUPCL:
                s += ' HUPCL'
            if attr & termios.CLOCAL:
                s += ' CLOCAL'
            if attr & termios.CRTSCTS:
                s += ' CRTSCTS'
            return s

        def _local(attr):
            s = ''
            if attr & termios.ECHOKE:
                s += ' ECHOKE'
            if attr & termios.ECHOE:
                s += ' ECHOE'
            if attr & termios.ECHO:
                s += ' ECHO'
            if attr & termios.ECHONL:
                s += ' ECHONL'
            if attr & termios.ECHOPRT:
                s += ' ECHOPRT'
            if attr & termios.ECHOCTL:
                s += ' ECHOCTL'
            if attr & termios.ISIG:
                s += ' ISIG'
            if attr & termios.ICANON:
                s += ' ICANON'
            if attr & termios.IEXTEN:
                s += ' IEXTEN'
            if attr & termios.TOSTOP:
                s += ' TOSTOP'
            if attr & termios.FLUSHO:
                s += ' FLUSHO'
            if attr & termios.PENDIN:
                s += ' PENDIN'
            if attr & termios.NOFLSH:
                s += ' NOFLSH'
            return s

        def _baudrate(attr):
            if attr == termios.B0:
                s = 'B0'
            elif attr == termios.B50:
                s = 'B50'
            elif attr == termios.B75:
                s = 'B75'
            elif attr == termios.B110:
                s = 'B110'
            elif attr == termios.B134:
                s = 'B134'
            elif attr == termios.B150:
                s = 'B150'
            elif attr == termios.B200:
                s = 'B200'
            elif attr == termios.B300:
                s = 'B300'
            elif attr == termios.B600:
                s = 'B600'
            elif attr == termios.B1800:
                s = 'B1800'
            elif attr == termios.B1200:
                s = 'B1200'
            elif attr == termios.B2400:
                s = 'B2400'
            elif attr == termios.B4800:
                s = 'B4800'
            elif attr == termios.B9600:
                s = 'B9600'
            elif attr == termios.B19200:
                s = 'B19200'
            elif attr == termios.B38400:
                s = 'B38400'
            elif attr == termios.B57600:
                s = 'B57600'
            elif attr == termios.B115200:
                s = 'B115200'
            elif attr == termios.B230400:
                s = 'B230400'
            elif attr == termios.B460800:
                s = 'B460800'
            else:
                s = 'unknown'
            return s

        if self.attr is None:
            return 'None'
        s = 'iflag: %s' % (_input(self.attr[0]))
        s += os.linesep + 'oflag: %s' % (_output(self.attr[1]))
        s += os.linesep + 'cflag: %s' % (_control(self.attr[2]))
        s += os.linesep + 'lflag: %s' % (_local(self.attr[3]))
        s += os.linesep + 'ispeed: %s' % (_baudrate(self.attr[4]))
        s += os.linesep + 'ospeed: %s' % (_baudrate(self.attr[5]))
        return s

    def _update(self):
        self.off()
        try:
            termios.tcflush(self.fd, termios.TCIOFLUSH)
            #attr = self.attr
            #attr[0] = termios.IGNPAR;
            #attr[1] = 0
            #attr[2] = termios.CRTSCTS | termios.CS8 | termios.CREAD;
            #attr[3] = 0
            #attr[6][termios.VMIN] = 1
            #attr[6][termios.VTIME] = 2
            #termios.tcsetattr(self.fd, termios.TCSANOW, attr)
            termios.tcsetattr(self.fd, termios.TCSANOW, self.attr)
            termios.tcflush(self.fd, termios.TCIOFLUSH)
        except:
            raise
        if self.is_on:
            self.on()

    def _baudrate_mask(self, flag):
        if flag == 'B0':
            mask = termios.B0
            self.attr[5] = termios.B0
        elif flag == 'B50':
            mask = termios.B50
        elif flag == 'B75':
            mask = termios.B75
        elif flag == 'B110':
            mask = termios.B110
        elif flag == 'B134':
            mask = termios.B134
        elif flag == 'B150':
            mask = termios.B150
        elif flag == 'B200':
            mask = termios.B200
        elif flag == 'B300':
            mask = termios.B300
        elif flag == 'B600':
            mask = termios.B600
        elif flag == 'B1800':
            mask = termios.B1800
        elif flag == 'B1200':
            mask = termios.B1200
        elif flag == 'B2400':
            mask = termios.B2400
        elif flag == 'B4800':
            mask = termios.B4800
        elif flag == 'B9600':
            mask = termios.B9600
        elif flag == 'B19200':
            mask = termios.B19200
        elif flag == 'B38400':
            mask = termios.B38400
        elif flag == 'B57600':
            mask = termios.B57600
        elif flag == 'B115200':
            mask = termios.B115200
        elif flag == 'B230400':
            mask = termios.B230400
        elif flag == 'B460800':
            mask = termios.B460800
        else:
            mask = None
        return mask

    def _input_mask(self, flag):
        if flag == 'IGNBRK':
            mask = termios.IGNBRK
        elif flag == 'BRKINT':
            mask = termios.BRKINT
        elif flag == 'IGNPAR':
            mask = termios.IGNPAR
        elif flag == 'PARMRK':
            mask = termios.PARMRK
        elif flag == 'INPCK':
            mask = termios.INPCK
        elif flag == 'ISTRIP':
            mask = termios.ISTRIP
        elif flag == 'INLCR':
            mask = termios.INLCR
        elif flag == 'IGNCR':
            mask = termios.IGNCR
        elif flag == 'IXON':
            mask = termios.IXON
        elif flag == 'IXOFF':
            mask = termios.IXOFF
        elif flag == 'IXANY':
            mask = termios.IXANY
        elif flag == 'IMAXBEL':
            mask = termios.IMAXBEL
        else:
            mask = None
        return mask

    def _output_mask(self, flag):
        if flag == 'OPOST':
            mask = termios.OPOST
        elif flag == 'ONLCR':
            mask = termios.ONLCR
        elif flag == 'OCRNL':
            mask = termios.OCRNL
        elif flag == 'TABDLY':
            mask = termios.TABDLY
        elif flag == 'TAB0':
            mask = termios.TAB0
        elif flag == 'TAB3':
            mask = termios.TAB3
        elif flag == 'ONOCR':
            mask = termios.ONOCR
        elif flag == 'ONLRET':
            mask = termios.ONLRET
        else:
            mask = None
        return mask

    def _control_mask(self, flag):
        if flag == 'CSTOPB':
            mask = termios.CSTOPB
        elif flag == 'CREAD':
            mask = termios.CREAD
        elif flag == 'PARENB':
            mask = termios.PARENB
        elif flag == 'PARODD':
            mask = termios.PARODD
        elif flag == 'HUPCL':
            mask = termios.HUPCL
        elif flag == 'CLOCAL':
            mask = termios.CLOCAL
        elif flag == 'CRTSCTS':
            mask = termios.CRTSCTS
        else:
            mask = None
        return mask

    def _local_mask(self, flag):
        if flag == 'ECHOKE':
            mask = termios.ECHOKE
        elif flag == 'ECHOE':
            mask = termios.ECHOE
        elif flag == 'ECHO':
            mask = termios.ECHO
        elif flag == 'ECHONL':
            mask = termios.ECHONL
        elif flag == 'ECHOPRT':
            mask = termios.ECHOPRT
        elif flag == 'ECHOCTL':
            mask = termios.ECHOCTL
        elif flag == 'ISIG':
            mask = termios.ISIG
        elif flag == 'ICANON':
            mask = termios.ICANON
        elif flag == 'IEXTEN':
            mask = termios.IEXTEN
        elif flag == 'TOSTOP':
            mask = termios.TOSTOP
        elif flag == 'FLUSHO':
            mask = termios.FLUSHO
        elif flag == 'PENDIN':
            mask = termios.PENDIN
        elif flag == 'NOFLSH':
            mask = termios.NOFLSH
        else:
            mask = None
        return mask

    def _set(self, index, mask, state):
        if state:
            self.attr[index] = self.attr[index] | mask
        else:
            self.attr[index] = self.attr[index] & ~mask

    def off(self):
        if self.fd:
            try:
                termios.tcflow(self.fd, termios.TCOOFF)
            except:
                pass
            try:
                termios.tcflow(self.fd, termios.TCIOFF)
            except:
                pass
            self.is_on = False

    def on(self):
        if self.fd:
            try:
                termios.tcflow(self.fd, termios.TCOON)
            except:
                pass
            try:
                termios.tcflow(self.fd, termios.TCION)
            except:
                pass
            self.is_on = True

    def baudrate(self, flag):
        mask = self._baudrate_mask(flag)
        if mask:
            self.attr[4] = mask
            self.attr[5] = mask
        else:
            raise error.general('invalid setting: %s' % (flag))
        self._update()

    def input(self, flag, on):
        mask = self._input_mask(flag)
        if mask is None:
            raise error.general('invalid input flag: %s' % (flag))
        self._set(0, mask, on)
        self._update()

    def output(self, flag, on):
        mask = self._output_mask(flag)
        if mask is None:
            raise error.general('invalid output flag: %s' % (flag))
        self._set(1, mask, on)
        self._update()

    def control(self, flag, on):
        mask = self._control_mask(flag)
        if mask is None:
            raise error.general('invalid control flag: %s' % (flag))
        self._set(2, mask, on)
        self._update()

    def local(self, flag, on):
        mask = self._local_mask(flag)
        if mask is None:
            raise error.general('invalid local flag: %s' % (flag))
        self._set(3, mask, on)
        self._update()

    def vmin(self, _vmin):
        self.attr[6][termios.VMIN] = _vmin

    def vtime(self, _vtime):
        self.attr[6][termios.VTIME] = _vtime

    def set(self, flags=None):
        if flags is None:
            flags = self.raw
        for f in flags.split(','):
            if len(f) < 2:
                raise error.general('invalid flag: %s' % (f))
            if f[0] == '~':
                on = False
                flag = f[1:]
            else:
                on = True
                flag = f
            if f.startswith('VMIN'):
                vs = f.split('=')
                if len(vs) != 2:
                    raise error.general('invalid vmin flag: %s' % (f))
                try:
                    _vmin = int(vs[1])
                except:
                    raise error.general('invalid vmin flag: %s' % (f))
                self.vmin(_vmin)
                continue
            if f.startswith('VTIME'):
                vs = f.split('=')
                if len(vs) != 2:
                    raise error.general('invalid vtime flag: %s' % (f))
                try:
                    _vtime = int(vs[1])
                except:
                    raise error.general('invalid vtime flag: %s' % (f))
                self.vtime(_vtime)
                continue
            mask = self._baudrate_mask(flag)
            if mask:
                if not on:
                    raise error.general('baudrates are not flags: %s' % (f))
                self.attr[4] = mask
                self.attr[5] = mask
                continue
            mask = self._input_mask(flag)
            if mask:
                self._set(0, mask, on)
                continue
            mask = self._output_mask(flag)
            if mask:
                self._set(1, mask, on)
                continue
            mask = self._control_mask(flag)
            if mask:
                self._set(2, mask, on)
                continue
            mask = self._local_mask(flag)
            if mask:
                self._set(3, mask, on)
                continue
            raise error.general('unknown tty flag: %s' % (f))
        self._update()

    def read(self):
        try:
            data = self.fd.read()
        except:
            data = b""
        return data


if __name__ == "__main__":
    if len(sys.argv) == 2:
        t = tty(sys.argv[1])
        t.baudrate('B115200')
        t.input('BRKINT', False)
        t.input('IGNBRK', True)
        t.input('IGNCR', True)
        t.local('ICANON', False)
        t.local('ISIG', False)
        t.local('IEXTEN', False)
        t.local('ECHO', False)
        t.control('CLOCAL', True)
        t.control('CRTSCTS', False)
        t.vmin(1)
        t.vtime(2)
        print(t)
        t.set(
            'B115200,~BRKINT,IGNBRK,IGNCR,~ICANON,~ISIG,~IEXTEN,~ECHO,CLOCAL,~CRTSCTS'
        )
        print(t)
        t.on()
        while True:
            c = t.fd.read(1)
            sys.stdout.write(c)
