/**
 * 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.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.SocketException;
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.Properties;

import org.jasypt.util.text.BasicTextEncryptor;

import com.Ostermiller.util.CSVParser;
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;
import com.TwentyCodes.java.OrderProcessor.Status;
import com.TwentyCodes.java.OrderProcessor.UI.SettingsPanel;

/**
 * An SQLite Order Data Base
 * @author ricky barrette
 */
public class OrderDB {
	
	public final static String PASSWORD = "askjdhfui2ywpordhewpfiy2390uriyfu230rhuiyhew8fu293hro9yqewrfup9u34bh0yf3-eqwjf9834-fjud9i-he9-fhuj830uewfh";
	private final String CREATE_TABLE = "CREATE TABLE Orders (id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, item blob);";
	private final String CREATE_TABLE_LOCAL = "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;
	private ArrayList<Order> list;
	private boolean isUsingRemoteDatabase;

	/**
	 * Creates a new OrderDB
	 * @author ricky barrette
	 * @param password 
	 * @throws ClassNotFoundException 
	 * @throws SQLException 
	 * @throws SocketException 
	 * @throws Exception 
	 */
	public OrderDB(String password) throws ClassNotFoundException, SQLException, SocketException {
		
		Properties props = getProperties();
		
		isUsingRemoteDatabase = Boolean.parseBoolean(props.getProperty(SettingsPanel.IS_USING_REMOTE_SERVER, "false"));
	
		if(isUsingRemoteDatabase) {
			Class.forName("com.mysql.jdbc.Driver");
		}
		else {
			Class.forName("org.sqlite.JDBC");
			dbLocation = System.getProperty("user.home") +"/.TwentyCodesOrders";
		}
		
		Connection conn;
		if(password == null)
			conn = getConnection();
		else
			conn = getConnection(password);
		
	    Statement stat = conn.createStatement();
	    
//	    stat.executeUpdate("drop table if exists Orders;");
	    try {
	    	if(isUsingRemoteDatabase)
	    		stat.executeUpdate(CREATE_TABLE);
	    	else
	    		stat.executeUpdate(CREATE_TABLE_LOCAL);
		} catch (SQLException e) {
			//most likely the table already exist
			e.printStackTrace();
		}
	    conn.close();
	}
	
	/**
	 * 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 : list)
			
			if(item.getGoogleOrderNumber() == order.getGoogleOrderNumber())
				if(item.getFulfillmentStatus() == Status.NEW )
					deleteOrder(item);
				else
					throw new OrderExistsException(order.toString());
		
		list.add(order);
		
		saveOrder(order);
	}

	/**
	 * counts the number of lines in a csv 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 {
		int cnt = 1;
		CSVParser csvParser = new CSVParser(new FileInputStream(file));
		while(csvParser.getLine() != null)
			cnt++;
		return cnt;
	}

	/**
	 * deletes the old order that IS BEING replaced 
	 * @throws SQLException
	 * @throws IOException
	 * @throws ClassNotFoundException
	 * @author ricky barrette
	 */
	private void deleteOrder(Order order) throws SQLException, IOException, ClassNotFoundException{
		int row = 1;
		int count = getCount();
		Connection conn = getConnection();
		Statement stat = conn.createStatement();
		ResultSet rs = stat.executeQuery("select * from Orders;");
		ByteArrayInputStream bais;
		ObjectInputStream ins;
		Order item;
		while (rs.next()) {
			
			if(mListener != null)
				mListener.onProgressUpdate("Deleting old Order", false, row++, count);
			bais = new ByteArrayInputStream(rs.getBytes("item"));
		    ins = new ObjectInputStream(bais);
		    item = (Order) ins.readObject();
		    
	    	if (order.getGoogleOrderNumber() == item.getGoogleOrderNumber()){
	    		conn.createStatement().execute("DELETE FROM Orders WHERE id="+rs.getInt("id")+";");
	    		System.out.println("deleting "+ item.toString());
	    	}
		    ins.close();
		 }
		 rs.close();
		 conn.close();

		 if(mListener != null)
     		mListener.onProgressUpdate("Done", false, 1, 1);
     	
	}
	
	/**
	 * 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("Getting All Orders", false, 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, SocketException{
		Properties props = getProperties();
		BasicTextEncryptor passwordEncryptor = new BasicTextEncryptor();
		passwordEncryptor.setPassword(PASSWORD);
		
		if(isUsingRemoteDatabase){
			String url = "jdbc:mysql://"+props.getProperty(SettingsPanel.SERVER_LOCATION)+":"+props.getProperty(SettingsPanel.SERVER_PORT_NUMBER, "3306")+"/"+props.getProperty(SettingsPanel.DATABASE_NAME,"Orders");
			
			if(props.getProperty(SettingsPanel.SERVER_USERNAME).length() > 0)
				url += "?user="+ props.getProperty(SettingsPanel.SERVER_USERNAME); 
			
			if(props.getProperty(SettingsPanel.SERVER_PASSWORD).length() > 0)
				url += "&password="+ passwordEncryptor.decrypt(props.getProperty(SettingsPanel.SERVER_PASSWORD));
			
			return DriverManager.getConnection(url);
					
		}
		else
			return DriverManager.getConnection("jdbc:sqlite:"+ dbLocation+".db");
	}
	
	private Connection getConnection(String password) throws SQLException {
		Properties props = getProperties();
		BasicTextEncryptor passwordEncryptor = new BasicTextEncryptor();
		passwordEncryptor.setPassword(PASSWORD);
		
		if(isUsingRemoteDatabase){
			String url = "jdbc:mysql://"+props.getProperty(SettingsPanel.SERVER_LOCATION)+":"+props.getProperty(SettingsPanel.SERVER_PORT_NUMBER, "3306")+"/"+props.getProperty(SettingsPanel.DATABASE_NAME,"Orders");
			
			if(props.getProperty(SettingsPanel.SERVER_USERNAME).length() > 0)
				url += "?user="+ props.getProperty(SettingsPanel.SERVER_USERNAME); 
			
			url += "&password="+ password;
			
			return DriverManager.getConnection(url);
					
		}
		else
			return DriverManager.getConnection("jdbc:sqlite:"+ dbLocation+".db");
	}
	
	/**
	 * returns the row count
	 * @return number orders in the database
	 * @throws SQLException
	 * @author ricky barrette
	 * @throws SocketException 
	 */
	public int getCount() throws SQLException, SocketException{
		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;
	}
	
	/**
	 * gets the properties from the properties file
	 * @return properties
	 * @author ricky barrette
	 */
	private Properties getProperties() {
		Properties props = new java.util.Properties();
		try {
			FileInputStream in = new FileInputStream(".settings.propertys");
			props.load(in);
			in.close();
			return props;
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		return props;
	}
	
	/**
	 * 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{

		prep = null;
		list = null;
		
		CSVParser csvParser = new CSVParser(new FileInputStream(file));
		list = new ArrayList<Order>(getAllOrders());

		int line = 1;
		int lineCount = countLines(file);
		isLoadingFile = true;
		
		Connection conn = getConnection();
		prep = conn.prepareStatement(INSERT);
		conn.setAutoCommit(true);
			try {
				while (csvParser.lastLineNumber() < lineCount) {

					if (mListener != null)
						mListener.onProgressUpdate(file.toString() + " ~ Parsing Order: "+ line +" of "+ lineCount, false, line++, lineCount);

					if (Main.DEBUG)
						System.out.println("\non line: " + line);

						try {
							addOrder(new Order(csvParser.getLine()));
						} catch (NumberFormatException e) {
							e.printStackTrace();
						} catch (OrderExistsException e) {
							e.printStackTrace();
						} catch (InvalidDateFormatException e) {
							e.printStackTrace();
						}
					
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
			
		prep.close();
		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();
		
        ArrayList<Order> list = new ArrayList<Order>();
        String[] parts = text.split(", ");
        
        for(Order order : getAllOrders()){
        	if(mListener != null && ! isLoadingFile)
        		mListener.onProgressUpdate("Searching for: "+text, false, row++, count);
		    
		    for (int i = 0; i < parts.length; i++) {
				if (isExclusive) {
					if (!order.getItemName().equalsIgnoreCase(parts[i]))
						list.add(order);
				} else if (order.getItemName().equalsIgnoreCase(parts[i])
						|| (orderNumber > 0)
						&& (order.getGoogleOrderNumber() == orderNumber)
						|| order.getCustomerName().toLowerCase(Locale.ENGLISH).contains(parts[i].toLowerCase(Locale.ENGLISH))
						|| order.getCustomerContry().toLowerCase(Locale.ENGLISH).contains(parts[i].toLowerCase(Locale.ENGLISH)))
					list.add(order);
			}
		 }
			
		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;
	}
}