/*!
 * Simple chat program (client side).cpp - http://github.com/hassanyf
 * Version - 2.0.1
 *
 * Copyright (c) 2016 Hassan M. Yousuf
 */

#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>

int main()
{
    /* ---------- ИНИЦИАЛИЗАЦИЯ ПЕРЕМЕННЫХ КЛИЕНТА---------- */
    
    /*  
       1. client - файл-дескриптор, хранящий значение сокета,
       которые вернула система при подключении к серверу.
       2. portNum нужен для хранения номера порта, на котором происходит соединение.
       3. isExit - булевая переменная, признак завершения программы.
       4. Клиент считывает сообщение из сокета соединения в буффер обмена для приема/отправки данных к/от сервера
       5. sockaddr_in - структура, содержащая интернет адрес, с которым будет установлено соединение.
       Эта структура уже определена в netinet/in.h, поэтому нет необходимости
       заново ее задавать.
        DEFINITION:   
        struct sockaddr_in
        {
          short   sin_family;
          u_short sin_port;
          struct  in_addr sin_addr;
          char    sin_zero[8];
        };

        6. serv_addr будет содержать адрес сервера
    */

    int client;
    int portNum = 1500; // Номер порта один для сервера и клиента!
    bool isExit = false;
    int bufsize = 1024;
    char buffer[bufsize];
    char* ip = "127.0.0.1";

    struct sockaddr_in server_addr;

    /*
        Socket() функция создает новый сокет.
        На вход получает 3 аргумента,
            a. AF_INET: доменный адрес сокета.
            b. SOCK_STREAM: тип сокета. Потоковый сокет, в котором сообщение 
            читается последовательным потоком(TCP).
            c. Третий это аргумент протокола: всегда должен быть 0. 
            Операционная система выберет самый подходящий протокол.
            Функция возвращает файл-дескриптор целое число - соединение, которое используется для всех ссылок 
            на этот сокет. Если произошла ошибка соединения, то возвращается -1.
    */
    
    /* ---------- УСТАНОВКА СОКЕТ ПОДКЛЮЧЕНИЯ ----------*/
    /* --------------- socket() функция ------------------*/

    client = socket(AF_INET, SOCK_STREAM, 0);

    printf("CLIENT\n");

    if (client < 0) 
    {
        printf("Error establishing socket...\n");
        exit(1);
    }

    printf("=> Socket client has been created...\n");
    
    /* 
        Переменная serv_addr - это структура sockaddr_in. 
        sin_family содержит код для адресной семьи. 
        Всегда должна быть установлена AF_INET.
        htons() переводит номер порта из порядка байтов хоста 
        в номер порта в порядке байтов сети.
    */

    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(portNum);

	// эта функция возвращает 1, если IP-адрес действительный
    // и 0, если недействительный
    // inet_pton переводит IP-адрес в пакеты
    // inet_ntoa переводит обратно из пакета в IP-адрес
    
    inet_pton(AF_INET, ip, &server_addr.sin_addr);

    /* ---------- СОЕДИНЕНИЕ СОКЕТА ---------- */
    /* ---------------- connect() ---------------- */

    if (connect(client,(struct sockaddr *)&server_addr, sizeof(server_addr)) == 0)
        printf("=> Connection to the server %s with port number: %d\n",inet_ntoa(server_addr.sin_addr),portNum);

    if (connect(client,(struct sockaddr *)&server_addr, sizeof(server_addr)) == 0)
        printf("=> Connection to the server port number: %d \n",portNum);

    /* 
        Функция connect вызывается клиентом для установки соединения с сервером. 
        Ей нужны 3 аргумента: файловый дескриптор сокета, адрес хоста, к которому
        клиент хочет подключится (включая номер порта), и  размер адреса.
        Функция возвращает 0 при успехе и 1 при неудаче.
        Примечание: клиенту нужно знать номер порта сервера, 
        а не свой номер порта.
    */

    printf("=> Awaiting confirmation from the server...\n");
    recv(client, buffer, bufsize, 0);
    printf("=> Connection confirmed, you are good to go...\n");

    printf("\n\n=> Enter # to end the connection\n");

    // При достижении соединения, клиент может послать сообщение первым.
    
    do {
        char c;

		printf("Client: ");

        c = 0x7E;
        send(client, &c, 1, 0); //SOP
        
        do
        {
            c = getchar();
            if(c == 0x7E)
            {
                c = 0x7D; send(client, &c, 1, 0);
                c = 0x5E; send(client, &c, 1, 0);
                continue;
            }
            if(c == 0x7D)
            {
                c = 0x7D; send(client, &c, 1, 0);
                c = 0x5D; send(client, &c, 1, 0);
                continue;
            }
            send(client, &c, 1, 0);
            if(c == '#')
            {
                isExit = true;
                break;
            }
        } while (c != '\n');
        c = 0x7E;
        send(client, &c, 1, 0); //EOP      


        printf("Server: ");

        do
        {
            recv(client, &c, 1, 0);
        } while (c != 0x7E); //SOP

        recv(client, &c, 1, 0);
        while (c != 0x7E) //EOP  
        {
            if(c == 0x7D)
            {
                recv(client, &c, 1, 0);
                if(c == 0x5E)
                {
                    c = 0x7E;
                }
                else if(c == 0x5D)
                {
                    c = 0x7D;
                }
                else
                {
                    printf("%c", 0x7D);
                    continue;
                }
            }
            printf("%c", c);
            if (c == '#') 
            {
                isExit = true;
            }
            recv(client, &c, 1, 0);
        }     

    } while (!isExit);

    /* ---------------- ЗАВЕРШЕНИЕ СОЕДИНЕНИЯ ------------- */
    /* ----------------- close() --------------- */

    /* 
        Как только сервер нажмет # для завершения соединения,
        цикл завершится и закроет серверное сокет соединение
        и клиентсткое соединение.
    */

    printf("\n=> Connection terminated.\n\nGoodbye...\n");

    close(client);
    return 0;
}