/*
 * Copyright (C) 2014 Dell, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.dell.doradus.testprocessor;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.List;

public class RestClient
{
    private static boolean  USE_CUSTOM_BUFFER_SIZE  = true;
    private static int      NET_BUFFER_SIZE         = 65536;
    private static boolean  DISABLE_NAGLES          = true;

    private String          m_host      = null;
    private int             m_port      = -1;
    private Socket          m_socket    = null;
    private InputStream     m_inStream  = null;
    private OutputStream    m_outStream = null;

    public void connect(String host, int port)
    throws Exception
    {
        disconnect();

        m_host = host;
        m_port = port;

        m_socket = new Socket();
        if (m_socket == null) {
            throw new Exception("Failed to create socket");
        }

        try  {
            if (DISABLE_NAGLES) {
                m_socket.setTcpNoDelay(true);
            }
            if (USE_CUSTOM_BUFFER_SIZE) {
                if (m_socket.getSendBufferSize() < NET_BUFFER_SIZE) {
                    m_socket.setSendBufferSize(NET_BUFFER_SIZE);
                }
                if (m_socket.getReceiveBufferSize() < NET_BUFFER_SIZE) {
                    m_socket.setReceiveBufferSize(NET_BUFFER_SIZE);
                }
            }

            SocketAddress addr = new InetSocketAddress(m_host, m_port);
            m_socket.connect(addr);

            m_inStream  = new BufferedInputStream(m_socket.getInputStream(), NET_BUFFER_SIZE);
            m_outStream = new BufferedOutputStream(m_socket.getOutputStream(), NET_BUFFER_SIZE);
        }
        catch(Exception ex) {
            disconnect();
            String msg = "Failed to connect to " + m_host + ":" + m_port;
            throw new Exception(msg, ex);
        }
    }

    public boolean notConnected()
    {
        return m_socket == null
                || m_socket.isClosed()
                || !m_socket.isConnected();
    }

    public void disconnect()
    {
        if (m_socket == null)
            return;

        try { m_socket.close(); }
        catch (Exception ex) { /* ignore */  }

        m_socket = null;
    }

    public void sendRequest(RestRequest request)
    throws Exception
    {
        if (m_socket == null) {
            String msg = "No connection to Doradus server (possibly <connect> statement is missed)";
            throw new Exception(msg);
        }
        try {
            StringBuilder header = new StringBuilder();

            header.append(request.getRest() + " HTTP/1.1" + "\r\n");
            header.append("Host: " + m_host + "\r\n");

            String acceptType = request.getAcceptType();
            if (acceptType != null && !acceptType.isEmpty()) {
                header.append("Accept: " + acceptType + "\r\n");
            }
            String contentType = request.getContentType();
            if (contentType != null && !contentType.isEmpty()) {
                header.append("Content-type: " + contentType + "\r\n");
            }
            
            String body = request.getBody();
            int contentLength = (body == null) ? 0 : body.length();
            
            header.append("Content-length: " + contentLength + "\r\n");
            header.append("x-api-version: 2" + "\r\n");
            header.append("\r\n");

            byte[] headerBytes = header.toString().getBytes("UTF-8");
            byte[] requestBytes = headerBytes;

            if (contentLength > 0)
            {
                byte[] bodyBytes = body.getBytes("UTF-8");

                requestBytes = new byte[headerBytes.length + bodyBytes.length];
                System.arraycopy(headerBytes, 0, requestBytes, 0, headerBytes.length);
                System.arraycopy(bodyBytes, 0, requestBytes, headerBytes.length, bodyBytes.length);
            }

            m_outStream.write(requestBytes);
            m_outStream.flush();
        }
        catch(Exception ex) {
            String msg = "Failed to send request";
            throw new Exception(msg, ex);
        }
    }

    public RestResponse readResponse()
    throws Exception
    {
        try {
            RestResponse response = new RestResponse();

            List<String> headerLines = readHeaderLines();
            response.parseHeader(headerLines);

            readBody(response);

            return response;
        }
        catch(Exception ex) {
            String msg = "Failed to read response";
            throw new Exception(msg, ex);
        }
    }

    private List<String> readHeaderLines()
    throws Exception
    {
        try {
            List<String> lines = new ArrayList<String>();
            while (true)
            {
                String line = readHeaderLine();
                if (line.length() <= 2)
                {
                    if (!line.equals("\r\n")) {
                        String msg = "Response header is not properly terminated: \"" + line + "\"";
                        throw new Exception(msg);
                    }
                    break;
                }
                lines.add(line.trim());
            }
            return lines;
        }
        catch(Exception ex) {
            String msg = "Failed to read response header";
            throw new Exception(msg, ex);
        }
    }

    private String readHeaderLine()
    throws Exception
    {
        try {
            StringBuilder line = new StringBuilder();
            int buf = ' ';
            while (buf != '\n')
            {
                buf = m_inStream.read();
                if (buf == -1) {
                    String msg = "Unexpected EOF";
                    throw new Exception(msg);
                }
                line.append((char) buf);
            }

            return line.toString();
        }
        catch(Exception ex) {
            String msg = "Failed to read response header line";
            throw new Exception(msg, ex);
        }
    }

    private void readBody(RestResponse response)
    throws Exception
    {
        int bytesLeft = response.getContentLength();

        if (bytesLeft <= 0) {
            response.setBody("");
            return;
        }

        byte[] buffer = new byte[bytesLeft];
        int offset = 0;

        while (bytesLeft > 0) {
            int bytesGot = m_inStream.read(buffer, offset, bytesLeft);
            bytesLeft -= bytesGot;
            offset += bytesGot;
        }

        String body = (new String(buffer, "UTF-8")).trim();
        /*
        if ("xml".equals(response.contentType()) && body.indexOf("<?xml") > -1) {
            int pos = body.indexOf("?>");
            if (pos > -1) {
                body = body.substring(pos + 2, body.length() - (pos + 2)).trim();
            }
        }
        */
        response.setBody(body);
    }
}