/*
 * 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.google.re2j.Matcher;
import com.google.re2j.Pattern;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import packetproxy.common.Binary;
import packetproxy.common.Binary.HexString;
import packetproxy.common.Utils;
import org.apache.commons.lang3.ArrayUtils;

@DatabaseTable(tableName = "modifications")
public class Modification
{
	public static final int ALL_SERVER = -1;
	public enum Direction { CLIENT_REQUEST, SERVER_RESPONSE, ALL };
	public enum Method { SIMPLE, REGEX, BINARY };

	@DatabaseField(generatedId = true)
	private int id;
	@DatabaseField
	private Boolean enabled;
	@DatabaseField(uniqueCombo = true)
	private int server_id;
	@DatabaseField(uniqueCombo = true)
	private Direction direction;
	@DatabaseField(uniqueCombo = true)
	private String pattern;
	@DatabaseField(uniqueCombo = true)
	private Method method;
	@DatabaseField
	private String replaced;

	public Modification() {
		// ORMLite needs a no-arg constructor 
	}
	public Modification(Direction direction, String pattern, String replaced, Method method, Server server) {
		this.enabled = false;
		this.server_id = server != null ? server.getId() : ALL_SERVER;
		this.direction = direction;
		this.pattern = pattern;
		this.replaced = replaced;
		this.method = method;
	}
	public boolean isEnabled() {
		return this.enabled;
	}
	public void setEnabled() {
		this.enabled = true;
	}
	public void setDisabled() {
		this.enabled = false;
	}
	public int getServerId() {
		return this.server_id;
	}
	public void setServerId(int server_id) {
		this.server_id = server_id;
	}
	public Server getServer() throws Exception {
		return Servers.getInstance().query(this.server_id);
	}
	public String getServerName() throws Exception {
		if (this.server_id == ALL_SERVER) { return "*"; }
		Server server = Servers.getInstance().query(this.server_id);
		return server != null ? server.toString() : "";
	}
	public Direction getDirection() {
		return this.direction;
	}
	public void setDirection(Direction direction) {
		this.direction = direction;
	}
	public String getPattern() {
		return this.pattern;
	}
	public void setPattern(String pattern) {
		this.pattern = pattern;
	}
	public String getReplaced() {
		return this.replaced;
	}
	public void setReplaced(String replaced) {
		this.replaced = replaced;
	}
	public Method getMethod() {
		return this.method;
	}
	public void setMethod(Method method) {
		this.method = method;
	}
	public int getId() {
		return id;
	}
	public byte[] replace(byte[] data, Packet packet) throws Exception {
		if (method == Method.SIMPLE) {
			return replaceText(data, packet);
		} else if (method == Method.REGEX) {
			return replaceRegex(data, packet);
		} else if (method == Method.BINARY) {
			return replaceBinary(data, packet);
		} else {
			throw new Exception("未定義の置換方法");
		}
	}
	private byte[] replaceText(byte[] data, Packet packet) {
		return replaceBinary(data, pattern.getBytes(), replaced.getBytes(), packet);
	}
	private byte[] replaceRegex(byte[] data, Packet packet) {
		Pattern pattern = Pattern.compile(this.pattern, Pattern.MULTILINE);
		Matcher matcher = pattern.matcher(new String(data));
		String result = new String(data);
		while (matcher.find()) {
			result = matcher.replaceAll(this.replaced);
			packet.setModified();
		}
		return result.getBytes();
	}
	private byte[] replaceBinary(byte[] data, Packet packet) throws Exception {
		byte[] binPattern = new Binary(new HexString(pattern)).toByteArray();
		byte[] binReplaced = new Binary(new HexString(replaced)).toByteArray();
		return replaceBinary(data, binPattern, binReplaced, packet);
	}
	private byte[] replaceBinary(byte[] data, byte[] binPattern, byte[] binReplaced, Packet packet) {
		int idx = 0;
		while (idx < data.length) {
			if ((idx = Utils.indexOf(data, idx, data.length, binPattern)) < 0) {
				return data;
			}
			byte[] front_data = ArrayUtils.subarray(data, 0, idx);
			byte[] back_data = ArrayUtils.subarray(data, idx + binPattern.length, data.length);
			data = ArrayUtils.addAll(front_data, binReplaced);
			data = ArrayUtils.addAll(data, back_data);
			idx = idx + binReplaced.length;
			packet.setModified();
		}
		return data;
	}
	@Override
	public int hashCode() {
		return this.getId();
	}
	public boolean equals(Modification obj) {
		return this.getId() == obj.getId() ? true : false;
	}
}