/*
 * Copyright (C) 2001-2003 by egnite Software GmbH. All rights reserved.
 *
 * 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.
 * 3. Neither the name of the copyright holders nor the names of
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * 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 OWNER 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.
 *
 * For additional information see http://www.ethernut.de/
 *
 */

/*
 * $Log$
 * Revision 1.8  2009/02/13 14:52:05  haraldkipp
 * Include memdebug.h for heap management debugging support.
 *
 * Revision 1.7  2008/08/11 06:59:42  haraldkipp
 * BSD types replaced by stdint types (feature request #1282721).
 *
 * Revision 1.6  2005/08/02 17:46:47  haraldkipp
 * Major API documentation update.
 *
 * Revision 1.5  2004/05/24 17:11:05  olereinhardt
 * dded terminal device driver for hd44780 compatible LCD displays directly
 * connected to the memory bus (memory mapped). See hd44780.c for more
 * information.Therefore some minor changed in include/dev/term.h and
 * dev/term.c are needet to
 * pass a base address to the lcd driver.
 *
 * Revision 1.4  2004/03/18 18:30:11  haraldkipp
 * Added Michael Fischer's TIOCGWINSZ ioctl
 *
 * Revision 1.3  2004/03/18 14:02:46  haraldkipp
 * Comments updated
 *
 * Revision 1.2  2004/03/16 16:48:27  haraldkipp
 * Added Jan Dubiec's H8/300 port.
 *
 * Revision 1.1.1.1  2003/05/09 14:40:52  haraldkipp
 * Initial using 3.2.1
 *
 * Revision 1.3  2003/05/06 18:34:22  harald
 * Cleanup
 *
 * Revision 1.2  2003/04/21 16:25:24  harald
 * Release prep
 *
 * Revision 1.1  2003/03/31 14:53:08  harald
 * Prepare release 3.1
 *
 */

#include <dev/term.h>

#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <memdebug.h>

/*!
 * \addtogroup xgTerminal
 */

/*@{*/

static const char termid[] = "Term 1.0";

static void TermRefreshLineEnd(const TERMDCB * dcb, uint8_t row, uint8_t col)
{
    uint8_t i = col;
    uint8_t *cp = dcb->dcb_smem + row * dcb->dcb_vcols + col;

    /* Disable cursor to avoid flickering. */
    if (dcb->dcb_modeflags & LCD_MF_CURSORON)
        (*dcb->dss_cursor_mode) (0);

    /* Position cursor to the rwo and column to refresh. */
    (*dcb->dss_set_cursor) (row * dcb->dcb_ncols + col);

    /*
     * This loop looks weird. But it was the only way I found to get
     * around a GCC bug in reload1.c:1920.
     */
    for (;;) {
        if (i++ >= dcb->dcb_vcols)
            break;
        (*dcb->dss_write) (*cp++);
    }

    /* Re-enable cursor. */
    if (dcb->dcb_modeflags & LCD_MF_CURSORON)
        (*dcb->dss_cursor_mode) (1);
}

void TermRefresh(TERMDCB * dcb)
{
    uint8_t ir;

    for (ir = 0; ir < dcb->dcb_nrows; ir++)
        TermRefreshLineEnd(dcb, ir, 0);
    (*dcb->dss_set_cursor) (dcb->dcb_row * dcb->dcb_ncols + dcb->dcb_col);
}

static void TermClear(TERMDCB * dcb)
{
    memset(dcb->dcb_smem, ' ', dcb->dcb_vcols * dcb->dcb_nrows);
    dcb->dcb_col = 0;
    dcb->dcb_row = 0;
    (*dcb->dss_clear) ();
}

static void TermDeleteLine(TERMDCB * dcb, uint8_t row)
{
    uint8_t i;
    uint8_t *dcp;

    for (i = row; i < dcb->dcb_nrows - 1; i++) {
        dcp = dcb->dcb_smem + i * dcb->dcb_vcols;
        memcpy(dcp, dcp + dcb->dcb_vcols, dcb->dcb_vcols);
    }
    memset(dcb->dcb_smem + (dcb->dcb_nrows - 1) * dcb->dcb_vcols, ' ', dcb->dcb_vcols);
    TermRefresh(dcb);
}

static void TermInsertLine(TERMDCB * dcb, uint8_t row)
{
    uint8_t i;
    uint8_t *dcp;

    for (i = dcb->dcb_nrows - 1; i > row; i--) {
        dcp = dcb->dcb_smem + i * dcb->dcb_vcols;
        memcpy(dcp, dcp - dcb->dcb_vcols, dcb->dcb_vcols);
    }
    memset(dcb->dcb_smem + row * dcb->dcb_vcols, ' ', dcb->dcb_vcols);
    TermRefresh(dcb);
}

static void TermCursorLeft(TERMDCB * dcb)
{
    if (dcb->dcb_col) {
        (*dcb->dss_cursor_left) ();
        dcb->dcb_col--;
    }
}

static void TermCursorRight(TERMDCB * dcb)
{
    if (++dcb->dcb_col < dcb->dcb_vcols)
        (*dcb->dss_cursor_right) ();
    else
        dcb->dcb_col = dcb->dcb_vcols - 1;
}

static void TermCursorUp(TERMDCB * dcb)
{
    if (dcb->dcb_row) {
        dcb->dcb_row--;
        (*dcb->dss_set_cursor) (dcb->dcb_row * dcb->dcb_ncols + dcb->dcb_col);
    }
}

static void TermLinefeed(TERMDCB * dcb)
{
    if (++dcb->dcb_row >= dcb->dcb_nrows) {
        dcb->dcb_row = dcb->dcb_nrows - 1;
        TermDeleteLine(dcb, 0);
    } else
        (*dcb->dss_set_cursor) (dcb->dcb_row * dcb->dcb_ncols + dcb->dcb_col);
}

static void TermReverseLinefeed(TERMDCB * dcb)
{
    if (dcb->dcb_row--)
        (*dcb->dss_set_cursor) (dcb->dcb_row * dcb->dcb_ncols + dcb->dcb_col);
    else {
        dcb->dcb_row = 0;
        TermInsertLine(dcb, 0);
    }
}

static void TermEraseLineEnd(TERMDCB * dcb, uint8_t col)
{
    if (col < dcb->dcb_vcols) {
        memset(dcb->dcb_smem + dcb->dcb_row * dcb->dcb_vcols + col, ' ', dcb->dcb_vcols - col);
        TermRefresh(dcb);
    }
}

static void TermEraseEnd(TERMDCB * dcb)
{
    uint8_t i;

    if (dcb->dcb_col < dcb->dcb_vcols)
        memset(dcb->dcb_smem + dcb->dcb_row * dcb->dcb_vcols + dcb->dcb_col, ' ', dcb->dcb_vcols - dcb->dcb_col);
    for (i = dcb->dcb_row + 1; i < dcb->dcb_nrows; i++)
        memset(dcb->dcb_smem + i * dcb->dcb_vcols, ' ', dcb->dcb_vcols);
    TermRefresh(dcb);
}

static void TermEraseLineStart(TERMDCB * dcb)
{
    if (dcb->dcb_col) {
        memset(dcb->dcb_smem + dcb->dcb_row * dcb->dcb_vcols, ' ', dcb->dcb_col);
        TermRefresh(dcb);
    }
}

static void TermEraseStart(TERMDCB * dcb)
{
    uint8_t i;

    if (dcb->dcb_col)
        memset(dcb->dcb_smem + dcb->dcb_row * dcb->dcb_vcols, ' ', dcb->dcb_col);
    for (i = 0; i < dcb->dcb_row; i++)
        memset(dcb->dcb_smem + i * dcb->dcb_vcols, ' ', dcb->dcb_vcols);
    TermRefresh(dcb);
}

static void TermDeleteChar(TERMDCB * dcb, uint8_t col)
{
    uint8_t i;
    uint8_t *cp = dcb->dcb_smem + dcb->dcb_row * dcb->dcb_vcols + col;

    for (i = col; i < dcb->dcb_vcols - 1; i++, cp++)
        *cp = *(cp + 1);
    *cp = ' ';
    TermRefresh(dcb);
}

/*
 * Insert a space at the cursor position.
 */
static void TermInsertSpace(TERMDCB * dcb)
{
    uint8_t i;
    uint8_t *cp = dcb->dcb_smem + (dcb->dcb_row + 1) * dcb->dcb_vcols - 1;

    for (i = dcb->dcb_col; i < dcb->dcb_vcols - 1; i++, cp--)
        *cp = *(cp - 1);
    *cp = ' ';
    TermRefreshLineEnd(dcb, dcb->dcb_row, dcb->dcb_col);
    (*dcb->dss_set_cursor) (dcb->dcb_row * dcb->dcb_ncols + dcb->dcb_col);
}

/*
 * Clear display and print identification.
 */
static void TermIdentify(TERMDCB * dcb)
{
    const char *pcp = termid;

    TermClear(dcb);
    while (*pcp) {
        (*dcb->dss_write) (*pcp);
        pcp++;
    }
}

/*!
 * \brief Perform special LCD control functions.
 *
 * \param dev  Identifies the device that receives the device-control
 *             function.
 * \param req  Requested control function. May be set to one of the
 *             following constants:
 * - LCD_CMDBYTE     Send command byte to display. Parameter conf points
 *                   to  an u_char value containing the command byte.
 * - LCD_CMDWORD16   Send 16 bit command word to display. Parameter conf
 *                   points to an uint16_t value containing the command
 *                   bytes. The most significant byte is send first.
 * - LCD_CMDWORD32   Send 32 bit command byte to display. Parameter conf
 *                   points to an uint32_t value containing the command
 *                   bytes. The most significant bytes are send first.
 * - LCD_DATABYTE    Send raw data byte to display. Parameter conf points
 *                   to an u_char value containing the data byte.
 * - LCD_DATAWORD16  Send 16 bit raw data word to display. Parameter conf
 *                   points to an uint16_t value containing the data
 *                   bytes. The most significant byte is send first.
 * - LCD_DATAWORD32  Send 32 bit raw data word to display. Parameter conf
 *                   points to an uint32_t value containing the data
 *                   bytes. The most significant bytes are send first.
 * - LCD_SETCOOKEDMODE Set terminal control character mode. Parameter conf
 *                     points to an uint32_t value containing 0 (off) or 1
 *                     (on).
 * - LCD_GETCOOKEDMODE Query terminal control character mode. Parameter
 *                     conf points to an uint32_t value receiving 0 (off)
 *                     or 1 (on).
 *
 * \param conf Points to a buffer that contains any data required for
 *             the given control function or receives data from that
 *             function.
 *
 * \return 0 on success, -1 otherwise.
 *
 */
int TermIOCtl(NUTDEVICE * dev, int req, void *conf)
{
    TERMDCB *dcb = dev->dev_dcb;
    uint16_t usv;
    uint32_t ulv;
    WINSIZE *win_size;

    switch (req) {
    case LCD_CMDBYTE:
        (*dcb->dss_command) (*(uint8_t *)conf, 10);
        break;
    case LCD_CMDWORD16:
        usv = *(uint16_t *)conf;
        (*dcb->dss_command) ((uint8_t)(usv >> 8), 10);
        (*dcb->dss_command) ((uint8_t)usv, 10);
        break;
    case LCD_CMDWORD32:
        ulv = *(uint32_t *)conf;
        (*dcb->dss_command) ((uint8_t)(ulv >> 24), 10);
        (*dcb->dss_command) ((uint8_t)(ulv >> 16), 10);
        (*dcb->dss_command) ((uint8_t)(ulv >> 8), 10);
        (*dcb->dss_command) ((uint8_t)ulv, 10);
        break;
    case LCD_DATABYTE:
        (*dcb->dss_write) (*(uint8_t *)conf);
        break;
    case LCD_DATAWORD16:
        usv = *(uint16_t *)conf;
        (*dcb->dss_write) ((uint8_t)(usv >> 8));
        (*dcb->dss_write) ((uint8_t)usv);
        break;
    case LCD_DATAWORD32:
        ulv = *(uint32_t *)conf;
        (*dcb->dss_write) ((uint8_t)(ulv >> 24));
        (*dcb->dss_write) ((uint8_t)(ulv >> 16));
        (*dcb->dss_write) ((uint8_t)(ulv >> 8));
        (*dcb->dss_write) ((uint8_t)ulv);
        break;
    case LCD_SETCOOKEDMODE:
        if (*(uint32_t *)conf)
            dcb->dcb_modeflags |= LCD_MF_COOKEDMODE;
        else
            dcb->dcb_modeflags &= ~LCD_MF_COOKEDMODE;
        break;
    case LCD_GETCOOKEDMODE:
        if (dcb->dcb_modeflags & LCD_MF_COOKEDMODE)
            *(uint32_t *)conf = 1;
        else
            *(uint32_t *)conf = 0;
        break;

    case LCD_SET_AUTOLF:
        if (*(uint32_t *)conf)
            dcb->dcb_modeflags |= LCD_MF_AUTOLF;
        else
            dcb->dcb_modeflags &= ~LCD_MF_AUTOLF;
        break;
    case LCD_GET_AUTOLF:
        if (dcb->dcb_modeflags & LCD_MF_AUTOLF)
            *(uint32_t *)conf = 1;
        else
            *(uint32_t *)conf = 0;
        break;

    case TIOCGWINSZ:
        win_size = (WINSIZE *)conf;
        win_size->ws_col    = dcb->dcb_nrows;
        win_size->ws_row    = dcb->dcb_vcols;
        win_size->ws_xpixel = 0;
        win_size->ws_ypixel = 0;
        break;
    }
    return 0;
}


/*!
 * \brief Initialize the terminal device.
 *
 * Prepares the device for subsequent writing.
 *
 * Application should not call this function directly, but use the
 * stdio interface.
 *
 * \param dev  Identifies the device to initialize.
 *
 * \return 0 on success, -1 otherwise.
 */
int TermInit(NUTDEVICE * dev)
{
    TERMDCB *dcb = dev->dev_dcb;

    /*
     * Check if initialisazion needed.
     */
    if( dcb->dss_init != NULL) {
        /*
         * Initialize the display hardware.
         */
        if(  dcb->dss_init(dev) != 0) {
            return -1;
        }
    }
    /*
     * Initialize driver control block.
     */
    dcb->dcb_smem = malloc(dcb->dcb_nrows * dcb->dcb_vcols);
    if( dcb->dcb_smem == NULL) {
        return -1;
    }

    TermClear(dcb);

    return 0;
}

/*!
 * \brief Write to the terminal device.
 *
 * \param fp     File pointer to a previously opened device.
 * \param buffer Pointer to the data bytes to be written.
 * \param len    Number of bytes to write.
 * \param pflg   Set if the buffer is located in program space.
 *
 * \return The number of bytes written.
 *
 */
static int TermPut(NUTDEVICE * dev, const void *buffer, int len, int pflg)
{
    int rc;
    const uint8_t *cp;
    uint8_t ch;
    TERMDCB *dcb = dev->dev_dcb;

    /*
     * Call without data pointer is accepted.
     */
    if (buffer == 0)
        return 0;

    /*
     * Put characters in transmit buffer.
     */
    cp = buffer;
    for (rc = 0; rc < len; cp++, rc++) {
        ch = pflg ? PRG_RDB(cp) : *cp;

        if (dcb->dcb_modeflags & LCD_MF_COOKEDMODE) {
            /* Process special characters. */
            if (dcb->dcb_ctlseq == 0) {

                /* Linefeed. */
                if (ch == 10) {
                    dcb->dcb_col = 0;
                    TermLinefeed(dcb);
                    continue;
                }

                /* Carriage return. */
                if (ch == 13) {
                    dcb->dcb_col = 0;
                    (*dcb->dss_set_cursor) (dcb->dcb_row * dcb->dcb_ncols);
                    continue;
                }

                /* Escape. */
                if (ch == 27) {
                    dcb->dcb_ctlseq = 1;
                    continue;
                }

                /* Backspace. */
                if (ch == 8) {
                    if (dcb->dcb_col) {
                        dcb->dcb_col--;
                        TermDeleteChar(dcb, dcb->dcb_col);
                    }
                    continue;
                }

                /* Formfeed. */
                if (ch == 12) {
                    TermClear(dcb);
                    continue;
                }
            }

            /* Last character was ESC. */
            if (dcb->dcb_ctlseq == 1) {
                dcb->dcb_ctlseq = 0;

                switch (ch) {
                    /* Insert space. */
                case '@':
                    TermInsertSpace(dcb);
                    break;

                    /* Cursor up. */
                case 'A':
                    TermCursorUp(dcb);
                    break;

                    /* Cursor down. */
                case 'B':
                    if (++dcb->dcb_row >= dcb->dcb_nrows)
                        dcb->dcb_row = dcb->dcb_nrows - 1;
                    else
                        (*dcb->dss_set_cursor) (dcb->dcb_row * dcb->dcb_ncols + dcb->dcb_col);
                    break;

                    /* Cursor right. */
                case 'C':
                    TermCursorRight(dcb);
                    break;

                    /* Cursor left. */
                case 'D':
                    TermCursorLeft(dcb);
                    break;

                    /* Clear screen and cursor home. */
                case 'E':
                    TermClear(dcb);
                    break;

                    /* Cursor home. */
                case 'H':
                    dcb->dcb_col = 0;
                    dcb->dcb_row = 0;
                    (*dcb->dss_cursor_home) ();
                    break;

                    /* Reverse linefeed. */
                case 'I':
                    TermReverseLinefeed(dcb);
                    break;

                    /* Erase to end of screen. */
                case 'J':
                    TermEraseEnd(dcb);
                    break;

                    /* Erase to end of line. */
                case 'K':
                    TermEraseLineEnd(dcb, dcb->dcb_col);
                    break;

                    /* Insert line. */
                case 'L':
                    TermInsertLine(dcb, dcb->dcb_row);
                    break;

                    /* Delete line. */
                case 'M':
                    TermDeleteLine(dcb, dcb->dcb_row);
                    break;

                    /* Delete character. */
                case 'P':
                    TermDeleteChar(dcb, dcb->dcb_col);
                    break;

                    /* Cursor position. */
                case 'Y':
                    dcb->dcb_ctlseq = 2;
                    break;

                    /* Identify. */
                case 'Z':
                    TermIdentify(dcb);
                    break;

                    /* Cursor on. */
                case 'e':
                    dcb->dcb_modeflags |= LCD_MF_CURSORON;
                    (*dcb->dss_cursor_mode) (1);
                    break;

                    /* Cursor off. */
                case 'f':
                    dcb->dcb_modeflags &= ~LCD_MF_CURSORON;
                    (*dcb->dss_cursor_mode) (0);
                    break;

                    /* Erase to start of screen. */
                case 'd':
                    TermEraseStart(dcb);
                    break;

                    /* Erase to start of line. */
                case 'o':
                    TermEraseLineStart(dcb);
                    break;

                case 'i':
                    dcb->dcb_modeflags |= LCD_MF_INVERTED;
                    (*dcb->dss_cursor_mode) (3);
                    break;

                case 'n':
                    dcb->dcb_modeflags &= ~LCD_MF_INVERTED;
                    (*dcb->dss_cursor_mode) (2);
                    break;
                }
                continue;
            }

            /* Receive cursor row position. */
            if (dcb->dcb_ctlseq == 2) {
                dcb->dcb_ctlseq = 3;
                if (ch < 32)
                    dcb->dcb_row = 0;
                else if (ch - 32 >= dcb->dcb_nrows)
                    dcb->dcb_row = dcb->dcb_nrows - 1;
                else
                    dcb->dcb_row = ch - 32;
                continue;
            }

            /* Receive cursor column position. */
            if (dcb->dcb_ctlseq == 3) {
                dcb->dcb_ctlseq = 0;
                if (ch < 32)
                    dcb->dcb_col = 0;
                else if (ch - 32 >= dcb->dcb_vcols)
                    dcb->dcb_col = dcb->dcb_vcols - 1;
                else
                    dcb->dcb_col = ch - 32;
                (*dcb->dss_set_cursor) (dcb->dcb_row * dcb->dcb_ncols + dcb->dcb_col);
                continue;
            }
        }

        /*
         * Send any character to the LCD driver, which had been left
         * unprocessed upto this point.
         */
        (*dcb->dss_write) (ch);

        if (dcb->dcb_modeflags & LCD_MF_COOKEDMODE) {
            /* Update shadow memory. */
            *(dcb->dcb_smem + dcb->dcb_row * dcb->dcb_vcols + dcb->dcb_col) = ch;

            /* step cursor forward */
            if (++dcb->dcb_col >= dcb->dcb_vcols) {
                if( dcb->dcb_modeflags & LCD_MF_AUTOLF) {
                    dcb->dcb_col = 0;
                    if( dcb->dcb_row < dcb->dcb_nrows) dcb->dcb_row++;
                }
                else
                    dcb->dcb_col = dcb->dcb_vcols - 1;
                (*dcb->dss_set_cursor) (dcb->dcb_row * dcb->dcb_ncols + dcb->dcb_col);
            }
        }
    }
    return rc;
}

/*!
 * \brief Write data to a terminal device.
 *
 * Application should not call this function directly, but use the
 * stdio interface.
 *
 * The data may contain special character sequences, which are interpreted
 * by the terminal device to control specific display functions:
 * - Ctrl-H (ASCII 8) Backspace
 * - Ctrl-J (ASCII 10) Linefeed
 * - Ctrl-L (ASCII 12) Formfeed
 * - Ctrl-M (ASCII 13) Carriage return
 *
 * In addition a superset of VT52 control sequences are interpreted. Each
 * sequence starts with an ESC character (ASCII 27):
 * - ESC @ Insert space(*).
 * - ESC A Move cursor up, ignored if on first line.
 * - ESC B Move cursor down, ignored if on last line.
 * - ESC C Move cursor right, ignored if on last column.
 * - ESC D Move cursor left, ignored if on first column.
 * - ESC E Clear display and move cursor home (*).
 * - ESC H Move cursor home.
 * - ESC I Reverse linefeed.
 * - ESC J Erase to end of display.
 * - ESC K Erase to end of line.
 * - ESC L Insert line (*).
 * - ESC M Delete line (*).
 * - ESC P Delete character (*).
 * - ESC R Reactivate display (*).
 * - ESC S Put display in sleep mode (*).
 * - ESC Y x+32 y+32 Move cursor to position.
 * - ESC e Cursor on (*).
 * - ESC f Cursor off (*).
 * - ESC d Erase to start of display (*).
 * - ESC o Erase to start of line (*).
 *
 * (*) Not a VT52 command.
 *
 * \param fp     File pointer to a previously opened device.
 * \param buffer Pointer to the data bytes to be written.
 * \param len    Number of bytes to write.
 *
 * \return The number of bytes written.
 *
 * \todo TAB should be interpreted.
 *
 * \bug Switching from graphic mode back to text mode will not work,
 *      because all escape sequences are ignored as soon as the graphic
 *      mode has been enabled. Applications should use _ioctl()
 *      functions to switch modes.
 */
int TermWrite(NUTFILE * fp, const void *buffer, int len)
{
    return TermPut(fp->nf_dev, buffer, len, 0);
}

/*!
 * \brief Write data from program space to a terminal device.
 *
 * Similar to TermWrite() except that the data is located in program memory.
 *
 * Application should not call this function directly, but use the
 * stdio interface.
 *
 * \param fp     File pointer to a previously opened device.
 * \param buffer Pointer to the data bytes in program space to be written.
 * \param len    Number of bytes to write.
 *
 * \return The number of bytes written.
 */
#ifdef __HARVARD_ARCH__
int TermWrite_P(NUTFILE * fp, PGM_P buffer, int len)
{
    return TermPut(fp->nf_dev, (const char *) buffer, len, 1);
}
#endif

/*!
 * \brief Open a terminal device.
 *
 * Application should not call this function directly, but use the
 * stdio interface.
 *
 * \param dev  Pointer to device information structure.
 * \param name Filename. Not used with terminal devices.
 * \param mode Operation mode. May be any of the following:
 *             - _O_BINARY Raw mode, no control character
 *               interpretation.
 *             - _O_TEXT Text mode. Control characters are
 *               interpreted and tranlated to LCD commands.
 * \param acc  Access mode. Not used with terminal devices.
 *
 * \return Pointer to a NUTFILE structure or –1 to indicate an error.
 */
NUTFILE *TermOpen(NUTDEVICE * dev, const char *name, int mode, int acc)
{
    TERMDCB *dcb = dev->dev_dcb;
    NUTFILE *fp = malloc(sizeof(NUTFILE));

    if (fp == 0)
        return NUTFILE_EOF;

    if (mode & _O_BINARY)
        dcb->dcb_modeflags &= ~LCD_MF_COOKEDMODE;
    else
        dcb->dcb_modeflags |= LCD_MF_COOKEDMODE;

    fp->nf_dev = dev;
    fp->nf_fcb = NULL;

    return fp;
}

/*!
 * \brief Close a device or file.
 *
 * Application should not call this function directly, but use the
 * stdio interface.
 *
 * \param fp   Pointer to a previously opened NUTFILE structure.
 *
 * \return 0 if the device was successfully closed or –1 to indicate an error.
 */
int TermClose(NUTFILE * fp)
{
    if (fp && fp != NUTFILE_EOF) {
        free(fp);
        return 0;
    }
    return -1;
}

/*@}*/
