/**
 * OrderDB.java
 * @date Jan 8, 2011
 * @author ricky barrette
 * @author Twenty Codes, LLC
 */
package com.TwentyCodes.java.OrderProcessor.DB;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Locale;
import java.util.Scanner;

import com.TwentyCodes.java.OrderProcessor.Date;
import com.TwentyCodes.java.OrderProcessor.InvalidDateFormatException;
import com.TwentyCodes.java.OrderProcessor.Main;
import com.TwentyCodes.java.OrderProcessor.Order;
import com.TwentyCodes.java.OrderProcessor.OrderExistsException;
import com.TwentyCodes.java.OrderProcessor.ProgressListener;

/**
 * An SQLite Order Data Base
 * @author ricky barrette
 */
public class OrderDB {
	
	private final String CREATE_TABLE = "CREATE TABLE Orders " +
			"(id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," +
			" 'item' blob);";
	
	private final String INSERT = "insert into Orders (item) values (?);";
	private ProgressListener mListener;
	private PreparedStatement prep;
	private String dbLocation;
	private boolean isLoadingFile = false;

	/**
	 * Creates a new OrderDB
	 * @author ricky barrette
	 * @throws ClassNotFoundException 
	 * @throws SQLException 
	 * @throws Exception 
	 */
	public OrderDB() throws ClassNotFoundException, SQLException {
		dbLocation = System.getProperty("user.home") +"/.TwentyCodesOrders";
		Class.forName("org.sqlite.JDBC");
	    Connection conn = getConnection();
	    Statement stat = conn.createStatement();
	    
//	    stat.executeUpdate("drop table if exists Orders;");
	    try {
			stat.executeUpdate(CREATE_TABLE);
		} catch (SQLException e) {
			//most likely the table already exist
			e.printStackTrace();
		}
	    conn.close();
	}
	
	/**
	 * counts the number of lines in a file
	 * @param file to be read
	 * @return number of lines in a file
	 * @throws IOException
	 * @author ricky barrette
	 */
	public int countLines(File file) throws IOException {
		LineNumberReader reader = new LineNumberReader(new FileReader(file));
		int cnt = 0;
		while ((reader.readLine()) != null) ;
		cnt = reader.getLineNumber();
		reader.close();
		return cnt;
	}
	
	/**
	 * added the order to the database
	 * @param order
	 * @throws OrderExistsException 
	 * @throws ClassNotFoundException 
	 * @throws IOException 
	 * @throws SQLException 
	 */
	public void addOrder(Order order) throws OrderExistsException, SQLException, IOException, ClassNotFoundException {
		
		/*
		 * check the current database for an orders with a matching order number
		 */
		for(Order item : getAllOrders())
			if(item.getGoogleOrderNumber() == order.getGoogleOrderNumber())
				throw new OrderExistsException(order.toString());
		
		saveOrder(order);
	}
	
	/**
	 * gets all orders from the database
	 * @return a list of orders
	 * @throws SQLException
	 * @throws IOException
	 * @throws ClassNotFoundException
	 * @author ricky barrette
	 */
	public ArrayList<Order> getAllOrders() throws SQLException, IOException, ClassNotFoundException {
		
		int row = 1;
		int count = getCount();
		Connection conn = getConnection();
		Statement stat = conn.createStatement();
		ResultSet rs = stat.executeQuery("select * from Orders;");
        ArrayList<Order> list = new ArrayList<Order>();
        
        ByteArrayInputStream bais;
	    ObjectInputStream ins;
        while (rs.next()) {
        	if(mListener != null && ! isLoadingFile)
        		mListener.onProgressUpdate(row++, count);
        	bais = new ByteArrayInputStream(rs.getBytes("item"));
		    ins = new ObjectInputStream(bais);
		    list.add((Order) ins.readObject());
		    ins.close();
		 }
		 rs.close();
		 conn.close();
		 Collections.sort(list);
		 return list;
    }
	
	/**
	 * gets the connection to the data base
	 * @return
	 * @throws SQLException
	 * @author ricky barrette
	 */
	private Connection getConnection() throws SQLException{
		return DriverManager.getConnection("jdbc:sqlite:"+ dbLocation+".db");
	}
	
	/**
	 * returns the row count
	 * @return number orders in the database
	 * @throws SQLException
	 * @author ricky barrette
	 */
	public int getCount() throws SQLException{
		int count = -1;
		Connection conn = getConnection();
		ResultSet rs = conn.createStatement().executeQuery("select COUNT(*) from Orders;");
		
		if(rs.next())
			count = rs.getInt(1);
		conn.close();
		return count;
	}
	
	/**
	 * loads the orders from the specified *.CSV file
	 * @param file to be parsed
	 * @author ricky barrette
	 * @throws SQLException 
	 * @throws IOException 
	 * @throws ClassNotFoundException 
	 * @throws OrderExistsException 
	 */
	public void load(File file) throws SQLException, IOException, ClassNotFoundException{
		Scanner scan = new Scanner(file);
		int line = 1;
		int lineCount = countLines(file);
		isLoadingFile = true;
		
		Connection conn = getConnection();
		prep = conn.prepareStatement(INSERT);
		conn.setAutoCommit(true);
		
		while(scan.hasNextLine()){
			
			if(mListener != null)
				mListener.onProgressUpdate(line++, lineCount);
			
			if(Main.DEBUG)
				System.out.println("\non line: "+ line);
			
			try {
				addOrder(new Order(scan.nextLine()));
			} catch (NumberFormatException e) {
				e.printStackTrace();
			} catch (InvalidDateFormatException e) {
				e.printStackTrace();
			} catch (OrderExistsException e) {
				e.printStackTrace();
			}
		}
		conn.close();
		isLoadingFile = false;
	}


	/**
	 * saves an order to the database
	 * @param order
	 * @throws IOException
	 * @throws SQLException
	 * @author ricky barrette
	 */
	private void saveOrder(Order order) throws IOException, SQLException {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);

        oos.writeObject(order);
        oos.flush();
        oos.close();
        bos.close();

        prep.setBytes(1, bos.toByteArray());
        prep.executeUpdate();
	}
	
	/**
	 * returns a list of orders that pertain any information being searched for.
	 * possible seraches include: item name, google order number, customer name
	 * @param text
	 * @param isExclusive true if the search is excluding the string
	 * @return an array list containing all orders meeting search criteria 
	 * @throws SQLException
	 * @throws IOException
	 * @throws ClassNotFoundException
	 * @author ricky barrette
	 */
	public ArrayList<Order> search(String text, boolean isExclusive) throws SQLException, IOException, ClassNotFoundException {
		long orderNumber = -1;
		try {
			orderNumber = Long.parseLong(text);
		} catch (Exception e) {
			// TO NOTHING
		}
		int row = 1;
		int count = getCount();
		
		Connection conn = getConnection();
		Statement stat = conn.createStatement();
		ResultSet rs = stat.executeQuery("select * from Orders;");
        ArrayList<Order> list = new ArrayList<Order>();
        
        ByteArrayInputStream bais;
	    ObjectInputStream ins;
        while (rs.next()) {
        	if(mListener != null && ! isLoadingFile)
        		mListener.onProgressUpdate(row++, count);
        	bais = new ByteArrayInputStream(rs.getBytes("item"));
		    ins = new ObjectInputStream(bais);
		    Order order = (Order) ins.readObject();
		    
		    if (isExclusive){
				if ( ! order.getItemName().equalsIgnoreCase(text)
						|| (orderNumber > 0)
						&& (order.getGoogleOrderNumber() == orderNumber)
						|| order.getCustomerName().toLowerCase(Locale.ENGLISH).contains(text.toLowerCase(Locale.ENGLISH)))
					list.add(order);
			} else if ( order.getItemName().equalsIgnoreCase(text)
						|| (orderNumber > 0)
						&& (order.getGoogleOrderNumber() == orderNumber)
						|| order.getCustomerName().toLowerCase(Locale.ENGLISH).contains(text.toLowerCase(Locale.ENGLISH)))
					list.add(order);
		    
		    ins.close();
		    bais.close();
		 }
		 rs.close();
		 conn.close();
			
		Collections.sort(list);
		return list;
	}
	
	/**
	 * Searches for a specific string in between specific dates
	 * @param search string to search for
	 * @param start date
	 * @param end date
	 * @param isExclusive true if the search is excluding the string
	 * @return list of orders that meet requirements 
	 * @throws SQLException
	 * @throws IOException
	 * @throws ClassNotFoundException
	 * @author ricky barrette
	 */
	public ArrayList<Order> search(String search, Date start, Date end, boolean isExclusive) throws SQLException, IOException, ClassNotFoundException {
		ArrayList<Order> prelist = search(search, isExclusive);
		ArrayList<Order> list = new ArrayList<Order>();
		
		for(Order item : prelist){
			if(start.compareTo(item.getOrderCreationDate()) != 1)
				if(end.compareTo(item.getOrderCreationDate()) != -1)
					list.add(item);
		}
		return list;
	}

	/**
	 * sets a listener for db progress updates
	 * @param listener
	 * @author ricky barrette
	 */
	public void setOnProgressListerner(ProgressListener listener) {
		mListener = listener;
	}
}