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

import java.awt.Color;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;

import blanco.pdf.BlancoPdfWriter;
import blanco.pdf.font.BlancoPdfFontUtil;
import blanco.pdf.font.valueobject.BlancoPdfFontDef;
import blanco.pdf.form.valueobject.BlancoPdfFormLineDef;
import blanco.pdf.form.valueobject.BlancoPdfFormTextDef;
import blanco.pdf.page.valueobject.BlancoPdfBlockDef;
import blanco.pdf.page.valueobject.BlancoPdfItemDef;
import blanco.pdf.page.valueobject.BlancoPdfPageDef;
import blanco.pdf.valueobject.BlancoPdfMetrix;

import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfWriter;

/**
 * PDF ページを書き込むためのクラス。
 * 
 * @author Tosiki IGA
 */
public class BlancoPdfPageWriter {
    protected Map<String, BaseFont> fFontMap = new HashMap<String, BaseFont>();

    protected BlancoPdfPageDef fPageDef = null;
    protected BlancoPdfWriter fWriter = null;

    protected boolean fIsStrictLocation = false;

    public BlancoPdfPageWriter(final String confPath,
            final BlancoPdfPageDef pageDef, final OutputStream outStream) {
        fPageDef = pageDef;

        // TODO ライターの作成は、少し後のタイミングが好適?
        // TODO そもそもファイル名は外から与えたい。
        fWriter = new BlancoPdfWriter(pageDef, outStream);
        fWriter.getPdfSetting().setConfPath(confPath);

        drawForm();
    }

    public void setStrictLocation(final boolean isStrict) {
        fIsStrictLocation = isStrict;
    }

    public PdfWriter getWriter() {
        return fWriter.getWriter();
    }

    public void close() {
        try {
            fWriter.close();
        } catch (IOException e) {
            e.printStackTrace();
            throw new IllegalArgumentException("クローズ時に例外が発生", e);
        }
    }

    /**
     * 帳票設計用のグリッドを描画。
     * 
     * @param writer
     */
    public void drawGridSheet() {
        final BlancoPdfMetrix metrix = new BlancoPdfMetrix();

        metrix.setLineWidth(0.1f);

        fWriter.drawLine(0, 0, fPageDef.getColumn() * fPageDef.getPitchX(), 0,
                metrix);
        fWriter.drawLine(0, 0, 0, fPageDef.getRow() * fPageDef.getPitchY(),
                metrix);

        for (int x = 0; x <= fPageDef.getColumn(); x++) {
            metrix.setLineWidth(0.1f);
            int length = 3;
            if (x % 5 == 0) {
                length = 5;
            }
            if (x % 10 == 0) {
                length = 2;
                metrix.setLineWidth(1f);
            }
            fWriter.drawLine(x * fPageDef.getPitchX(), length, x
                    * fPageDef.getPitchX(), 0, metrix);
        }

        for (int y = 0; y <= fPageDef.getRow(); y++) {
            metrix.setLineWidth(0.1f);
            int length = 3;
            if (y % 3 == 0) {
                length = 5;
            }
            if (y % 6 == 0) {
                length = 2;
                metrix.setLineWidth(1f);
            }
            fWriter.drawLine(length, y * fPageDef.getPitchY(), 0, y
                    * fPageDef.getPitchY(), metrix);
        }

        final BaseFont bf = getFont("gothic");

        // 文字。横方向。
        for (int x = 1; x <= fPageDef.getColumn() / 10; x++) {
            fWriter.drawText(PdfContentByte.ALIGN_LEFT, x
                    * fPageDef.getPitchX() * 10 - 2, 2 + 8, String.valueOf(x),
                    bf, 8, metrix);
        }

        // 文字。縦方向。
        for (int y = 1; y <= fPageDef.getRow() / 6; y++) {
            fWriter.drawText(PdfContentByte.ALIGN_LEFT, 3, y
                    * fPageDef.getPitchY() * 6 + 3, String.valueOf(y), bf, 8,
                    metrix);
        }

        // 点
        for (int x = 1; x <= fPageDef.getColumn(); x++) {
            for (int y = 1; y <= fPageDef.getRow(); y++) {
                metrix.setColorFill(Color.BLACK);
                fWriter.drawCircle(x * fPageDef.getPitchX(), y
                        * fPageDef.getPitchY(), 0.3f, metrix);
            }
        }
    }

    /**
     * 帳票設計用のグリッドを描画。
     * 
     * @param writer
     */
    public void drawGridSheetItem() {
        final BlancoPdfMetrix metrix = new BlancoPdfMetrix();

        // 項目を参考として印字。

        // ブロックを処理。
        for (BlancoPdfBlockDef blockDef : fPageDef.getBlockList()) {
            for (int occur = 0; occur < blockDef.getMaxOccurs(); occur++) {
                for (BlancoPdfItemDef itemDef : blockDef.getItemList()) {
                    drawGridSheetItemInternal(itemDef, metrix, blockDef.getX()
                            + blockDef.getPitchX() * occur, blockDef.getY()
                            + blockDef.getPitchY() * occur);
                }
            }
        }

        // アイテムを処理。
        for (BlancoPdfItemDef itemDef : fPageDef.getItemList()) {
            drawGridSheetItemInternal(itemDef, metrix, 0, 0);
        }
    }

    void drawGridSheetItemInternal(final BlancoPdfItemDef itemDef,
            final BlancoPdfMetrix metrix, float stepX, float stepY) {
        final StringBuilder builder = new StringBuilder();
        for (int index = 0; index < itemDef.getLength(); index++) {
            if (itemDef.getDoubleByte() == false) {
                builder.append("M");
            } else {
                builder.append("龍");
            }
        }

        metrix.setFont(itemDef.getFont());
        metrix.setFontSize(itemDef.getFontSize());
        metrix.setColorText(Color.BLACK);

        drawText(itemDef.getX() + stepX, itemDef.getY() + stepY, builder
                .toString(), metrix);

        // 項目名称を参考として印字。
        metrix.setFont("gothic");
        metrix.setFontSize(4);
        metrix.setColorText(Color.MAGENTA);
        drawText(itemDef.getX() + 4 + stepX, itemDef.getY() - 2 + stepY,
                itemDef.getName(), metrix);
    }

    /**
     * フォームの印字。
     */
    public void drawForm() {
        final BlancoPdfMetrix metrix = new BlancoPdfMetrix();

        metrix.setColorStroke(Color.BLUE);
        metrix.setColorText(Color.BLUE);

        // フォーム腺の印字。
        for (BlancoPdfFormLineDef line : fPageDef.getForm().getLineList()) {
            fWriter.drawLine(line.getStartX(), line.getStartY(),
                    line.getEndX(), line.getEndY(), metrix);
        }

        // フォーム文字の印字。
        for (BlancoPdfFormTextDef text : fPageDef.getForm().getTextList()) {
            metrix.setFont(text.getFont());
            metrix.setFontSize(text.getFontSize());
            drawText(text.getX(), text.getY(), text.getValue(), metrix);
        }
    }

    /**
     * フォントを取得します。
     * 
     * @param fontName
     * @return
     */
    public BaseFont getFont(final String fontName) {
        final BaseFont check = fFontMap.get(fontName);
        if (check == null) {
            final BaseFont bf = BlancoPdfFontUtil.getFont(fWriter
                    .getPdfSetting(), fontName);
            fFontMap.put(fontName, bf);
        }
        return fFontMap.get(fontName);
    }

    /**
     * 指定のグリッド箇所にテキストを出力します。
     * 
     * @param column
     * @param row
     * @param value
     * @param fontName
     */
    public void drawTextGrid(final int column, final int row,
            final String value, final BlancoPdfMetrix metrix) {
        final BlancoPdfFontDef font = BlancoPdfFontUtil.read(fWriter
                .getPdfSetting(), metrix.getFont());
        drawText(column * fPageDef.getPitchX(), row * fPageDef.getPitchY()
                - font.getDescent(), value, metrix);
    }

    /**
     * テキストを描画します。
     * 
     * @param x
     * @param y
     * @param value
     * @param fontSize
     */
    public void drawText(final float x, final float y, final String value,
            final BlancoPdfMetrix metrix) {
        if (value == null) {
            // null だと処理しようがないのであきらめます。
            return;
        }

        if (fIsStrictLocation) {
            // 座標厳格モード。

            // 外字対応の際に、この記述と類似した記述が必要になる可能性がある。

            float delta = 0;
            for (int index = 0; index < value.length(); index++) {
                final String look = String.valueOf(value.charAt(index));

                boolean isDoubleWidth = false;
                try {
                    if (look.getBytes("Windows-31J").length > 1) {
                        isDoubleWidth = true;
                    }
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }

                final BaseFont bf = getFont(metrix.getFont());
                final BlancoPdfFontDef font = BlancoPdfFontUtil.read(fWriter
                        .getPdfSetting(), metrix.getFont());

                float pitchX = metrix.getFontSize();
                if (font.getPitch() > 0) {
                    // フォントにピッチ情報があればそれを採用。
                    pitchX = font.getPitch();
                }

                fWriter.drawText(PdfContentByte.ALIGN_LEFT, x + delta, y
                        + metrix.getFontSize() - font.getDescent(), look, bf,
                        metrix.getFontSize(), metrix);
                delta += pitchX / 2;
                if (isDoubleWidth) {
                    delta += pitchX / 2;
                }
            }
        } else {
            // 座標妥協モード。
            final BaseFont bf = getFont(metrix.getFont());
            final BlancoPdfFontDef font = BlancoPdfFontUtil.read(fWriter
                    .getPdfSetting(), metrix.getFont());

            fWriter.drawText(PdfContentByte.ALIGN_LEFT, x, y
                    + metrix.getFontSize() - font.getDescent(), value, bf,
                    metrix.getFontSize(), metrix);
        }
    }

    /**
     * テキストをセンタリングで描画します。
     * 
     * @param x
     * @param y
     * @param value
     * @param fontSize
     */
    public void drawTextCenter(final float x, final float y,
            final String value, final BlancoPdfMetrix metrix) {
        if (value == null) {
            // null だと処理しようがないのであきらめます。
            return;
        }

        final BaseFont bf = getFont(metrix.getFont());
        final BlancoPdfFontDef font = BlancoPdfFontUtil.read(fWriter
                .getPdfSetting(), metrix.getFont());

        fWriter.drawText(PdfContentByte.ALIGN_CENTER, x, y
                + metrix.getFontSize() - font.getDescent(), value, bf, metrix
                .getFontSize(), metrix);
    }

    /**
     * 次ページに進みます。
     */
    public void nextPage() {
        fWriter.newPage();
        // TODO ページの設定を反映したり、template PDF を適用したりする処理が未実装。

        drawForm();
    }
}
