/*
 * Copyright 2006 Takahiro Nakamura.
 *
 * 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 woolpack.samples.gcrud;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Arrays;
import java.util.regex.Pattern;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Element;
import org.w3c.dom.Node;

import woolpack.action.ActionDefMaker;
import woolpack.action.ActionInvoker;
import woolpack.action.ForwardDef;
import woolpack.adapter.JXP;
import woolpack.adapter.OGE;
import woolpack.adapter.OGNLUtils;
import woolpack.config.ConfigUtils;
import woolpack.container.ContainerContext;
import woolpack.container.ContainerUtils;
import woolpack.convert.ConvertUtils;
import woolpack.ee.ActionBuilder;
import woolpack.ee.EEContext;
import woolpack.ee.HttpSessionMap;
import woolpack.ee.ServletContextMap;
import woolpack.ee.ServletInputStreamFactory;
import woolpack.ee.ServletRequestAttributeMap;
import woolpack.el.ELUtils;
import woolpack.el.GettingEL;
import woolpack.el.PathEL;
import woolpack.factory.FactoryUtils;
import woolpack.fn.Delegator;
import woolpack.fn.Fn;
import woolpack.fn.FnUtils;
import woolpack.html.AutoUpdater;
import woolpack.html.HtmlUtils;
import woolpack.id.IdConverter;
import woolpack.id.IdUtils;
import woolpack.sql.meta.TableInfoUtils;
import woolpack.sql.tx.TxBuilder;
import woolpack.sql.tx.TxFn;
import woolpack.utils.BuildableArrayList;
import woolpack.utils.BuildableHashMap;
import woolpack.utils.Utils;
import woolpack.web.ContainerContextSetter;
import woolpack.web.WebContext;
import woolpack.xml.NodeContext;
import woolpack.xml.NodeSetter;
import woolpack.xml.XmlTransformerContext;
import woolpack.xml.XmlTransformerUtils;
import woolpack.xml.XmlUtils;

/**
 * 汎用 CRUD のウェブアプリケーションです。
 * @author nakamura
 *
 */
public class GcrudServlet extends HttpServlet {

	public final transient Delegator<String, Node> toNode;
	public final transient Fn<EEContext, Void> fn;
	private final transient GcrudManager2 manager;
	private final transient TxFn<EEContext, Void> txFn;
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	public GcrudServlet() {
		super();
		OGNLUtils.setting();
		
		final ActionBuilder actionBuilder = new ActionBuilder(new ActionInvoker(
			FnUtils.switching(new ActionDefMaker()
				.put(
						"listTable",
						ELUtils.NULL,
						ELUtils.NULL,
						new OGE("container.gcrud")
				)
				.put(
						"insertInput",
						ELUtils.NULL,
						ELUtils.NULL,
						new OGE("container.gcrud.getColumns(input.tableName[0])")
				)
				.put(
						"insertResult",
						ELUtils.NULL,
						new OGE("container.gcrud.insert(input)"),
						new OGE("input.table[0]")
				)
				.put(
						"selectInput",
						ELUtils.NULL,
						ELUtils.NULL,
						new OGE("container.gcrud.getColumns(input.tableName[0])")
				)
				.put(
						"selectResult",
						ELUtils.NULL,
						new OGE("request.result = container.gcrud.selectList(input)"),
						new OGE("request.result")
				)
				.put(
						"selectDetail",
						ELUtils.NULL,
						new OGE("request.result = container.gcrud.selectOne(input)"),
						new OGE("request.result")
				)
				.put(
						"updateResult",
						ELUtils.NULL,
						new OGE("container.gcrud.update(input)"),
						new OGE("input.table[0]")
				)
				.put(
						"deleteResult",
						ELUtils.NULL,
						new OGE("container.gcrud.delete(input)"),
						new OGE("input.table[0]")
				)
				.get()),
				new ForwardDef("error", new PathEL("local"), FnUtils.fix(true))),
				new Fn<GettingEL, Fn<? super EEContext, Void>>() {
					public Fn<? super EEContext, Void> exec(final GettingEL c) {
						return new AutoUpdater(Arrays.asList("name", "id"), c, ConfigUtils.CONFIG_EL) {
							@Override
							public Void exec(final NodeContext c) {
								if (c.getNode().getNodeType() == Node.ELEMENT_NODE) {
									final Element e = (Element) c.getNode();
									if ("INPUT".equals(e.getNodeName())) {
										final Object o = getComponentEL().getValue(c);
										if (o instanceof SingleColumnInfo) {
											final SingleColumnInfo columnInfo2 = (SingleColumnInfo) o;
											e.setAttribute("name", columnInfo2.getColumnName());
											final Object value = columnInfo2.getValue();
											e.setAttribute("value", value == null ? "": value.toString());
											return null;
										}
									}
								}
								return super.exec(c);
							}
						};
					}
			});

		toNode = new Delegator<String, Node>();
		manager = new GcrudManager2();
		txFn = new TxFn<EEContext, Void>(null, actionBuilder.getActionExpression());
		
		fn = FnUtils.seq(new BuildableArrayList<Fn<? super EEContext, Void>>()
				.list(new IdConverter<Void>(ConvertUtils.convertRegExp(Pattern.compile("^.*/([^/]+)$"), "$1")))
				.list(new ContainerContextSetter(
					FnUtils.exec(FnUtils.join(
						ContainerUtils.GET_KEY,
						FnUtils.switching(new BuildableHashMap<Object, Fn<? super ContainerContext<WebContext>, ?>>()
							.map("gcrud", FnUtils.fix(manager))
						)))
				))
				.list(txFn)
				// DOMのキャッシュを行う場合は???にDOM生成を移動します。
				//.list(IdNodeUtils.cache(new HashMap<String, Object>(), ???))
				.list(FnUtils.seq(new BuildableArrayList<Fn<? super EEContext, Void>>()
						.list(new NodeSetter<EEContext>(FnUtils.join(IdUtils.GET_ID, toNode)))
						.list(HtmlUtils.NORMALIZE_CASE)
						.list(XmlUtils.findNode(new JXP("//*[@id=\"dummy\" or @name=\"dummy\"]"), XmlUtils.REMOVE_THIS))
						.list(HtmlUtils.removeExtension("A", "href"))
						.list(HtmlUtils.removeExtension("FORM", "action"))
				))
				.list(XmlUtils.CLONE_NODE)
				.list(actionBuilder.getAutoUpdateExpression())
		);
	}

	@Override
	public void init(final ServletConfig servletConfig) throws ServletException {
		super.init(servletConfig);
		toNode.setFn(FnUtils.join(
				ConvertUtils.convertRegExp(Pattern.compile("^(.*)$"), "/html/sample/gcrud/$1.html"),
				XmlUtils.nodeFactory(
						FactoryUtils.inputStreamReaderFactory(new ServletInputStreamFactory(
						servletConfig.getServletContext()), "UTF-8"),
						XmlTransformerUtils.TRANSFORMER)));
	}
	
	/**
	 * DB接続オブジェクトを使用する設定を行います。
	 * サブクラスの{@link HttpServlet#init()}または{@link HttpServlet#init(ServletConfig)}から呼び出されることを想定しています。
	 * @param dataSource
	 */
	public void init(final DataSource dataSource) {
		final GcrudManager gcrudManager = new GcrudManager();
		gcrudManager.setTableInfoList(TableInfoUtils.getTableInfoList(dataSource));
		
		final TxBuilder txBuilder = new TxBuilder(dataSource);
		txFn.setDataSource(txBuilder.getTxDataSource());
		
		gcrudManager.setDataSource(txBuilder.getTmpDataSource());
		gcrudManager.init();
		manager.setManager(gcrudManager);
		manager.init();
	}

	@Override protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
		final EEContext context = new EEContext();
		context.setId(request.getRequestURI());
		context.setInput(request.getParameterMap());
		context.setRequest(new ServletRequestAttributeMap(request));
		context.setSession(Utils.concurrentMap(new HttpSessionMap(request.getSession()), request.getSession()));
		context.setApplication(Utils.concurrentMap(new ServletContextMap(request.getSession().getServletContext()), request.getSession().getServletContext()));
		fn.exec(context);
		final Writer w = new BufferedWriter(new OutputStreamWriter(response.getOutputStream(), "UTF-8"));
		try {
			final XmlTransformerContext tc = new XmlTransformerContext();
			tc.setSource(new DOMSource(context.getNode()));
			tc.setResult(new StreamResult(w));
			XmlTransformerUtils.TRANSFORMER.exec(tc);
		} finally {
			w.close();
		}
	}
}
