/*
 * 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.bc;

import java.io.IOException;
import java.io.InputStream;
import java.io.PipedReader;
import java.io.PipedWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;

import net.morilib.dc.DcDefaultModel;
import net.morilib.dc.DcInterpreter;
import net.morilib.dc.DcModel;
import net.morilib.dc.DcParser;

/**
 * 
 *
 *
 * @author MORIGUCHI, Yuichiro 2013/07/14
 */
public class BcParser {

	/* 
	 * S' -> <statement>
	 * 
	 * <statement-list> -> <statement-list> <statement>
	 *                   | <statement> ;
	 * 
	 * <statement> -> <expression>
	 *              | <define>
	 *              | string-literal
	 *              | <print-statement>
	 *              | { <statement-list> }
	 *              | <if-statement>
	 *              | <while-statement>
	 *              | <for-statement>
	 *              | break
	 *              | continue
	 *              | halt
	 *              | <return-statement>
	 *              | limits
	 *              | quit
	 *              | throw string-literal
	 *              | <empty>
	 * 
	 * <define> -> define symbol ( <variable-list> ) {
	 *               <auto-list>
	 *               <statement-list>
	 *               }
	 * 
	 * <variable-list> -> <variable-list> , symbol
	 *                  | symbol
	 * 
	 * <auto-list> -> auto <variable-list>
	 *              | <empty>
	 * 
	 * <print-statement> -> print <print-list>
	 * 
	 * <print-list> -> <expression-list> , <expression>
	 *               | <expression>
	 *               | string-literal
	 * 
	 * <if-statement> -> if ( <expression> ) <statement>
	 *                 | if ( <expression> ) <statement> else <statement>
	 * 
	 * <while-statement> -> while ( <expression> ) <statement>
	 * 
	 * <for-statement> -> for ( <option-expression> ;
	 *                          <option-expression> ;
	 *                          <option-expression> )
	 *                      <statement>
	 * 
	 * <option-expression> -> <expression>
	 *                      | <empty>
	 * 
	 * <return-statement> -> return <expression>
	 *                     | return
	 * 
	 * <expression> -> ( <expression> )
	 *               | ++ <variable>
	 *               | -- <variable>
	 *               | <variable> ++
	 *               | <variable> --
	 *               | - <expression>
	 *               | <expression> ^ <expression>
	 *               | <expression> / <expression>
	 *               | <expression> * <expression>
	 *               | <expression> % <expression>
	 *               | <expression> + <expression>
	 *               | <expression> - <expression>
	 *               | symbol assign-operator <expression>
	 *               | <variable> relation-operator <expression>
	 *               | ! <expression>
	 *               | <expression> && <expression>
	 *               | <expression> || <expression>
	 *               | symbol ( <expression-list> )
	 *               | symbol [ <expression> ]
	 *               | variable
	 *               | number
	 * 
	 * <variable> -> symbol [ <expression> ]
	 *             | symbol
	 * 
	 * <expression-list> -> <expression-list> , <expression>
	 *                    | <expression>
	 */

	//
	private DcParser<DcModel> dcparser;
	private DcModel dcmodel;
	private PrintStream output;

	/**
	 * 
	 * @param ins
	 * @param ous
	 */
	public BcParser(InputStream ins, PrintStream ous) {
		output = ous;
		dcmodel = new DcDefaultModel(ins, ous);
		dcparser = new DcParser<DcModel>(DcInterpreter.getInstance(),
				dcmodel);
	}

	void _statementList(BcLexer lex, BcNamespace ns, int lv, int fv,
			boolean lp, PrintWriter w) throws IOException {
		do {
			_statement(lex, ns, lv, fv, lp, true, w);
			if(!lex.isEof())  lex.eateos();
		} while(!lex.isEof() && !lex.eqchar('}'));
	}

	void _statement(BcLexer lex, BcNamespace ns, int lv, int fv,
			boolean lp, boolean pr, PrintWriter w) throws IOException {
		BcStringLiteral s;
		BcExprParser p;

		if(lex.isSymbol("define")) {
			_define(lex, ns, 0, w);
		} else if((s = lex.getString()) != null) {
			w.format("[%s]", s.toString());
		} else if(lex.isSymbol("print")) {
			_printList(lex, ns, lv, w);
		} else if(lex.eqchar('{')) {
			_statementList(lex, ns, lv, fv, lp, w);
		} else if(lex.isSymbol("if")) {
			_if(lex, ns, lv, fv, lp, w);
		} else if(lex.isSymbol("while")) {
			_while(lex, ns, lv, w);
		} else if(lex.isSymbol("for")) {
			_for(lex, ns, lv, w);
		} else if(lex.isSymbol("break")) {
			if(!lp) {
				throw new BcSyntaxException();
			}
			w.format("%dQ", fv + 2);
		} else if(lex.isSymbol("continue")) {
			if(!lp) {
				throw new BcSyntaxException();
			}
			w.format("%dQ", fv + 1);
		} else if(lex.isSymbol("halt")) {
			w.print('停');
		} else if(lex.isSymbol("return")) {
			p = new BcExprParser(lex);
			if(lex.eqchar('(')) {
				p.parseExpression(ns, w);
				lex.eatchar(')');
			} else if(!lex.isEof() && !lex.checkeos()) {
				p.parseExpression(ns, w);
			} else {
				w.print("0 ");
			}
			w.format("%dQ", lv + 1);
		} else if(lex.isSymbol("throw")) {
			if((s = lex.getString()) != null) {
				w.format("[%s]誤", s.toString());
			} else {
				throw new BcSyntaxException();
			}
		} else if(lex.isSymbol("limits")) {
			// do nothing
		} else if(lex.isSymbol("quit")) {
			throw new BcQuitException();
		} else if(!lex.checkeos()) {
			p = new BcExprParser(lex);
			if(p.parseExpression(ns, w) && pr) {
				w.print('p');
			}
			w.print('捨');
		}
	}

	private void putz(String f, List<String> k, BcNamespace ns,
			PrintWriter w) {
		char d;

		if(k != null) {
			for(int i = 0; i < k.size(); i++) {
				d = ns.getCharacter(k.get(i));
				w.format(f, d);
			}
		}
	}

	void _define(BcLexer lex, BcNamespace ns, int lv,
			PrintWriter w) throws IOException {
		List<String> l, k = null;
		String s;
		char c;

		s = lex.eatSymbol();
		lex.eatchar('(');
		l = _variableList(lex, ns, w);
		lex.eatchar(')');  lex.skipnl();

		c = ns.defineFunction(s, l);
		lex.eatchar('{');  lex.skipnl();

		if(lex.isSymbol("auto")) {
			k = _variableList(lex, ns, w);
			for(int i = 0; i < k.size(); i++) {
				ns.allocLocalCharacter(k.get(i));
			}
			lex.eqchar(';');  lex.skipnl();
		}

		w.print('[');
		for(int i = l.size() - 1; i >= 0; i--) {
			w.format("S%c", ns.allocLocalCharacter(l.get(i)));
		}
		putz("0S%c", k, ns, w);
		w.print('[');
		_statementList(lex, ns, 0, 0, false, w);
//		_statement(lex, m, 0, 0, false, true, w);
		w.print("]x");
		putz("L%c捨", k, ns, w);
		putz("L%c捨", l, ns, w);
		w.format("]s%c", c);

		ns.clearLocalCharacter();
	}

	List<String> _variableList(BcLexer lex, BcNamespace ns,
			PrintWriter w) throws IOException {
		List<String> l = new ArrayList<String>();

		lex.skipnl();
		if(lex.checkchar(')'))  return l;
		do {
			lex.skipnl();
			l.add(lex.eatSymbol());
			lex.skipnl();
		} while(lex.eqchar(','));
		return l;
	}

	void _printList(BcLexer lex, BcNamespace ns, int lv,
			PrintWriter w) throws IOException {
		BcStringLiteral s;
		BcExprParser p;
		int n = 0;

		do {
			if((s = lex.getString()) != null) {
				w.format("[%s]", s.toString());
			} else {
				p = new BcExprParser(lex);
				p.parseExpression(ns, w);
			}
			n++;
		} while(lex.eqchar(','));
		w.format("%d刷", n);
	}

	void _if(BcLexer lex, BcNamespace ns, int lv, int fv,
			boolean lp, PrintWriter w) throws IOException {
		BcExprParser p;

		lex.setnl();
		lex.eatchar('(');
		p = new BcExprParser(lex);
		p.parseExpression(ns, w);
		lex.eatchar(')');  lex.skipnl();

		w.print('[');
		_statement(lex, ns, lv + 1, fv + 1, lp, true, w);
		w.print(']');

		lex.setnl();
		w.print('[');
		if(lex.isSymbol("else")) {
			lex.skipnl();
			_statement(lex, ns, lv + 1, fv + 1, lp, true, w);
		}
		w.print("]？");
	}

	void _while(BcLexer lex, BcNamespace ns, int lv,
			PrintWriter w) throws IOException {
		StringWriter a = new StringWriter();
		PrintWriter  b = new PrintWriter(a);
		BcExprParser p;
		char c;

		lex.setnl();
		lex.eatchar('(');
		p = new BcExprParser(lex);
		p.parseExpression(ns, b);
		lex.eatchar(')');  lex.skipnl();

		w.format("[%s[[", a.toString());
		c = ns.pushLoop();
		_statement(lex, ns, lv + 2, 0, true, true, w);
		w.format("]xl%c走][]岐]s%cl%cx", c, c, c);
	}

	void _for(BcLexer lex, BcNamespace ns, int lv,
			PrintWriter w) throws IOException {
		StringWriter a = new StringWriter();
		StringWriter x = new StringWriter();
		PrintWriter  b = new PrintWriter(a);
		PrintWriter  y = new PrintWriter(x);
		BcExprParser p;
		char c;

		lex.setnl();
		lex.eatchar('(');
		if(!lex.eqchar(';')) {
			p = new BcExprParser(lex);
			p.parseExpression(ns, w);  w.print('捨');
			lex.eqchar(';');
		}

		if(!lex.eqchar(';')) {
			p = new BcExprParser(lex);
			p.parseExpression(ns, b);
			lex.eqchar(';');
		} else {
			b.print('1');
		}

		if(!lex.eqchar(')')) {
			p = new BcExprParser(lex);
			p.parseExpression(ns, y);  y.print("捨");
			lex.eqchar(')');
		}
		lex.skipnl();

		w.format("[%s[[", a.toString());
		c = ns.pushLoop();
		_statement(lex, ns, lv + 2, 0, true, true, w);
		w.format("]x%sl%c走][]岐]s%cl%cx", x.toString(), c, c, c);
		ns.popLoop();
	}

	/**
	 * 
	 * @param lex
	 * @param ns
	 * @param w
	 * @throws IOException
	 */
	public void parse(BcLexer lex, BcNamespace ns,
			PrintWriter w) throws IOException {
//		_statementList(lex, ns, 0, 0, false, w);
//		lex.eqchar(';');
		_statement(lex, ns, 0, 0, false, true, w);
	}

	/**
	 * 
	 * @param src
	 * @param ins
	 * @param ous
	 * @param ns
	 * @param level
	 * @throws IOException
	 */
	public void run(Reader src, BcNamespace ns,
			int level) throws IOException {
		BcLexer lex = new BcLexer(src, level);
		PipedWriter p;
		PrintWriter w;
		PipedReader r;

		do {
			p = new PipedWriter();
			w = new PrintWriter(p);
			r = new PipedReader(p);
			for(; lex.isEof(); lex.restart());
			_statement(lex, ns, 0, 0, false, true, w);
			if(!lex.isEof())  lex.eateos();
			w.close();
			dcparser.parse(r);
			if(!dcmodel.isEmpty())  output.println(dcmodel.pop());
		} while(!lex.isEof());
	}

}
