using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace ChatServer.util
{
  class NetUtil
  {
    public class StateObject
    {
      // size of receive buffer
      public const int BufferSize = 1024;

      // receive buffer
      public byte[] buffer = new byte[BufferSize];

      // received data string
      public StringBuilder sb = new StringBuilder();

      // client socket
      public Socket workSocket = null;

      public void ClearBuffer()
      {
        Array.Clear(buffer, 0, BufferSize);
      }
    }

    public static void StartListening()
    {
      IPEndPoint iep = new IPEndPoint(IPAddress.Parse("172.16.10.197"), 8888);
      Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

      try
      {
        listener.Bind(iep);
        listener.Listen(20);

        Console.WriteLine("Waiting for a connection...");

        while (true)
        {
          // 내부적으로 클라이언트 연결 요청을 수락하기 위한 루프를 돌리고 루프가 끝나는 것을 기다리는 것이 아닌 다음 코드 제어가 이동됨
          listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
        }
      }
      catch (Exception e)
      {
        Console.WriteLine(e.ToString());
      }
    }

    // 클라이언트 소켓을 얻고 비동기적으로 패킷을 receive하는 메서드
    public static void AcceptCallback(IAsyncResult ar)
    {
      // Get the socket that handles the client request
      Socket listener = (Socket)ar.AsyncState;
      Socket clientSocket = listener.EndAccept(ar);

      IPEndPoint remoteIpEndPoint = clientSocket.RemoteEndPoint as IPEndPoint;
      Console.WriteLine("연결된 호스트:" + remoteIpEndPoint.Address + ":" + remoteIpEndPoint.Port);

      // Create the state object
      StateObject state = new StateObject();
      state.workSocket = clientSocket;
      clientSocket.BeginReceive(
          state.buffer, 0,
          StateObject.BufferSize, 0,
          new AsyncCallback(ReadCallback), state);
    }

    public static void ReadCallback(IAsyncResult ar)
    {
      // AsyncResult로 부터 AsyncState 객체(StateObject)를 찾고 얘로 부터 소켓을 얻는다
      StateObject state = (StateObject) ar.AsyncState;
      Socket clientSocket = state.workSocket;
      try
      {
        QueueUtil.Enqueue(new Item(state));
        if (clientSocket.Connected)
        {
          // buffer로 메시지를 받고 receive 함수로 메시지가 올 때까지 대기
          clientSocket.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);
        }
      }
      catch (Exception e)
      {
        Console.WriteLine("예외 발생...");
        clientSocket.Shutdown(SocketShutdown.Both);
        clientSocket.Close();
      }
    }
  }
}