/*******************************************************************************
 * Copyright (c) 2009 IGA Tosiki, NTT DATA BUSINESS BRAINS Corp.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    IGA Tosiki (NTT DATA BUSINESS BRAINS Corp.) - initial API and implementation
 *******************************************************************************/
/*
 * blanco Framework
 * Copyright (C) 2009 NTT DATA BUSINESS BRAINS CORPORATION
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 */

package blanco.pdf;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;

import blanco.pdf.page.valueobject.BlancoPdfPageDef;
import blanco.pdf.valueobject.BlancoPdfMetrix;
import blanco.pdf.valueobject.BlancoPdfSetting;

import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.PageSize;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfImportedPage;
import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.PdfWriter;

/**
 * PDF ファイルに対する最低限の出力を担うライター。
 * 
 * @author Tosiki IGA
 */
public class BlancoPdfWriter {
    protected OutputStream fOutputStream = null;
    protected Document fPdfDocument = null;
    protected PdfWriter fWriter = null;

    protected Rectangle fPageSize = null;

    protected BlancoPdfSetting fPdfSetting = new BlancoPdfSetting();

    protected BlancoPdfPageDef fPageDef = null;

    protected PdfReader fTemplatePdfCache = null;

    /**
     * ライターを作成します。
     * 
     * @param outStream
     */
    public BlancoPdfWriter(final BlancoPdfPageDef pageDef,
            final OutputStream outStream) {
        fPageDef = pageDef;
        fOutputStream = outStream;
    }

    public PdfWriter getWriter() {
        return fWriter;
    }

    /**
     * PDF設定を取得します。
     * 
     * @return
     */
    public BlancoPdfSetting getPdfSetting() {
        return fPdfSetting;
    }

    public BlancoPdfPageDef getPageDef() {
        return fPageDef;
    }

    /**
     * ドキュメントを開きます。
     */
    protected void openDocument() {
        final PdfReader template = loadTemplatePdf();

        if (fPageSize == null) {
            // TODO 現在、有無を言わさず A4 にしています。
            fPageSize = PageSize.A4;
            if (fPageDef.getRotate()) {
                fPageSize = fPageSize.rotate();
            }
        }

        fPdfDocument = new Document(fPageSize);
        try {
            fWriter = PdfWriter.getInstance(fPdfDocument, fOutputStream);
        } catch (DocumentException e) {
            e.printStackTrace();
            throw new IllegalArgumentException("PDF ドキュメント作成に失敗", e);
        }

        fPdfDocument.open();

        fWriter.setPdfVersion(PdfWriter.VERSION_1_4);

        applyTemplate(template, fWriter);
    }

    /**
     * ドキュメントが開いていることを念押しします。
     */
    protected void ensureOpen() {
        if (fPdfDocument == null) {
            openDocument();
        }
    }

    /**
     * PDF ライターを閉じます。
     * 
     * @throws IOException
     */
    public void close() throws IOException {
        if (fPdfDocument != null) {
            fPdfDocument.close();
            fPdfDocument = null;
        }

        if (fWriter != null) {
            fWriter.close();
            fWriter = null;
        }

        if (fOutputStream != null) {
            fOutputStream.flush();
            fOutputStream.close();
            fOutputStream = null;
        }
    }

    /**
     * テンプレート PDF をロードします。
     * 
     * @return
     */
    protected PdfReader loadTemplatePdf() {
        if (fPageDef.getTemplate() == null) {
            return null;
        }

        if (fTemplatePdfCache != null) {
            return fTemplatePdfCache;
        }

        try {
            final File existCheck = new File(fPdfSetting.getConfPath()
                    + "/template/" + fPageDef.getTemplate() + ".pdf");
            if (existCheck.exists() == false) {
                throw new IllegalArgumentException("指定されたファイル '"
                        + existCheck.getAbsolutePath() + "' が見つかりません。");
            }

            fTemplatePdfCache = new PdfReader(existCheck.getAbsolutePath());
            if (fPageSize == null) {
                // ページ情報を読み込み。
                fPageSize = fTemplatePdfCache.getPageSize(1);
            }
            return fTemplatePdfCache;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 現在ページにテンプレートを適用します。
     * 
     * @param template
     * @param writer
     */
    protected void applyTemplate(final PdfReader template,
            final PdfWriter writer) {
        if (template == null) {
            return;
        }

        final PdfImportedPage importedPage = writer
                .getImportedPage(template, 1);

        final PdfContentByte cb = writer.getDirectContent();
        cb.saveState();
        cb.addTemplate(importedPage, 0, 0);
        cb.restoreState();
    }

    /**
     * テキストの描画。
     * 
     * 注意: 起点は左下となります。これは PDF がもともと左下が起点であるためです。
     * 
     * @param x
     * @param y
     * @param value
     * @throws Exception
     */
    public void drawText(int mode, final float x, final float y,
            final String value, final BaseFont bf, final float fontSize,
            final BlancoPdfMetrix metrix) {
        ensureOpen();

        final PdfContentByte cb = fWriter.getDirectContent();
        cb.saveState();

        if (metrix.getColorText() != null) {
            cb.setColorFill(metrix.getColorText());
        }

        cb.beginText();

        cb.setFontAndSize(bf, fontSize);
        // 座標系を変換。左上が 0,0
        // cb.moveText(getX(x), getY(y));
        cb.showTextAlignedKerned(mode, value, getX(x), getY(y), 0);

        cb.endText();

        cb.restoreState();
    }

    /**
     * 線の描画。
     * 
     * @param startX
     * @param startY
     * @param endX
     * @param endY
     */
    public void drawLine(final float startX, final float startY,
            final float endX, final float endY, final BlancoPdfMetrix metrix) {
        ensureOpen();

        final PdfContentByte cb = fWriter.getDirectContent();
        cb.saveState();

        if (metrix.getColorStroke() != null) {
            cb.setColorStroke(metrix.getColorStroke());
        }
        if (metrix.getLineDash() != null) {
            cb.setLineDash(metrix.getLineDash(), 0);
        }
        if (metrix.getLineWidth() > 0) {
            cb.setLineWidth(metrix.getLineWidth());
        }

        cb.moveTo(getX(startX), getY(startY));
        cb.lineTo(getX(endX), getY(endY));
        cb.stroke();

        cb.restoreState();
    }

    public void drawCircle(final float x, final float y, final float r,
            final BlancoPdfMetrix metrix) {
        ensureOpen();

        final PdfContentByte cb = fWriter.getDirectContent();
        cb.saveState();

        cb.circle(getX(x), getY(y), r);

        if (metrix.getColorFill() != null) {
            cb.fill();
        }

        cb.restoreState();
    }

    /**
     * 改ページをおこないます。
     */
    public void newPage() {
        ensureOpen();

        fPdfDocument.newPage();

        applyTemplate(loadTemplatePdf(), fWriter);
    }

    /**
     * マージンを考慮して x 座標を戻します。
     * 
     * @param originalX
     * @return
     */
    public float getX(final float originalX) {
        return originalX + fPageDef.getMarginX();
    }

    /**
     * マージンおよび座標系変換を考慮して y 座標を戻します。
     * 
     * @param originalY
     * @return
     */
    public float getY(final float originalY) {
        return fPageSize.getHeight() - originalY - fPageDef.getMarginY();
    }
}
