/*
 * Copyright 2013 Yuichiro Moriguchi
 *
 * 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 net.morilib.awk.nano.namespace;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import net.morilib.awk.nano.value.AwkInteger;
import net.morilib.awk.nano.value.AwkString;
import net.morilib.awk.nano.value.AwkUndefined;
import net.morilib.awk.nano.value.AwkValue;

/**
 * ルートの名前空間です。
 *
 * @author MORIGUCHI, Yuichiro 2013/03
 */
public class AwkRootNamespace extends AwkSimpleNamespace {

	private static final Pattern DEFPT = Pattern.compile("[ \t]+");

	private String line;
	private List<String> fields;
	private String fieldwidth = null;

	/**
	 * 名前空間を生成します。
	 */
	public AwkRootNamespace() {
		super();
	}

	/* (non-Javadoc)
	 * @see net.morilib.awk.namespace.AwkSimpleNamespace#find(java.lang.String)
	 */
	@Override
	public AwkValue find(String s) {
		if(s.equals("NF")) {
			return AwkInteger.valueOf(fields.size());
		} else {
			return super.find(s);
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.awk.namespace.AwkSimpleNamespace#assign(java.lang.String, net.morilib.awk.value.AwkValue)
	 */
	@Override
	public void assign(String s, AwkValue v) {
		if(s.equals("FIELDWIDTHS"))  fieldwidth = v.toString();
		if(s.equals("FS"))  fieldwidth = null;
		if(!s.equals("NF")) {
			super.assign(s, v);
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.awk.namespace.AwkSimpleNamespace#referField(int)
	 */
	public AwkValue referField(int n) {
		if(n < 0) {
			return AwkUndefined.UNDEF;
		} else if(n == 0) {
			return AwkString.valueOf(line);
		} else if(n <= fields.size()) {
			return AwkString.valueOf(fields.get(n - 1));
		} else {
			return AwkUndefined.UNDEF;
		}
	}

	private void _joinField() {
		StringBuffer b = new StringBuffer();
		String ofs = getOFS(), d = "";

		for(String s : fields) {
			b.append(d).append(s);
			d = ofs;
		}
		line = b.toString();
	}

	/* (non-Javadoc)
	 * @see net.morilib.awk.namespace.AwkSimpleNamespace#setField(int, java.lang.String)
	 */
	public void setField(int n, String v) {
		if(n < 0) {
			// do nothing
		} else if(n == 0) {
			setField(v);
		} else if(n <= fields.size()) {
			fields.set(n - 1, v);
			_joinField();
		} else {
			for(int i = sizeOfField(); i < n - 1; i++) {
				fields.add("");
			}
			fields.add(v);
			_joinField();
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.awk.namespace.AwkSimpleNamespace#sizeOfField()
	 */
	public int sizeOfField() {
		return fields.size();
	}

	void setField(String s, String fs, boolean seq) {
		StringBuffer b = null;
		boolean ws = seq;
		int c;

		line = s;
		fields = new ArrayList<String>();
		for(int i = 0; i < s.length(); i += c > 0xffff ? 2 : 1) {
			c = s.codePointAt(i);
			if(ws && fs.indexOf(c) < 0) {
				b = new StringBuffer().appendCodePoint(c);
				ws = false;
			} else if(!ws && fs.indexOf(c) >= 0) {
				fields.add(b == null ? "" : b.toString());
				b = null;
				ws = seq;
			} else if(!ws) {
				if(b == null)  b = new StringBuffer();
				b.appendCodePoint(c);
			}
		}
		if(b != null)  fields.add(b.toString());
	}

	void setFieldRegex(String s, String re) {
		try {
			line = s;
			fields = Arrays.asList(s.split(re));
	
			if(fields.size() > 0 && fields.get(0).equals("")) {
				fields = new ArrayList<String>(fields);
				fields.remove(0);
			}
	
			if(fields.size() > 0 &&
					fields.get(fields.size() - 1).equals("")) {
				fields = new ArrayList<String>(fields);
				fields.remove(fields.size() - 1);
			}
		} catch(PatternSyntaxException e) {
			fields.add(s);
		}
	}

	int[] getFieldwidth(String rs) {
		String[] a = rs.split(" ");
		int[] r = new int[a.length];

		for(int i = 0; i < a.length; i++) {
			try {
				r[i] = Integer.parseInt(a[i]);
				r[i] = r[i] < 0 ? 0 : r[i];
			} catch(NumberFormatException e) {
				r[i] = 0;
			}
		}
		return r;
	}

	void setFixed(String s, String rs, boolean trim) {
		int[] fl = getFieldwidth(rs);
		int x = 0;
		String t;

		fields = new ArrayList<String>();
		for(int i = 0; i < fl.length; i++) {
			if(x + fl[i] >= s.length()) {
				t = s.substring(x);
				if(trim)  t = t.trim();
				fields.add(t);
				return;
			} else {
				t = s.substring(x, x + fl[i]);
				if(trim)  t = t.trim();
				fields.add(t);
				x += fl[i];
			}
		}
		t = s.substring(x);
		if(trim)  t = t.trim();
		fields.add(t);
	}

	void setProp(String s, String rs) {
		Matcher m;
		int l;

		line = s;
		fields = new ArrayList<String>();
		if(rs.equals("")) {
			fields.add(s);
			return;
		} else if(rs.equals(" ")) {
			m = DEFPT.matcher(s);
		} else if(rs.length() == 1) {
			if((l = s.indexOf(rs.charAt(0))) < 0) {
				fields.add(s);
			} else if(l + 1 == s.length()) {
				fields.add(s.substring(0, l));
			} else {
				fields.add(s.substring(0, l));
				fields.add(s.substring(l + 1));
			}
			return;
		} else {
			try {
				m = Pattern.compile(rs).matcher(s);
			} catch(PatternSyntaxException e) {
				fields.add(s);
				return;
			}
		}

		if(!m.find()) {
			fields.add(s);
		} else if(m.end() == s.length()) {
			fields.add(s.substring(0, m.start()));
		} else {
			fields.add(s.substring(0, m.start()));
			fields.add(s.substring(m.end()));
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.awk.namespace.AwkSimpleNamespace#setField(java.lang.String)
	 */
	public void setField(String s) {
		AwkValue pm = find("SEPMODE");
		String rs = getFS(), md;

		md = pm == null ? "" : pm.toString();
		if(fieldwidth != null) {
			setFixed(s, fieldwidth, false);
		} else if(md.equals("fixed")) {
			setFixed(s, rs, false);
		} else if(md.equals("fixed trim")) {
			setFixed(s, rs, true);
		} else if(md.equals("property")) {
			setProp(s, rs);
		} else if(rs.equals("")) {
			line = s;
			fields = new ArrayList<String>();
			fields.add(s);
		} else if(rs.equals(" ")) {
			setField(s, " \t", true);
		} else if(rs.length() == 1) {
			setField(s, rs, false);
		} else {
			setFieldRegex(s, rs);
		}
	}

	/* (non-Javadoc)
	 * @see net.morilib.awk.namespace.AwkSimpleNamespace#getFS()
	 */
	public String getFS() {
		return find("FS").toString();
	}

	/* (non-Javadoc)
	 * @see net.morilib.awk.namespace.AwkSimpleNamespace#getRS()
	 */
	public String getRS() {
		return find("RS").toString();
	}

	/* (non-Javadoc)
	 * @see net.morilib.awk.namespace.AwkSimpleNamespace#getOFS()
	 */
	public String getOFS() {
		return find("OFS").toString();
	}

	/* (non-Javadoc)
	 * @see net.morilib.awk.namespace.AwkSimpleNamespace#getORS()
	 */
	public String getORS() {
		return find("ORS").toString();
	}

	/* (non-Javadoc)
	 * @see net.morilib.awk.namespace.AwkSimpleNamespace#getRoot()
	 */
	public AwkNamespace getRoot() {
		return this;
	}

	/* (non-Javadoc)
	 * @see net.morilib.awk.namespace.AwkSimpleNamespace#isRoot()
	 */
	public boolean isRoot() {
		return true;
	}

	/* (non-Javadoc)
	 * @see net.morilib.awk.namespace.AwkNamespace#isIgnoreCase()
	 */
	public boolean isIgnoreCase() {
		AwkValue v = find("IGNORECASE");

		return v != null && v.toInteger().signum() != 0;
	}

}
