/*
 * telnet.cpp
 *
 *  Created on: Nov 5, 2011
 *      Author: jirka
 */


#include <sys/types.h>
#include <sys/socket.h>
#include <iostream>
#include <unistd.h>
#include <malloc.h>
#include <resolv.h>
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include "telnet.h"

using namespace std;

/*
 * Konstruktor tridy Telnet pro rizeni spojeni
 */
Telnet::Telnet(char *hostName, int port)
{
        // Zjistíme info o vzdáleném počítači
        if ((host = gethostbyname(hostName)) == NULL)
        {
            init = -1;
            abort();
        }
        // Vytvoříme soket
        if ((mySocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
        {
            init = -1;
            abort();
        }
        // Zaplníme strukturu sockaddr_in
        // 1) Rodina protokolů
        serverSock.sin_family = AF_INET;
        // 2) Číslo portu, ke kterému se připojíme
        serverSock.sin_port = htons(port);
        // 3) Nastavení IP adresy, ke které se připojíme
        memcpy(&(serverSock.sin_addr), host->h_addr, host->h_length);
        // Připojení soketu
        if (connect(mySocket, (sockaddr *)&serverSock, sizeof(serverSock)) == -1)
        {
            init = -1;
            abort();
        }

        /* Uzamknout zamek na spojeni */
        pthread_mutex_init(&dataMutex,NULL);

        /* Odeslat signal o spojeni */
        pthread_cond_init(&dataCond,NULL);

        /* Vymazat zasobnik spojeni */
        data.clear();

        /* Aktivace vlakna pro rizeni spojeni */
        pthread_create(&listenerThread,NULL,&ParseTelnet,this);
        init = 0;
}

/*
 * Odeslani konfiguracniho paketu s patricnymi daty
 *
 * Vstup:
 *      cmd:            prikaz
 *      option:         parametr
 *
 * Vystup:
 *      uspesnost operace
 */
int  Telnet::sendOption(unsigned char cmd, unsigned char option)
{
      /* Sestaveni balicku dat */
      char buf[3] = {IAC,option,cmd};

      /* Odeslani */
      int size = send(mySocket, buf, 3, 0);
      if (size < 0)
        init = -1;
      return size;

}


/*
 * Jadro parseru telnetovych dat
 */
void *ParseTelnet(void* params)
{
int size = 0;
//Inicializace zasobniku
unsigned char input[1] = "";
unsigned char inputverb[1]="";
unsigned char inputoption[1]="";

/* Pretypovani parametru vlakna */
Telnet * curr = (Telnet*)params;

/* Dokud lze ze spojeni cist data, cteme jeden znak */
while ((size = recv(curr->mySocket, input, 1, 0)) > 0)
{
        /* Podle znaku */
	switch (input[0])
	{
	case IAC:
		/* Interpret As Command => cteme dalsi bajt*/
		if ((size = recv(curr->mySocket, inputverb, 1, 0)) <1)
		{
			curr->init = -1;
			return 0;
		}
		/* Podle prijateho bajtu */
		switch (inputverb[0])
		{
			case IAC:
				/* Opakovane IAC */
				//sb+=inputverb[0];

			        /* Uzamkneme zamek na datech */
				pthread_mutex_lock(&curr->dataMutex);

				/* Pridame data na konec bufferu */
				curr->data+=inputverb[0];

				/* Odeslani signalu */
				pthread_cond_signal(&curr->dataCond);

				/* Odemknuti zamku */
				pthread_mutex_unlock(&curr->dataMutex);
				break;
			case SB:
			        /* Subnegotiation */
				char inputprocess[1];

				/* Prijmeme dalsi bajt */
				if ((size = recv(curr->mySocket, inputoption, 1, 0)) == -1)
				{
					curr->init = -1;
					break;
				}

				/* Podle prijateho bajtu */
				switch (inputoption[0])
				{
				case TERMINAL_TYPE:
				        /* Typ terminalu */
					if ((size = recv(curr->mySocket, inputprocess, 1, 0)) == -1)
					{
						curr->init = -1;
						break;
					}
					/* Pokud je prijat pozadavek na nastaveni typu */
					if (inputprocess[0]==SEND)
						curr->sendSubnegotiation(TERMINAL_TYPE,6,(unsigned char*)"\0vt100");
					break;
				default:
					break;
				}

				break;
			case DO:
			case DONT:
			case WILL:
			case WONT:
                                /* Odpovidame na vsechno DONT/WONT krome SGA, NAWS a TERMINAL_TYPE */
			        /* Prijmeme bajt */
				if ((size = recv(curr->mySocket, inputoption, 1, 0)) == -1)
				{
					curr->init = -1;
					break;
				}
				/* Na zminene bajty odesleme odpoved DO/WILL */
				if (inputoption[0] == (unsigned char)SGA ||  inputoption[0] == (unsigned char)TERMINAL_TYPE || inputoption[0] == (unsigned char)NAWS)
				{
				        /* DO/WILL */
					curr->sendOption(inputoption[0],(inputverb[0] == (unsigned char)DO ? WILL:DO));

					/* Pokud je DO */
					if (inputverb[0] == DO)
					{
					        /* Velikost okna */
						switch (inputoption[0])
						{

						case NAWS:
						        /* 50x16 ale nejak to nejede... */
							curr->sendSubnegotiation(NAWS,4,(unsigned char*)"\0\0x50\0\0x0F");
							break;
						default:
							break;

						}
					}
				}
				else
				{
				        /* Odpovidame DONT/WONT */
					curr->sendOption(inputoption[0],(inputverb[0] == (unsigned char)DO ? WONT:DONT));
				}
                    break;
                default:
                    break;
            	}
            break;
        default:
                /* Ostatni data */

                /* Uzamkneme datovy zamek */
        	pthread_mutex_lock(&curr->dataMutex);

        	/* Vlozime data do zasobniku */
        	curr->data+=input[0];

        	/* Odesleme signal */
        	pthread_cond_signal(&curr->dataCond);
        	pthread_mutex_unlock(&curr->dataMutex);
            break;
    	}

	}
curr->init = -1;
return 0;
}

/*
 * Vraci ukazatel na datovy zamek
 */
pthread_mutex_t * Telnet::GetReaderLock(void)
{
  return &dataMutex;
}

/*
 * Vraci ukazatel na podminkovou promennou
 */
pthread_cond_t * Telnet::GetReaderCond(void)
{
  return &dataCond;
}


/*
 * Vraci data prijata pres Telnet
 */
string Telnet::ReadData(void)
{

        /* Uzamkneme zamek na datech */
	pthread_mutex_lock(&dataMutex);

	/* Kopie dat */
	string data_out = data;

	/* Vyprazdnime zasobnik */
	data.clear();

	/* Odemkneme zamek */
	pthread_mutex_unlock(&dataMutex);
	return data_out;

}

/*
 * Vraci informaci o prijatych datech
 */
int Telnet::AvailableData(void)
{
  return data.length();
}

/*
 * Odesle data pres Telnet
 */
int Telnet::WriteData(string input)
{
	if (send(mySocket, input.c_str(), input.length(), 0) < 0)
	{
		init = -1;
		return -1;
	}
return 0;
}

/*
 * Vraci info jestli je spojeni otevrene
 */
BOOLEAN Telnet::isConnected(void)
{
	if (init < 0)
		return FALSE;
	else
		return TRUE;
}

/*
 * Odesle subnegotiation pres Telnet
 *
 * Vstup:
 *      cmd:            prikaz
 *      paramsLen:      Delka dat
 *      params:         Ukazatel na parametry
 */
char Telnet::sendSubnegotiation(unsigned char cmd, int paramsLen,unsigned char* params)
{
	unsigned char buf[5+paramsLen+2];
	int n = 0;
	buf[n++] = IAC;
	buf[n++] = SB;
	buf[n++] = cmd;

	for (int i = 0; i < paramsLen; i++)
	{
		buf[n++] = params[i];
	}
	buf[n++] = IAC;
	buf[n++] = SE;
	buf[n++] = 0;
	if (send(mySocket, buf, n-1, 0) < 0)
	{
		init = -1;
		return -1;
	}
	return n;
}




