/*
 *
 * UNICON - The Console Chinese & I18N
 * Copyright (c) 1999-2000
 *
 * This file is part of UNICON, a console Chinese & I18N
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * See the file COPYING directory of this archive
 * Author: see CREDITS
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <netdb.h>
#include <unistd.h>
#include <iostream.h>
#include <pth.h>

#include <TLS_MemFile.hpp>
#include <TLS_HzInput.hpp>
#include <TLS_ImmOp.hpp>
#include <ImmComm.h>
#include <TLS_PthSocket.hpp>

#define MAXREQLINE 1024
TLS_CImmOp *pMyCImmOp = NULL;

static void InitAllTLS_CHzInput (TLS_CHzInput **aTLS_CHzInput, int total);
static void DeleteAllTLS_CHzInput (TLS_CHzInput **aTLS_CHzInput, int total);
static void SaveTLS_CHzInput (TLS_CHzInput *pTLS_CHzInput, TLS_CHzInput **aTLS_CHzInput, int total);
static void RemoveTLS_CHzInput (TLS_CHzInput *pTLS_CHzInput, 
                            TLS_CHzInput **aTLS_CHzInput, int total);

void GetBufPhrase (char *buf, PhraseItem *p)
{
    char *p1 = buf;

    p->szKeys = p1;
    p1 += strlen (p->szKeys) + 1;

    p->KeyLen = (u_char *) p1;
    p1 += sizeof (u_char);

    p->szPhrase = p1;
    strcpy (p->szPhrase, p1);
    p1 += strlen (p->szPhrase) + 1;

    p->frequency = (freq_t *) p1;
    p1 += sizeof (freq_t);
}

static void *handler(void *_arg)
{
    int fd = (int)((long)_arg);
    char caLine[MAXREQLINE];
    char str[256], str1[256];
    int n;

    long Command, bExit = 0;
    long handle, total;
    TLS_CMemFile *MemIn = new TLS_CMemFile (caLine, 0, MAXREQLINE);
    TLS_CMemFile *MemOut = new TLS_CMemFile (512);
    TLS_CHzInput *pTLS_CHzInput = NULL;
    TLS_CHzInput *aTLS_CHzInput[64];
    ImmOperation * pImmOp = NULL;
    TLS_CPthSocket MyTLS_CPthSocket (fd);

    InitAllTLS_CHzInput (aTLS_CHzInput, 64);
    printf ("========== Begin New Client Socket =========== \n");
    /* read request */
    while (bExit == 0) {
        n = MyTLS_CPthSocket.read (caLine, MAXREQLINE);
        if (n < 0) {
            fprintf(stderr, "read error: errno=%d\n", errno);
            close(fd);
            return NULL;
        }
        MemIn->rewind ();
        MemIn->SetBufLen (n);
        MemOut->rewind ();
        *MemIn >> Command;
        if (Command != IMM_OPEN_SERVER && pTLS_CHzInput == NULL)
            *MemOut << (long) IMM_IMM_NOTOPEN;
        else  switch (Command) {
            case IMM_OPEN_SERVER:  
            {
                long type;
                PSTR ImmModule = (PSTR) str;
                PSTR ImmTable = (PSTR) str1;
                *MemIn >> ImmModule; 
                *MemIn >> ImmTable;
                *MemIn >> type;
                cout << "decode : " << ImmModule << ":" 
                     << ImmTable << ":" << type << "\n";
                pImmOp = pMyCImmOp->OpenImm (str, type);
                if (pImmOp == NULL) {
                    *MemOut << (long) IMM_NO_SUCH_IMM;
                    break;
                }
                pTLS_CHzInput = new TLS_CHzInput (pImmOp, str1, type);
                if (pTLS_CHzInput->error == 1)
                {
                    pMyCImmOp->CloseImm (pTLS_CHzInput->pImmOp);
                    delete pTLS_CHzInput;
                    *MemOut << (long) IMM_ILEGAL_FILE;
                    break;
                }
                SaveTLS_CHzInput (pTLS_CHzInput, aTLS_CHzInput, 64);
                *MemOut << (long) IMM_NORMAL;
                *MemOut << (long) pTLS_CHzInput;
                PSTR szImmName = pImmOp->name;
                *MemOut << szImmName;
                cout << "pTLS_CHzInput = " << (long) pTLS_CHzInput << "\n";
                break;
            }
            case IMM_CLOSE_SERVER:
                *MemIn >> handle;
                pTLS_CHzInput = (TLS_CHzInput *) handle;
                *MemOut << (long) IMM_NORMAL;
                bExit = 1;
                RemoveTLS_CHzInput (pTLS_CHzInput, aTLS_CHzInput, 64);
                pMyCImmOp->CloseImm (pTLS_CHzInput->pImmOp);
                delete pTLS_CHzInput;
                pTLS_CHzInput = NULL;
                break;
            case IMM_RESET_INPUT:
                *MemIn >> handle;
                pTLS_CHzInput = (TLS_CHzInput *) handle;
                pTLS_CHzInput->ResetInput ();
                *MemOut << (long) IMM_NORMAL;
                break;
            case IMM_KEY_FILTER:
            {
                char key;
                int rt, len;
                *MemIn >> handle;
                pTLS_CHzInput = (TLS_CHzInput *) handle;
                *MemIn >> key;

                rt = pTLS_CHzInput->KeyFilter (key, str, &len);

                *MemOut << (long) IMM_NORMAL;
                *MemOut << (long) rt;
                if (rt == 2)
                {
                   *MemOut << (long) len;
                   *MemOut << (PSTR) str;
                }
                break;
            }
            case IMM_CONFIG_INPUT_AREA:
            {
                long TotalSelection;
                *MemIn >> handle;
                *MemIn >> TotalSelection;
                pTLS_CHzInput = (TLS_CHzInput *) handle;
                pTLS_CHzInput->ConfigInputArea (TotalSelection);
                *MemOut << (long) IMM_NORMAL;
                break;
            }
            case IMM_GET_INPUT_DISPLAY:
            {
                *MemIn >> handle;
                pTLS_CHzInput = (TLS_CHzInput *) handle;
                total = pTLS_CHzInput->GetInputDisplay (str, sizeof (str));
                if (total == 0)
                    str[0] = '\0';
                *MemOut << (long) IMM_NORMAL;
                *MemOut << (PSTR) str;
                break;
            }
            case IMM_GET_SELECT_DISPLAY:
            {
                *MemIn >> handle;
                pTLS_CHzInput = (TLS_CHzInput *) handle;
                total = (long) pTLS_CHzInput->GetSelectDisplay (str, sizeof (str));
                if (total == 0)
                    str[0] = '\0';
                *MemOut << (long) IMM_NORMAL;
                *MemOut << total;
                *MemOut << (PSTR) str;
                break;
            }
            case IMM_SET_PHRASE_ITEM:
            {
                long n;
                PhraseItem a;
                *MemIn >> handle;
                pTLS_CHzInput = (TLS_CHzInput *) handle;
                *MemIn >> n;
                GetBufPhrase (MemIn->pGetCurrentPos (), &a);
                pTLS_CHzInput->ModifyPhrase (n, &a);
                *MemOut << (long) IMM_NORMAL;
                break;
           }
           case IMM_ADD_USER_PHRASE:
           {
                PhraseItem a;
                *MemIn >> handle;
                pTLS_CHzInput = (TLS_CHzInput *) handle;
                GetBufPhrase (MemIn->pGetCurrentPos (), &a);
                pTLS_CHzInput->AppendPhrase (&a);
                *MemOut << (long) IMM_NORMAL;
                break;
           }
           case IMM_FLUSH_USER_PHRASE:
                *MemIn >> handle;
                pTLS_CHzInput = (TLS_CHzInput *) handle;
                pTLS_CHzInput->FlushUserPhrase ();
                *MemOut << (long) IMM_NORMAL;
                break;
           default:
                *MemOut << (long) IMM_BAD_REQUEST;
                break;
        }
        /* simulate a little bit of processing ;) */
        pth_yield (NULL);
        n = MyTLS_CPthSocket.write (MemOut->pGetBuf (), MemOut->GetBufLen());
        //pth_write(fd, MemOut->pGetBuf (), MemOut->GetBufLen());
    }
    /* close connection and let thread die */
    delete MemIn;
    delete MemOut;
    DeleteAllTLS_CHzInput (aTLS_CHzInput, 64);
    close(fd);
    printf ("========== End Client Socket =========== \n");
    return NULL;
}

static void InitAllTLS_CHzInput (TLS_CHzInput **aTLS_CHzInput, int total)
{
    for (int i = 0; i < total; i++)
        aTLS_CHzInput[i] = NULL;
}

static void DeleteAllTLS_CHzInput (TLS_CHzInput **aTLS_CHzInput, int total)
{
    for (int i = 0; i < total; i++)
    {
         TLS_CHzInput *p = aTLS_CHzInput[i];
         if (p == NULL)
             continue;
         delete p;
    }
}

void SaveTLS_CHzInput (TLS_CHzInput *pTLS_CHzInput, TLS_CHzInput **aTLS_CHzInput, int total)
{
    for (int i = 0; i < total; i++)
    {
         TLS_CHzInput *p = aTLS_CHzInput[i];
         if (p == NULL)
         {
             aTLS_CHzInput[i] = pTLS_CHzInput;
             return;
         }
    }
}

void RemoveTLS_CHzInput (TLS_CHzInput *pTLS_CHzInput, TLS_CHzInput **aTLS_CHzInput, int total)
{
    for (int i = 0; i < total; i++)
    {
         TLS_CHzInput *p = aTLS_CHzInput[i];
         if (p == pTLS_CHzInput)
         {
             aTLS_CHzInput[i] = NULL;
             return;
         }
    }
}

/*
 * A useless ticker we let run just for fun in parallel
 */

static void *ticker(void *_arg)
{
    time_t now;
    char *ct;
    float avload;

    for (;;) {
        pth_sleep(5);
        now = time(NULL);
        ct = ctime(&now);
        ct[strlen(ct)-1] = NUL;
        pth_ctrl(PTH_CTRL_GETAVLOAD, &avload);
        fprintf(stderr, "ticker woken up on %s, average load: %.2f\n", 
                ct, avload);
    }
    /* NOT REACHED */
    return NULL;
}

/*
 * And the server main procedure
 */

#if defined(FD_SETSIZE)
#define REQ_MAX FD_SETSIZE-100
#else
#define REQ_MAX 100
#endif

static int s;
pth_attr_t attr;

static void myexit(int sig)
{
    close(s);
    pth_kill();
    pth_attr_destroy(attr);
    fprintf(stderr, "**Break\n");
    exit(0);
}

int StartImmService (int argc, char *argv[]) 
{
    struct sockaddr_in sar;
    struct protoent *pe;
    struct sockaddr_in peer_addr;
    socklen_t peer_len;
    int sr;
    int port;

    /* initialize scheduler */
    pth_init();
    signal(SIGPIPE, SIG_IGN);
    signal(SIGINT,  myexit);
    signal(SIGTERM, myexit);

    /* argument line parsing */
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <port>\n", argv[0]);
        exit(1);
    }
    port = atoi(argv[1]);
    if (port <= 0 || port >= 65535) {
        fprintf(stderr, "Illegal port: %d\n", port);
        exit(1);
    }

    fprintf(stderr, "This is Chinese Input Server 0.1.\n");
    /* run a just for fun ticker thread */
    attr = pth_attr_new();
    pth_attr_set(attr, PTH_ATTR_NAME, "ticker");
    pth_attr_set(attr, PTH_ATTR_JOINABLE, FALSE);
    pth_attr_set(attr, PTH_ATTR_STACK_SIZE, 32*1024);
//    pth_spawn(attr, ticker, NULL);

    /* create TCP socket */
    if ((pe = getprotobyname("tcp")) == NULL) {
        perror("getprotobyname");
        exit(1);
    }
    if ((s = socket(AF_INET, SOCK_STREAM, pe->p_proto)) == -1) {
        perror("socket");
        exit(1);
    }

    /* bind socket to port */
    sar.sin_family      = AF_INET;
    sar.sin_addr.s_addr = INADDR_ANY;
    sar.sin_port        = htons(port);
    if (bind(s, (struct sockaddr *)&sar, sizeof(struct sockaddr_in)) == -1) {
        perror("socket");
        exit(1);
    }

    /* start listening on the socket with a queue of 10 */
    if (listen(s, REQ_MAX) == -1) {
        perror("listen");
        exit(1);
    }

    /* finally loop for requests */
    pth_attr_set(attr, PTH_ATTR_NAME, "handler");
    fprintf(stderr, "listening on port %d (max %d simultaneous connections)\n", port, REQ_MAX);
    for (;;) {
        /* accept next connection */
        peer_len = sizeof(peer_addr);
        if ((sr = pth_accept(s, (struct sockaddr *)&peer_addr, &peer_len)) == -1) {
            perror("accept");
            pth_sleep(1);
            continue;
        }
        if (pth_ctrl(PTH_CTRL_GETTHREADS) >= REQ_MAX) {
            fprintf(stderr, "currently no more connections acceptable\n");
            continue;
        }
        fprintf(stderr, "connection established (fd: %d, ip: %s, port: %d)\n", 
                sr, inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port));

        /* spawn new handling thread for connection */
        pth_spawn(attr, handler, (void *)((long)sr));
    }
}

#include <TLS_DoubleByteConvertor.hpp>
#include <TLS_AsciiConvertor.hpp>
#include <TLS_const.h>

TLS_CDoubleByteConvertor *pCDoubleByteConvertor = NULL;
TLS_CAsciiConvertor *pCAsciiConvertor [N_ASCII_CONVERTOR];

static struct AsciiConvertorConf
{
    char *szFileName;
}
AsciiConvertorConf[2] =
{
    {UNICON_LIB"/sys-gb.tab"},
    {UNICON_LIB"/sys-big5.tab"}
};

int main (int argc, char *argv[]) 
{
    int i;
    pMyCImmOp = new TLS_CImmOp ();
    pCDoubleByteConvertor = new TLS_CDoubleByteConvertor ();
    for (i = 0; i < N_ASCII_CONVERTOR; i++)
        pCAsciiConvertor [i] =
                  new TLS_CAsciiConvertor (AsciiConvertorConf[i].szFileName);

    StartImmService (argc, argv); 

    for (i = 0; i < N_ASCII_CONVERTOR; i++)
        delete pCAsciiConvertor [i];
    delete pMyCImmOp;
    delete pCDoubleByteConvertor;
}

