// 
// Copyright 2016 Agencia Estatal de Administración Tibutaria
//
// Licencia con arreglo a la EUPL, Versión 1.1 o -en cuanto
// sean aprobadas por la Comisión Europea- versiones
// posteriores de la EUPL (la «Licencia»);
// Solo podrá usarse esta obra si se respeta la Licencia.
// Puede obtenerse una copia de la Licencia en:
//  
// https://joinup.ec.europa.eu/software/page/eupl
//  
// Salvo cuando lo exija la legislación aplicable o se acuerde
// por escrito, el programa distribuido con arreglo a la
// Licencia se distribuye «TAL CUAL», SIN GARANTÍAS NI CONDICIONES
// DE NINGÚN TIPO, ni expresasni implícitas.
//
// Véase la Licencia en el idioma concreto que rige los permisos y 
// limitaciones que establece la Licencia.
// 

using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Windows.Forms;
using ateneo.utiles;

namespace ateneo.sockets
{
    public delegate void MensajeRecibidoEventHandler(object sender, MensajeRecibidoArgs e);
    public delegate void ErrorSocketEventHandler(object sender, MensajeRecibidoArgs e);
    public delegate void DatosRecibidosEventHandler(object sender, DatosRecibidosArgs e);

    public class MessageSocket
    {
        internal Socket socket;
        internal NetworkStream networkStream;
        internal System.IO.StreamWriter streamWriter;
        private IAsyncResult m_result;
        public AsyncCallback m_pfnCallBack;
        private bool _abierto;
        private string _host;
        StringBuilder sb;
        private bool _modoBin = false;

        public event MensajeRecibidoEventHandler mensajeRecibido;
        public event ErrorSocketEventHandler errorSocket;
        public event DatosRecibidosEventHandler datosRecibidos;

        static readonly object bloqueo = new object();

        public bool modoBin {
            get { return _modoBin; }
            set { _modoBin = value; }
        }
        public static System.Text.Decoder decoder = System.Text.Encoding.Default.GetDecoder();

        public string host {
            get {
                return _host;
            }
            set {
                bool reabrir = _abierto;
                if (reabrir) {
                    close();
                }
                _host = value;
                if (reabrir) {
                    open();
                }
            }
        }

        private int _puerto;
        public int puerto {
            get { return _puerto; }
            set {
                bool reabrir = _abierto;
                if(reabrir) {
                    close();
                }
                _puerto = value;
                if(reabrir) {
                    open();
                }
            }
        }

        public MessageSocket() {
            sb = new StringBuilder(64000);
        }

        public bool open() {
            try {
                socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                socket.Connect(_host, _puerto);
                networkStream = new NetworkStream(socket);
                streamWriter = new System.IO.StreamWriter(networkStream);
                _abierto = true;
                esperarDatos();
                return true;
            } catch (SocketException ex) {
                fireErrorSocket(ex.ErrorCode, ex.Message);
                return false;
            } catch (Exception) {
                return false;
            }
        }

        private void fireErrorSocket(int code, string p) {
            if (errorSocket != null) {
                errorSocket(this, new MensajeRecibidoArgs(p, code));
            }
        }

        public bool abierto {
            get { return _abierto; }
        }

        public class SocketPacket
        {
            public Socket socket;
            public byte[] bufer = new byte[4096];
        }

        private void esperarDatos() {
            try {
                if (m_pfnCallBack == null) {
                    m_pfnCallBack = new AsyncCallback(OnDataReceived);
                }
                SocketPacket sp = new SocketPacket();
                sp.socket = socket;
                m_result = socket.BeginReceive(sp.bufer, 0, sp.bufer.Length, SocketFlags.None,
                    m_pfnCallBack, sp);
            } catch (SocketException ex) {
                fireErrorSocket(ex.ErrorCode, ex.Message);
            }
        }

        public void OnDataReceived(IAsyncResult asyn) {
            try {
                SocketPacket sp = asyn.AsyncState as SocketPacket;
                int iRx = sp.socket.EndReceive(asyn);
                if (_modoBin) {
                    byte[] bytes = new byte[iRx];
                    Array.Copy(sp.bufer, bytes, iRx);
                    transmiteBufer(bytes);
                } else {
                    char[] chars = new char[iRx + 1]; 
                    int charLen = decoder.GetChars(sp.bufer, 0, iRx, chars, 0);
                    procesaBufer(chars, charLen);
                } 
                esperarDatos();
            } catch (ObjectDisposedException) {
                //MessageBox.Show(ex.Message);
            } catch (SocketException ex) {
                fireErrorSocket(ex.ErrorCode, ex.Message);
            }
        }

        private void transmiteBufer(byte[] chars) {
            if (datosRecibidos != null) {
                datosRecibidos(this, new DatosRecibidosArgs(chars));
            }
        }

        void procesaBufer(char[] bufer, int leidos) {
            int largoInicial = sb.Length;
            sb.Append(new string(bufer, 0, leidos));
            int posInicio = 0;
            int posFinal;
            for (int i = 0; i < leidos; i++) {
                if (bufer[i] == 10) {  // caracter de salto de linea
                    posFinal = largoInicial + i;
                    if (i > 0 && bufer[i - 1] == 13) {
                        posFinal--;
                    }
                    if (posFinal - posInicio > 0) {
                        nuevoMensaje(sb.ToString(posInicio, posFinal - posInicio));
                    }
                    posInicio = largoInicial + i + 1;
                }
            }
            if (posInicio > 0) {
                sb.Remove(0, posInicio);
            }
        }

        void nuevoMensaje(string s) {
            int numMensaje;
            string mensaje;
            if(!Int32.TryParse(s.Substring(0,3), out numMensaje)) {
                numMensaje = 999; 
            }
            if (numMensaje == 999) {
                mensaje = s;
            } else {
                mensaje = s.Substring(3);
            }
            if(mensajeRecibido != null) {
                mensajeRecibido(this, new MensajeRecibidoArgs(mensaje, numMensaje));
            }
        }

        public bool close() {
            try {
                if (!_abierto) {
                    return true;
                }
                _abierto = false;
                socket.Close();
                return true;
            } catch (Exception) {
                return false;
            }
        }

        public void enviar(string mensaje, int codigo) {
            string data = string.Format("{0:000}{1}", codigo, mensaje);
            enviar(data);
        }

        public void enviar(string mensaje) {
            lock (bloqueo) {
                try {
                    if (socket != null && socket.Connected) {
                        streamWriter.WriteLine(mensaje);
                        streamWriter.Flush();
                    }
                } catch (Exception exc) {
                    LogExcepcion.guardar("Error en enviar", exc);
                }
            }
        }

    }

    public class MensajeRecibidoArgs : EventArgs
    {
        public string mensaje;
        public int codigo;
        public MensajeRecibidoArgs(string s, int c) {
            mensaje = s;
            codigo = c;
        }
    }

    public class DatosRecibidosArgs : EventArgs
    {
        public byte[] bufer;
        public DatosRecibidosArgs(byte[] buf) {
            bufer = buf;
        }
    }
}
