package meerkat.bulletinboard.webapp;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.StreamingOutput;

import com.google.protobuf.BoolValue;
import com.google.protobuf.Int32Value;
import com.google.protobuf.Int64Value;
import meerkat.bulletinboard.BulletinBoardServer;
import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer;
import meerkat.bulletinboard.sqlserver.H2QueryProvider;
import meerkat.bulletinboard.sqlserver.MySQLQueryProvider;
import meerkat.bulletinboard.sqlserver.SQLiteQueryProvider;
import meerkat.comm.CommunicationException;
import meerkat.comm.MessageOutputStream;
import meerkat.protobuf.BulletinBoardApi.*;
import meerkat.protobuf.BulletinBoardWebapp.*;


import static meerkat.bulletinboard.BulletinBoardConstants.*;
import static meerkat.rest.Constants.*;

import java.io.IOException;
import java.io.OutputStream;

/**
 * An implementation of the BulletinBoardServer which functions as a WebApp
 */
@Path(BULLETIN_BOARD_SERVER_PATH)
public class BulletinBoardWebApp implements BulletinBoardServer, ServletContextListener {
		
	private static final String BULLETIN_BOARD_ATTRIBUTE_NAME = "bulletinBoard";
	
	@Context ServletContext servletContext;
	
	BulletinBoardServer bulletinBoard;
	
	/**
	 * This is the servlet init method.
	 */
	public void init(){
		bulletinBoard = (BulletinBoardServer) servletContext.getAttribute(BULLETIN_BOARD_ATTRIBUTE_NAME);
	}



    @Override
	public void contextInitialized(ServletContextEvent servletContextEvent) {
		ServletContext servletContext = servletContextEvent.getServletContext();
		String dbType = servletContext.getInitParameter("dbType");
		String dbName = servletContext.getInitParameter("dbName");

		if ("SQLite".equalsIgnoreCase(dbType)){

			bulletinBoard = new BulletinBoardSQLServer(new SQLiteQueryProvider(dbName));

		} else if ("H2".equalsIgnoreCase(dbType)) {

			bulletinBoard = new BulletinBoardSQLServer(new H2QueryProvider(dbName));

		} else if ("MySQL".equalsIgnoreCase(dbType)) {

			String dbAddress = servletContext.getInitParameter("dbAddress");
			int dbPort = Integer.parseInt(servletContext.getInitParameter("dbPort"));
			String username = servletContext.getInitParameter("username");
			String password = servletContext.getInitParameter("password");

			bulletinBoard = new BulletinBoardSQLServer(new MySQLQueryProvider(dbAddress,dbPort,dbName,username,password));
		}

		try {
			bulletinBoard.init();
			servletContext.setAttribute(BULLETIN_BOARD_ATTRIBUTE_NAME, bulletinBoard);
		} catch (CommunicationException e) {
			System.err.println(e.getMessage());
		}
	}


    /**
     * Get the current status of the bulletin board server.
     * @return
     * @throws CommunicationException
     */
    @Path(STATUS_PATH)
    @GET
    @Consumes(MEDIATYPE_PROTOBUF)
    @Produces(MEDIATYPE_PROTOBUF)
    public ServerStatus getStatus(@Context HttpServletResponse response) throws CommunicationException {
        // response.setHeader("Access-Control-Allow-Origin", "*"); // TODO: Remove
        return ServerStatus.newBuilder()
                .setName("Hello")
                .build();
    }

	@Path(POST_MESSAGE_PATH)
	@POST
	@Consumes(MEDIATYPE_PROTOBUF)
	@Produces(MEDIATYPE_PROTOBUF)
	@Override
	public BoolValue postMessage(BulletinBoardMessage msg) throws CommunicationException {
		init();
		return bulletinBoard.postMessage(msg);
	}

	@Override
	public void readMessages(MessageFilterList filterList, MessageOutputStream<BulletinBoardMessage> out) throws CommunicationException {
		init();
		bulletinBoard.readMessages(filterList, out);
	}

	@Path(COUNT_MESSAGES_PATH)
	@POST
	@Consumes(MEDIATYPE_PROTOBUF)
	@Produces(MEDIATYPE_PROTOBUF)
	@Override
	public Int32Value getMessageCount(MessageFilterList filterList) throws CommunicationException {
		init();
		return bulletinBoard.getMessageCount(filterList);
	}


	@Path(READ_MESSAGES_PATH)
	@POST
	@Consumes(MEDIATYPE_PROTOBUF)
	/**
	 * Wrapper for the readMessages method which streams the output into the response
	 */
	public StreamingOutput readMessages(final MessageFilterList filterList) {

		return new StreamingOutput() {

			@Override
			public void write(OutputStream output) throws IOException, WebApplicationException {
				MessageOutputStream<BulletinBoardMessage> out = new MessageOutputStream<>(output);

				try {
					init();
					bulletinBoard.readMessages(filterList, out);
				} catch (CommunicationException e) {
					//TODO: Log
					out.writeMessage(null);
				}
			}

		};

	}

	@Path(BEGIN_BATCH_PATH)
	@POST
	@Consumes(MEDIATYPE_PROTOBUF)
	@Produces(MEDIATYPE_PROTOBUF)
	@Override
	public Int64Value beginBatch(BeginBatchMessage message) {
		try {
			init();
			return bulletinBoard.beginBatch(message);
		} catch (CommunicationException e) {
			System.err.println(e.getMessage());
			return null;
		}
	}

	@Path(POST_BATCH_PATH)
	@POST
	@Consumes(MEDIATYPE_PROTOBUF)
	@Produces(MEDIATYPE_PROTOBUF)
	@Override
	public BoolValue postBatchMessage(BatchMessage batchMessage) {
		try {
			init();
			return bulletinBoard.postBatchMessage(batchMessage);
		} catch (CommunicationException e) {
			System.err.println(e.getMessage());
			return null;
		}
	}

	@Path(CLOSE_BATCH_PATH)
	@POST
	@Consumes(MEDIATYPE_PROTOBUF)
	@Produces(MEDIATYPE_PROTOBUF)
	@Override
	public BoolValue closeBatch(CloseBatchMessage message) {
		try {
			init();
			return bulletinBoard.closeBatch(message);
		} catch (CommunicationException e) {
			System.err.println(e.getMessage());
			return null;
		}
	}


	@Override
	public void readBatch(BatchQuery batchQuery, MessageOutputStream<BatchChunk> out) throws CommunicationException, IllegalArgumentException {
		try {
			init();
			bulletinBoard.readBatch(batchQuery, out);
		} catch (CommunicationException | IllegalArgumentException e) {
			System.err.println(e.getMessage());
		}
	}

	@Path(GENERATE_SYNC_QUERY_PATH)
	@POST
	@Consumes(MEDIATYPE_PROTOBUF)
	@Produces(MEDIATYPE_PROTOBUF)
	@Override
	public SyncQuery generateSyncQuery(GenerateSyncQueryParams generateSyncQueryParams) throws CommunicationException {
		try {
			init();
			return bulletinBoard.generateSyncQuery(generateSyncQueryParams);
		} catch (CommunicationException | IllegalArgumentException e) {
			System.err.println(e.getMessage());
			return null;
		}
	}

	@Path(READ_BATCH_PATH)
	@POST
	@Consumes(MEDIATYPE_PROTOBUF)
	/**
	 * Wrapper for the readBatch method which streams the output into the response
	 */
	public StreamingOutput readBatch(final BatchQuery batchQuery) {

		return new StreamingOutput() {

			@Override
			public void write(OutputStream output) throws IOException, WebApplicationException {
				MessageOutputStream<BatchChunk> out = new MessageOutputStream<>(output);

				try {
					init();
					bulletinBoard.readBatch(batchQuery, out);
				} catch (CommunicationException e) {
					//TODO: Log
					out.writeMessage(null);
				}
			}

		};

	}

	@Path(SYNC_QUERY_PATH)
	@POST
	@Consumes(MEDIATYPE_PROTOBUF)
	@Produces(MEDIATYPE_PROTOBUF)
	@Override
	public SyncQueryResponse querySync(SyncQuery syncQuery) throws CommunicationException {
		try{
			init();
			return bulletinBoard.querySync(syncQuery);
		} catch (CommunicationException | IllegalArgumentException e) {
			System.err.println(e.getMessage());
			return null;
		}
	}

	@Override
	public void close(){
		try {
			bulletinBoard.close();
		} catch (CommunicationException e) {
			System.err.println(e.getMessage());
		}
	}
	
	@GET
    @Produces(MediaType.TEXT_PLAIN)
	public String test() {
        return "This BulletinBoard is up and running!\n Please consult the API documents to perform queries.";
	}
	
	@Override
	public void contextDestroyed(ServletContextEvent servletContextEvent) {
		ServletContext servletContext = servletContextEvent.getServletContext();
		bulletinBoard = (BulletinBoardServer) servletContext.getAttribute(BULLETIN_BOARD_ATTRIBUTE_NAME);
		close();
	}
	
}