import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Scanner;

/**
 * This example demonstrates how to upload a file using the server REST API.
 */
public class PutFileExample {

    private static final BufferedReader CONSOLE_READER = new BufferedReader(new InputStreamReader(System.in));

    private static final String LOGIN_URL_TEMPLATE = "http://%s:%s/rest/login";
    private static final String LOGOUT_URL_TEMPLATE = "http://%s:%s/rest/logout";
    private static final String UPLOAD_URL_TEMPLATE = "http://%s:%s/rest/files?directory=%s&offset=0";

    private static final String LOGIN_TEMPLATE = "{\"domain\":\"%s\",\"username\":\"%s\", \"password\":\"%s\"}";

    private static final String CHARSET = "UTF-8";

    private final String host;
    private final int port;
    private final String username;
    private final String password;
    private final String domainName;
    private final String remoteDirectoryPath;
    private final File localFile;
    private String sessionId;

    /**
     * The application entry point.
     *
     * @param arguments command line arguments
     * @throws Exception if an error occurs
     */
    public static void main(String[] arguments)
            throws Exception {
        showExampleTitle();
        PutFileExample example = createExample();
        example.execute();
    }

    /**
     * Shows example title.
     */
    private static void showExampleTitle() {
        System.out.println("This example demonstrates how to upload a using the server REST API.");
        System.out.println();
    }

    /**
     * Creates an example instance.
     *
     * @return an example instance
     * @throws Exception if an error occurs
     */
    private static PutFileExample createExample()
            throws Exception {
        String host = getParameter("Enter server HTTP/S host: ");
        int port = Integer.parseInt(getParameter("Enter server HTTP/S port: "));
        String username = getParameter("Enter username: ");
        String password = getParameter("Enter password: ");
        String domainName = getParameter("Enter domain name: ");
        String remotePath = getParameter("Enter remote directory path: ");
        String localPath = getParameter("Enter local file path: ");

        return new PutFileExample(host, port, username, password, domainName, remotePath, localPath);
    }

    /**
     * Support method. Shows prompt on console and reads user response.
     *
     * @param prompt prompt string
     * @return user response
     * @throws Exception if an I/O error occurs
     */
    private static String getParameter(String prompt)
            throws Exception {
        System.out.print(prompt);
        return CONSOLE_READER.readLine();
    }

    /**
     * Constructs example.
     *
     * @param host server REST host
     * @param port server REST port
     * @param username server username
     * @param password server password
     * @param domainName domain name
     * @param remoteDirectoryPath the remote directory path
     * @param localFilePath the local file path
     */
    public PutFileExample(String host,
                          int port,
                          String username,
                          String password,
                          String domainName,
                          String remoteDirectoryPath,
                          String localFilePath) {
        this.host = host;
        this.port = port;
        this.username = username;
        this.password = password;
        this.domainName = domainName;
        this.remoteDirectoryPath = remoteDirectoryPath;
        this.localFile = new File(localFilePath);
    }

    /**
     * Starts domain.
     */
    public void execute() {
        try {
            login();
            uploadFile();
            logout();

            System.out.println("File uploaded successfully!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Performs server login.
     *
     * @throws Exception if an error occurs
     */
    private void login()
            throws Exception {
        URL url = new URL(String.format(LOGIN_URL_TEMPLATE, this.host, this.port));
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();

        try {
            connection.setDoOutput(true);
            connection.setRequestMethod("POST");
            connection.setRequestProperty("Content-Type", "application/json");

            String authenticationString = String.format(LOGIN_TEMPLATE, this.domainName, this.username, this.password);
            byte[] authenticationData = authenticationString.getBytes(CHARSET);

            connection.setRequestProperty("Content-Length", Integer.toString(authenticationData.length));

            OutputStream out = connection.getOutputStream();
            out.write(authenticationData);
            out.close();

            assertRequestSucceeded(connection);

            String cookie = connection.getHeaderField("Set-Cookie");
            this.sessionId = new Scanner(cookie).useDelimiter(";").next();
        } finally {
            connection.disconnect();
        }
    }

    /**
     * Downloads the file.
     *
     * @throws Exception if an error occurs
     */
    private void uploadFile()
            throws Exception {
        URL url = new URL(String.format(UPLOAD_URL_TEMPLATE, this.host, this.port, this.remoteDirectoryPath));
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();

        try {
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setRequestMethod("PUT");
            connection.setRequestProperty("Cookie", this.sessionId);

            String boundary = Long.toHexString(System.currentTimeMillis());

            connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
            connection.setChunkedStreamingMode(1024);

            uploadFile(connection, boundary);
            assertRequestSucceeded(connection);
        } finally {
            connection.disconnect();
        }
    }

    /**
     * Performs server logout.
     *
     * @throws Exception if an error occurs
     */
    private void logout()
            throws Exception {
        URL url = new URL(String.format(LOGOUT_URL_TEMPLATE, this.host, this.port));
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();

        try {
            connection.setDoOutput(true);
            connection.setRequestMethod("POST");
            connection.setRequestProperty("Cookie", this.sessionId);
        } finally {
            connection.disconnect();
        }
    }

    /**
     * Asserts that request was successful.
     *
     * @param connection target connection
     *
     * @throws Exception if an error occurs
     */
    private void assertRequestSucceeded(HttpURLConnection connection)
            throws Exception {
        if (connection.getResponseCode() > 299) {
            throw new Exception(String.format("HTTP request error. Response code: %d", connection.getResponseCode()));
        }
    }

    /**
     * Reads the response content.
     *
     * @param connection the connection
     * @param boundary the boundary
     *
     * @throws Exception if an error occurs
     */
    private void uploadFile(HttpURLConnection connection, String boundary)
            throws Exception {
        OutputStream out = connection.getOutputStream();
        InputStream in = null;

        try {
            in = new FileInputStream(this.localFile);
            PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, CHARSET), true);
            writer.append("--" + boundary + "\r\n");
            writer.append("Content-Disposition: form-data; name=\"data\"; filename=\"" + this.localFile.getName() + "\"\r\n");
            writer.append("Content-Transfer-Encoding: binary\r\n\r\n").flush();

            byte[] data = new byte[1024];
            int read;
            while ((read = in.read(data)) != -1) {
                out.write(data, 0, read);
            }

            out.flush();

            writer.append("\r\n").flush();
            writer.append("--" + boundary + "--\r\n").append("\r\n").flush();
        } finally {
            close(in);
            close(out);
        }
    }

    /**
     * Closes the specified stream.
     *
     * @param stream the stream
     */
    private void close(InputStream stream) {
        if (stream == null) {
            return;
        }

        try {
            stream.close();
        } catch (IOException e) {
            // ignore
        }
    }

    /**
     * Closes the specified stream.
     *
     * @param stream the stream
     */
    private void close(OutputStream stream) {
        if (stream == null) {
            return;
        }

        try {
            stream.close();
        } catch (IOException e) {
            // ignore
        }
    }


}
