/*
 * Copyright 2009 Andrew Prunicki <prunand@iit.edu,prunicki@ociweb.com>
 * 
 * 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.ociweb.jnb.protobuf;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.List;

import com.ociweb.jnb.db.Database;
import com.ociweb.jnb.protobuf.gen.CourseProtos;
import com.ociweb.jnb.protobuf.gen.CourseProtos.Course;
import com.ociweb.jnb.protobuf.gen.CourseProtos.CourseList.Builder;

public class ConnectionHandler implements Runnable {
    
    private final Socket socket;
    private final Database db;
    private final ByteArrayOutputStream tempOs;

    public ConnectionHandler(final Socket socket, final Database db) {
        this.socket = socket;
        this.db = db;
        
        tempOs = new MyByteArrayOutputStream();
    }

    @Override
    public void run() {
        try {
            final InputStream is = socket.getInputStream();
            final OutputStream os = socket.getOutputStream();
            
            boolean proceed = true;
            while (proceed) {
                final int headerByte = is.read();

                if (headerByte < 0) {
                    proceed = false;
                } else if (headerByte == ProtoBufServer.START_MESSAGE) {
                    processMessage(is, os);
                } else {
                    System.err.println("Unknown message header " + headerByte);
                    proceed = false;
                }
            }
        } catch (IOException e) {
            e.printStackTrace(System.err);
        } catch (RuntimeException e) {
            e.printStackTrace(System.err);
        } finally {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace(System.err);
            }
        }
    }

    private void processMessage(final InputStream is, final OutputStream os) throws IOException {
        final BufferedInputStream bis = new BufferedInputStream(is);
        final DataInputStream dis = new DataInputStream(bis);
        final BufferedOutputStream bos = new BufferedOutputStream(os);
        
        final int msgLen = dis.readInt() - 1;
        final int callType = bis.read();
        
        switch (callType) {
            case ProtoBufServer.GET_COURSE_INVENTORY:
                processGetInventory(bos, msgLen);
                break;
            case ProtoBufServer.GET_COURSE:
                processGetCourse(bis, bos, msgLen);
                break;
            case ProtoBufServer.ADD_COURSE:
                break;
            case ProtoBufServer.DEL_COURSE:
                processDeleteCourse(bis, msgLen);
                break;
            default:
                throw new RuntimeException("Unknown request type " + callType);
        }
    }

    private void processGetInventory(final BufferedOutputStream bos, final int msgLen) throws IOException {
        if (msgLen != 0) {
            throw new IllegalStateException("There should be no message beyond the calltype for getCourseInventory");
        }

        //Create output
        tempOs.reset();
        final List<String> courseList = db.getCourseList();
        final Builder courseListBuilder = CourseProtos.CourseList.newBuilder();
        for (final String courseNum : courseList) {
            courseListBuilder.addNumber(courseNum);
        }
        courseListBuilder.build().writeTo(tempOs);
        final byte[] output = tempOs.toByteArray();
        final int outputSize = tempOs.size();
        
        //Write it
        final DataOutputStream dos = new DataOutputStream(bos);
        dos.writeInt(outputSize + 1);
        dos.write(ProtoBufServer.GET_COURSE_INVENTORY);
        dos.write(output, 0, outputSize);
        dos.flush();
    }
    
    private void processGetCourse(final BufferedInputStream bis, final BufferedOutputStream bos, final int msgLen)
            throws IOException {
        final byte[] msg = new byte[msgLen];
        bis.read(msg);
        final String courseNum = new String(msg);
        
        //Create output
        tempOs.reset();
        final com.ociweb.jnb.db.Course dbCourse = db.getCourse(courseNum);
        final Course course = ConversionHelper.fromDbCourse(dbCourse);
        course.writeTo(tempOs);
        final byte[] output = tempOs.toByteArray();
        final int outputSize = tempOs.size();
        
        //Write it
        final DataOutputStream dos = new DataOutputStream(bos);
        dos.writeInt(outputSize + 1);
        dos.write(ProtoBufServer.GET_COURSE);
        dos.write(output, 0, outputSize);
        dos.flush();
    }

    private void processDeleteCourse(final BufferedInputStream bis, final int msgLen) throws IOException {
        final byte[] msg = new byte[msgLen];
        bis.read(msg);
        final String courseNum = new String(msg);
        db.deleteCourse(courseNum);
    }
    
    private class MyByteArrayOutputStream extends ByteArrayOutputStream {
        
        public MyByteArrayOutputStream() {
            super(2048);
        }

        @Override
        public synchronized byte[] toByteArray() {
            //Change to not copy the buffer
            return buf;
        }
    }
}
