/*
 * Copyright 2019 DeNA Co., Ltd.
 *
 * 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 packetproxy.model;

import com.j256.ormlite.dao.Dao;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import javax.swing.JOptionPane;
import packetproxy.common.Logger;
import packetproxy.model.Database.DatabaseMessage;
import packetproxy.util.PacketProxyUtility;

public class Packets extends Observable implements Observer {
	private static Packets instance;
	
	public static Packets getInstance(boolean restore) throws Exception {
		if (instance == null) {
			instance = new Packets(restore);
		}
		return instance;
	}

	public static Packets getInstance() throws Exception {
		if (instance == null) {
			// 初期化前に通信が走り実行されるとDrop Tableされるので例外を投げる
			throw new Exception("Packets インスタンスが作成されていません。");
		}
		return instance;
	}
	
	private PacketProxyUtility util;
	private Database database;
	private Dao<Packet,Integer> dao;

	private Packets(boolean restore) throws Exception {
		util = PacketProxyUtility.getInstance();
		database = Database.getInstance();
		if(!restore){
			util.packetProxyLog("drop history...");
			database.dropPacketTableFaster();
		}
		dao = database.createTable(Packet.class, this);
		if(restore){
			if (!isLatestVersion()) {
				RecreateTable();
			}
			util.packetProxyLog("load history...");
			util.packetProxyLog("load" + dao.countOf() + " records.");
		}
	}
	// TODO できれば非同期でやる(大きいデータのときに数秒止まってしまうので)
	public void create(Packet packet) throws Exception {
		synchronized (dao) {
			dao.createIfNotExists(packet);
		}
		notifyObservers();
	}
	public void refresh() {
		notifyObservers();
	}
	public void update(Packet packet) throws Exception {
		if(database.isAlertFileSize()){
			notifyObservers(true);
		}
		Dao.CreateOrUpdateStatus status;
		synchronized (dao) {
			status = dao.createOrUpdate(packet);
		}
		if (status.isCreated()) {
			notifyObservers(packet.getId() * -1);
		} else {
			notifyObservers(packet.getId());
		}
	}
	public void deleteAll() throws Exception {
		synchronized (dao) {
			dao.deleteBuilder().delete();
		}
		notifyObservers();
	}
	public void delete(Packet packet) throws Exception {
		synchronized (dao) {
			dao.delete(packet);
		}
		notifyObservers();
	}
	public long countOf() throws Exception {
		return dao.countOf();
	}
	public Packet query(int id) throws Exception {
		return dao.queryForId(id);
	}
	
	public List<Packet> queryAllIds() throws Exception {
		return dao.queryBuilder().selectColumns("id").orderBy("id", true).query();
	}

	public List<Packet> queryRange(long offset, long limit) throws Exception {
		return dao.queryBuilder().offset(offset).limit(limit).orderBy("id", true).query();
	}

	public List<Packet> queryAll() throws Exception {
		return dao.queryBuilder().orderBy("id", true).query();
	}
	public List<Packet> queryMoreThan(int date) throws Exception {
		return dao.queryBuilder().where().gt("id", date).query();
	}
	public List<Packet> queryFullText(String search, int start) throws Exception {
		return dao.queryBuilder().selectColumns("group").where()
				.ge("id", start)
				.and()
				.like("decoded_data",String.format("%%%s%%", search)).query();
	}
	public List<Packet> queryFullTextById(String search, int id) throws Exception {
		return dao.queryBuilder().selectColumns("group").where()
				.eq("id", id)
				.and()
				.like("decoded_data",String.format("%%%s%%", search)).query();
	}
	//case sensitive full text search
	public List<Packet> queryFullText(String search) throws Exception {
		//ORMLite does not support glob statement.
		String query = String.format("SELECT `group`,`id` FROM `packets` WHERE `decoded_data` GLOB '*%s*';", search);
		return dao.queryRaw(query,dao.getRawRowMapper()).getResults();
	}
	//case insensitive full text search
	public List<Packet> queryFullText_i(String search) throws Exception {
		return dao.queryBuilder().selectColumns("group").where()
				.like("decoded_data",String.format("%%%s%%", search)).query();
	}

	@Override
	public void notifyObservers(Object arg) {
		setChanged();
		super.notifyObservers(arg);
		clearChanged();
	}
	public String outputAllPackets(String filename) throws Exception {
		Logger logger = new Logger(queryAll());
		return logger.outputToFile(filename);
	}
	public boolean isEmpty() throws Exception {
		return dao.queryBuilder().limit(1L).query().isEmpty();
	}
	@Override
	public void update(Observable o, Object arg) {
		DatabaseMessage message = (DatabaseMessage)arg;
		try {
			switch (message) {
			case PAUSE:
				// TODO ロックを取る
				break;
			case RESUME:
				// TODO ロックを解除
				break;
			case DISCONNECT_NOW:
				break;
			case RECONNECT:
				database = Database.getInstance();
				dao = database.createTable(Packet.class, this);
				notifyObservers(arg);
				break;
			case RECREATE:
				database = Database.getInstance();
				dao = database.createTable(Packet.class, this);
				break;
			default:
				break;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	private boolean isLatestVersion() throws Exception {
		String result = dao.queryRaw("SELECT sql FROM sqlite_master WHERE name='packets'").getFirstResult()[0];
		//System.out.println(result);
		return result.equals("CREATE TABLE `packets` (`id` INTEGER PRIMARY KEY AUTOINCREMENT , `direction` VARCHAR , `decoded_data` BLOB , `modified_data` BLOB , `sent_data` BLOB , `received_data` BLOB , `listen_port` INTEGER , `client_ip` VARCHAR , `client_port` INTEGER , `server_ip` VARCHAR , `server_name` VARCHAR , `server_port` INTEGER , `use_ssl` BOOLEAN , `content_type` VARCHAR , `encoder_name` VARCHAR , `alpn` VARCHAR , `modified` BOOLEAN , `resend` BOOLEAN , `date` BIGINT , `conn` INTEGER , `group` BIGINT )");
	}
	private void RecreateTable() throws Exception {
		int option = JOptionPane.showConfirmDialog(null,
				"packetsテーブルの形式が更新されているため\n現在のテーブルを削除して再起動しても良いですか?",
				"テーブルの更新",
				JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
		if (option == JOptionPane.YES_OPTION) {
			database.dropTable(Packet.class);
			dao = database.createTable(Packet.class, this);
		}
	}
}