/*
 * SmPrinterDevice.java
 *
 * Created on July 31 2007, 16:46
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */
/**
 *
 * @author V.Kravtsov
 */
package com.shtrih.fiscalprinter;

import java.io.*;
import java.util.Vector;
import org.apache.log4j.*;
import java.security.InvalidParameterException;

import com.shtrih.ej.*;
import com.shtrih.jpos.*;
import com.shtrih.util.*;
import com.shtrih.barcode.*;
import com.shtrih.fiscalprinter.*;
import com.shtrih.fiscalprinter.port.*;
import com.shtrih.fiscalprinter.model.*;
import com.shtrih.fiscalprinter.table.*;
import com.shtrih.fiscalprinter.command.*;
import com.shtrih.jpos.fiscalprinter.*;
import com.shtrih.printer.ncr7167.*;

public class SMFiscalPrinterImpl implements SMFiscalPrinter, PrinterConst {

    public SMPrinterDevice device;
    // delay on wait
    public static final int TimeToSleep = 100;
    public static String charsetName = "Cp1251";   // device charset name
    // tax officer password
    public int taxPassword = 0;
    // operator password
    public int usrPassword = 1;
    // system administrator password
    public int sysPassword = 30;
    public boolean wrapText = true;
    private PrinterModel model = null;
    private PrinterModels models = new PrinterModels();
    /** Cashed field info **/
    public ReadTableInfo tableInfo = new ReadTableInfo(sysPassword, 0);
    public ReadFieldInfo fieldInfo = new ReadFieldInfo();
    public static Logger logger = Logger.getLogger(SMFiscalPrinterImpl.class);
    public IPrinterEvents events = null;
    private final FiscalPrinterParams params;
    private final PrinterImages printerImages = new PrinterImages();

    public SMFiscalPrinterImpl(
            SMPrinterDevice device, 
            FiscalPrinterParams params) {
        this.device = device;
        this.params = params;
        fieldInfo.setPassword(sysPassword);
        models.load("models.xml");
    }

    public FiscalPrinterParams getParams() {
        return params;
    }

    public PrinterImages getPrinterImages() {
        return printerImages;
    }

    public PrinterPort getPrinterPort() {
        return device.getPrinterPort();
    }

    public void setPrinterPort(PrinterPort printerPort) {
        device.setPrinterPort(printerPort);
    }

    public void setDevice(SMPrinterDevice device) {
        this.device = device;
    }

    public SMPrinterDevice getDevice() {
        return device;
    }

    public static String getCharsetName() {
        return charsetName;
    }

    public void setEvents(IPrinterEvents events) {
        this.events = events;
    }

    public byte[] sendCommand(byte[] data, int timeout)
            throws Exception {
        return device.sendCommand(data, timeout);
    }

    public synchronized void deviceExecute(PrinterCommand command)
            throws Exception {
        device.execute(command);
        if (events != null) {
            events.commandExecuted(command);
        }
    }

    public synchronized void connect()
            throws Exception {
        device.connect();
    }

    public void check(int errorCode)
            throws Exception {
        if (errorCode != 0) {
            String text = PrinterError.getFullText(errorCode);
            throw new SmFiscalPrinterException(errorCode, text);
        }
    }

    public synchronized void execute(PrinterCommand command)
            throws Exception {
        check(executeCommand(command));
    }

    public void setByteTimeout(int value) {
        device.setByteTimeout(value);
    }

    public int getSysPassword() {
        return sysPassword;
    }

    public int getUsrPassword() {
        return usrPassword;
    }

    public int getTaxPassword() {
        return taxPassword;
    }

    public boolean failed(int errorCode) {
        return errorCode != 0;
    }

    public boolean succeeded(int errorCode) {
        return errorCode == 0;
    }

    public synchronized int executeCommand(PrinterCommand command)
            throws Exception {

        while (true) {
            deviceExecute(command);
            int resultCode = command.getResultCode();


            switch (resultCode) {
                case PrinterError.E_PRINTER_PREVCOMMAND: {
                    waitForPrinting();
                    break;
                }

                case PrinterError.E_PRINTER_WAITPRINT: {
                    continuePrint();
                    waitForPrinting();
                    break;
                }

                default: {
                    return resultCode;
                }
            }
        }
    }

    public synchronized void setTaxPassword(int taxPassword) {
        this.taxPassword = taxPassword;
    }

    public synchronized void setUsrPassword(int usrPassword) {
        this.usrPassword = usrPassword;
    }

    public synchronized void setSysPassword(int sysPassword) {
        this.sysPassword = sysPassword;
    }

    public synchronized Beep beep()
            throws Exception {
        logger.debug("beep");
        Beep command = new Beep();
        command.setPassword(usrPassword);
        execute(command);
        return command;
    }

    public synchronized int activateEJ()
            throws Exception {
        logger.debug("activateEJ");
        ActivateEJCommand command = new ActivateEJCommand();
        command.setPassword(sysPassword);
        return executeCommand(command);
    }

    public synchronized int closeEJArchive()
            throws Exception {
        logger.debug("closeEJArchive");
        CloseEJArhive command = new CloseEJArhive();
        command.setPassword(sysPassword);
        return executeCommand(command);
    }

    public synchronized int initEJArchive()
            throws Exception {
        logger.debug("initEJArchive");
        InitEJArchive command = new InitEJArchive();
        command.setPassword(sysPassword);
        return executeCommand(command);
    }

    public synchronized int testEJArchive()
            throws Exception {
        logger.debug("testEJArchive");
        TestEJArchive command = new TestEJArchive();
        command.setPassword(sysPassword);
        return executeCommand(command);
    }

    public synchronized int stopEJPrint()
            throws Exception {
        logger.debug("stopEJPrint");
        StopEJPrint command = new StopEJPrint(sysPassword);
        return executeCommand(command);
    }

    public int readEJDayReport(int dayNumber)
            throws Exception {
        logger.debug("readEJDayReport");
        ReadEJDayReport command = new ReadEJDayReport(sysPassword, dayNumber);
        return executeCommand(command);
    }

    public int readEJDayTotals(int dayNumber)
            throws Exception {
        logger.debug("readEJDayTotals");
        ReadEJDayTotals command = new ReadEJDayTotals(sysPassword, dayNumber);
        return executeCommand(command);
    }

    public synchronized int printEJDayReport(int dayNumber)
            throws Exception {
        logger.debug("printEJDayReport");
        PrintEJDayReport command = new PrintEJDayReport(sysPassword, dayNumber);
        return executeCommand(command);
    }

    public synchronized int printEJDayTotal(int dayNumber)
            throws Exception {
        logger.debug("printEJDayTotal");
        PrintEJDayTotal command = new PrintEJDayTotal(sysPassword, dayNumber);
        return executeCommand(command);
    }

    public int printEJDocument(int macNumber)
            throws Exception {
        logger.debug("PrintEJDocument");
        PrintEJDocument command = new PrintEJDocument(sysPassword, macNumber);
        return executeCommand(command);
    }

    public synchronized int printEJActivationReport()
            throws Exception {
        logger.debug("printEJActivationReport");
        PrintEJActivationReport command = new PrintEJActivationReport(sysPassword);
        return executeCommand(command);
    }

    public int cancelEJDocument() throws Exception {
        logger.debug("cancelEJDocument");
        CancelEJDocument command = new CancelEJDocument();
        command.setPassword(sysPassword);
        return executeCommand(command);
    }

    public int writeEJErrorCode(int errorCode) throws Exception {
        logger.debug("writeEJErrorCode");
        WriteEJErrorCode command = new WriteEJErrorCode(sysPassword, errorCode);
        return executeCommand(command);
    }

    public synchronized LongPrinterStatus readLongStatus()
            throws Exception {
        logger.debug("readLongStatus");
        ReadLongStatus command = new ReadLongStatus(usrPassword);
        execute(command);
        return command.getStatus();
    }

    public synchronized ShortPrinterStatus readShortStatus()
            throws Exception {
        logger.debug("readShortStatus");
        ReadShortStatus command = new ReadShortStatus(usrPassword);
        execute(command);
        return command.getStatus();
    }

    public synchronized int printString(int station, String line)
            throws Exception {
        logger.debug("printString("
                + String.valueOf(station) + ", '" + line + "')");

        if (line == null) {
            throw new InvalidParameterException("printString, line = null");
        }

        PrintString command =
                new PrintString(usrPassword, station, line);
        execute(command);
        return command.getOperator();
    }

    public synchronized int printBoldString(int station, String line)
            throws Exception {
        logger.debug("printBoldString("
                + String.valueOf(station) + ", '" + line + "')");

        PrintBoldString command = new PrintBoldString();
        command.setPassword(usrPassword);
        command.setStation(station);
        command.setText(line);
        execute(command);
        return command.getOperator();
    }

    public synchronized void feedPaper(int station, int lineNumber)
            throws Exception {
        logger.debug("feedPaper");
        FeedPaper command = new FeedPaper();
        command.setPassword(usrPassword);
        command.setStations(station);
        command.setLineNumber(lineNumber);
        execute(command);
    }

    public synchronized int printStringFont(
            int station, int fontNumber, String line)
            throws Exception {
        logger.debug("printStringFont("
                + String.valueOf(station) + ", '"
                + String.valueOf(fontNumber) + ", '"
                + line + "')");

        PrintStringFont command = new PrintStringFont(
                usrPassword, station,
                new PrinterFont(fontNumber),
                line);

        execute(command);
        return command.getOperator();
    }

    // line is truncated to maximum print width
    public synchronized int printLine(
            int station, String line, int fontNumber)
            throws Exception {
        logger.debug("printLine("
                + String.valueOf(station) + ", "
                + "'" + line + "', "
                + String.valueOf(fontNumber) + ")");

        if (line.length() == 0) {
            line = " ";
        }
        int len = Math.min(line.length(), getModel().getTextLength(fontNumber));
        line = line.substring(0, len);
        if (getModel().getCapPrintStringFont()) {
            return printStringFont(station, fontNumber, line);
        } else {
            if (fontNumber == PrinterConst.FONT_NUMBER_BOLD_BIG) {
                return printBoldString(station, line);
            } else {
                return printString(station, line);
            }
        }
    }

    public String[] splitText(String text, int n, boolean wrap) {
        String line = "";
        Vector lines = new Vector();
        if (text.length() == 0) {
            lines.add("");
        } else {
            for (int i = 0; i < text.length(); i++) {
                switch (text.charAt(i)) {
                    case '\r':
                        break;

                    case '\n':
                        lines.add(line);
                        line = "";
                        break;

                    default:
                        line = line + text.charAt(i);
                        if (wrap) {
                            if (line.length() == n) {
                                lines.add(line);
                                line = "";
                            }
                        }
                }
            }
            if (line != "") {
                lines.add(line);
            }
        }
        return (String[]) lines.toArray(new String[0]);
    }

    public String[] splitText(String text, int fontNumber)
            throws Exception {
        int len = getModel().getTextLength(fontNumber);
        return splitText(text, len, wrapText);
    }

    public int updateFieldInfo(int tableNumber, int fieldNumber)
            throws Exception {
        int result = 0;
        if (!fieldInfo.isEqual(tableNumber, fieldNumber)) {
            ReadFieldInfo readFieldInfo = new ReadFieldInfo();
            readFieldInfo.setPassword(sysPassword);
            readFieldInfo.setTableNumber(tableNumber);
            readFieldInfo.setFieldNumber(fieldNumber);
            result = executeCommand(readFieldInfo);
            if (succeeded(result)) {
                fieldInfo = readFieldInfo;
            }
        }
        return result;
    }

    public synchronized int writeTable(int tableNumber, int rowNumber,
            int fieldNumber, String fieldValue)
            throws Exception {
        int result = 0;
        logger.debug("writeTable("
                + String.valueOf(tableNumber) + ", "
                + String.valueOf(rowNumber) + ", "
                + String.valueOf(fieldNumber) + ", "
                + "'" + fieldValue + "')");

        // update field info
        result = updateFieldInfo(tableNumber, fieldNumber);
        if (failed(result)) {
            return result;
        }

        byte[] fieldData = fieldInfo.fieldToBytes(fieldValue, charsetName);
        PrinterCommand command2 = new WriteTable(
                sysPassword, tableNumber, rowNumber, fieldNumber, fieldData);
        return executeCommand(command2);
    }

    public synchronized int readTable(int tableNumber, int rowNumber,
            int fieldNumber, String[] fieldValue)
            throws Exception {
        int result = 0;
        logger.debug("readTable("
                + String.valueOf(tableNumber) + ", "
                + String.valueOf(rowNumber) + ", "
                + String.valueOf(fieldNumber) + ")");

        // update field info
        result = updateFieldInfo(tableNumber, fieldNumber);
        if (failed(result)) {
            return result;
        }

        ReadTable commandReadTable = new ReadTable(
                sysPassword, tableNumber, rowNumber, fieldNumber);

        // read field value
        result = executeCommand(commandReadTable);
        if (failed(result)) {
            return result;
        }

        fieldValue[0] = fieldInfo.bytesToField(
                commandReadTable.fieldValue, charsetName);
        return result;
    }

    public synchronized int readTableInfo(int tableNumber, Object[] out)
            throws Exception {
        logger.debug("readTableInfo");
        ReadTableInfo command = new ReadTableInfo(
                sysPassword, tableNumber);

        out[0] = command;
        return executeCommand(command);
    }

    public synchronized ReadTableInfo readTableInfo(int tableNumber)
            throws Exception {
        Object[] out = new Object[1];
        check(readTableInfo(tableNumber, out));
        return (ReadTableInfo) out[0];
    }

    public synchronized PrintCashIn printCashIn(long sum)
            throws Exception {
        logger.debug("printCashIn");
        PrintCashIn command = new PrintCashIn();
        command.setPassword(usrPassword);
        command.setSum(sum);
        execute(command);
        return command;
    }

    public synchronized PrintCashOut printCashOut(long sum)
            throws Exception {
        logger.debug("printCashOut");
        PrintCashOut command = new PrintCashOut(usrPassword, sum);
        execute(command);
        return command;
    }

    public synchronized ContinuePrint continuePrint()
            throws Exception {
        logger.debug("continuePrint");
        ContinuePrint command = new ContinuePrint();
        command.setPassword(usrPassword);
        execute(command);
        return command;
    }

    public synchronized BeginTest startTest(int periodInMinutes)
            throws Exception {
        logger.debug("startTest");
        BeginTest command = new BeginTest();
        command.setPassword(usrPassword);
        command.setPeriodInMinutes(periodInMinutes);
        execute(command);
        return command;
    }

    public synchronized EndTest stopTest()
            throws Exception {
        logger.debug("stopTest");
        EndTest command = new EndTest();
        command.setPassword(usrPassword);
        execute(command);
        return command;
    }

    public synchronized VoidFiscalReceipt cancelReceipt()
            throws Exception {
        logger.debug("cancelReceipt");
        VoidFiscalReceipt command = new VoidFiscalReceipt(usrPassword);
        execute(command);
        return command;
    }

    public synchronized VoidFiscalReceipt cancelReceipt(int password)
            throws Exception {
        logger.debug("cancelReceipt");
        VoidFiscalReceipt command = new VoidFiscalReceipt(password);
        execute(command);
        return command;
    }

    public synchronized EndFiscalReceipt closeReceipt(long sum1, long sum2, long sum3, long sum4,
            int tax1, int tax2, int tax3, int tax4, int discount, String text)
            throws Exception {
        logger.debug("closeReceipt");
        CloseRecParams params = new CloseRecParams(sum1, sum2, sum3, sum4,
                tax1, tax2, tax3, tax4, discount, text);
        EndFiscalReceipt command = new EndFiscalReceipt();
        command.setPassword(usrPassword);
        command.setParams(params);
        execute(command);
        return command;
    }

    public synchronized long getSubtotal()
            throws Exception {
        logger.debug("getSubtotal");
        ReadSubtotal command = new ReadSubtotal(usrPassword);
        execute(command);
        return command.getSum();
    }

    public int readOperationRegister(OperationRegister register)
            throws Exception {
        logger.debug("readOperationRegister");
        ReadOperationRegister command = new ReadOperationRegister(
                usrPassword, register.getNumber());
        int result = executeCommand(command);
        if (result == 0) {
            register.setValue(command.getValue());
        }
        return result;
    }

    public int readOperationRegister(int number)
            throws Exception {
        logger.debug("readOperationRegister");
        ReadOperationRegister command = new ReadOperationRegister(
                usrPassword, number);
        execute(command);
        return command.getValue();
    }

    public synchronized int readCashRegister(CashRegister register)
            throws Exception {
        logger.debug("readCashRegister");
        ReadCashRegister command = new ReadCashRegister(
                usrPassword, register.getNumber());
        int result = executeCommand(command);
        if (result == 0) {
            register.setValue(command.getValue());
        }
        return result;
    }

    public synchronized long readCashRegister(int number)
            throws Exception {
        logger.debug("readCashRegister");
        ReadCashRegister command = new ReadCashRegister(
                usrPassword, number);
        execute(command);
        return command.getValue();
    }

    public synchronized PrintEJDayReportOnDates printEJDayReportOnDates(
            EJDate date1,
            EJDate date2,
            int reportType)
            throws Exception {
        logger.debug("printEJDayReportOnDates");
        PrintEJDayReportOnDates command = new PrintEJDayReportOnDates(
                sysPassword, reportType, date1, date2);
        execute(command);
        return command;
    }

    public synchronized PrintFMReportDates printFMReportDates(
            PrinterDate date1,
            PrinterDate date2,
            int reportType)
            throws Exception {
        logger.debug("printFMReportDates");
        PrintFMReportDates command = new PrintFMReportDates(
                taxPassword, reportType, date1, date2);
        execute(command);
        return command;
    }

    public synchronized PrintEJDayReportOnDays printEJReportDays(
            int day1,
            int day2,
            int reportType)
            throws Exception {
        logger.debug("printEJReportDays");
        PrintEJDayReportOnDays command = new PrintEJDayReportOnDays(
                sysPassword, reportType, day1, day2);
        execute(command);
        return command;
    }

    public synchronized PrintFMReportDays printFMReportDays(
            int day1,
            int day2,
            int reportType)
            throws Exception {
        logger.debug("printFMReportDays");
        PrintFMReportDays command = new PrintFMReportDays(
                taxPassword, reportType, day1, day2);
        execute(command);
        return command;
    }

    public synchronized void printSale(long price, long quantity,
            int department, int tax1, int tax2, int tax3, int tax4, String text)
            throws Exception {
        logger.debug("printSale");

        PriceItem item = new PriceItem(price, quantity, department,
                tax1, tax2, tax3, tax4, text);
        PrintSale command = new PrintSale(usrPassword, item);
        execute(command);
    }

    public synchronized void printVoidSale(long price, long quantity,
            int department, int tax1, int tax2, int tax3, int tax4, String text)
            throws Exception {
        logger.debug("printVoidSale");

        PrintVoidSale command = new PrintVoidSale(usrPassword,
                price, quantity, department, tax1, tax2, tax3, tax4, text);
        execute(command);
    }

    public synchronized void printRefund(long price, long quantity,
            int department, int tax1, int tax2, int tax3, int tax4, String text)
            throws Exception {
        logger.debug("printRefund");

        PriceItem item = new PriceItem(price, quantity, department,
                tax1, tax2, tax3, tax4, text);
        PrintRefund command = new PrintRefund(usrPassword, item);
        execute(command);
    }

    public synchronized void printVoidRefund(long price, long quantity,
            int department, int tax1, int tax2, int tax3, int tax4, String text)
            throws Exception {
        logger.debug("printVoidRefund");

        PriceItem item = new PriceItem(price, quantity, department,
                tax1, tax2, tax3, tax4, text);
        PrintVoidRefund command = new PrintVoidRefund(usrPassword, item);
        execute(command);
    }

    public synchronized PrintVoidItem printVoidItem(long price, long quantity, int department,
            int tax1, int tax2, int tax3, int tax4, String text)
            throws Exception {
        logger.debug("printVoidItem");

        PrintVoidItem command = new PrintVoidItem(
                usrPassword, price, quantity, department, tax1, tax2,
                tax3, tax4, text);
        execute(command);
        return command;
    }

    public synchronized PrintDiscount printDiscount(
            long amount, int tax1, int tax2, int tax3, int tax4, String text)
            throws Exception {
        logger.debug("printDiscount");

        AmountItem item = new AmountItem(amount, tax1, tax2, tax3, tax4, text);
        PrintDiscount command = new PrintDiscount();
        command.setPassword(usrPassword);
        command.setItem(item);
        execute(command);
        return command;
    }

    public synchronized PrintVoidDiscount printVoidDiscount(
            long amount, int tax1, int tax2, int tax3, int tax4, String text)
            throws Exception {
        logger.debug("printVoidDiscount");

        AmountItem item = new AmountItem(amount, tax1, tax2, tax3, tax4, text);
        PrintVoidDiscount command = new PrintVoidDiscount(usrPassword, item);
        execute(command);
        return command;
    }

    public synchronized PrintCharge printCharge(
            long amount, int tax1, int tax2, int tax3, int tax4,
            String text)
            throws Exception {
        logger.debug("printCharge");

        PrintCharge command = new PrintCharge();
        command.setPassword(usrPassword);
        command.setAmount(amount);
        command.setTax1(tax1);
        command.setTax2(tax2);
        command.setTax3(tax3);
        command.setTax4(tax4);
        command.setText(text);
        execute(command);
        return command;
    }

    public synchronized PrintVoidCharge printVoidCharge(
            long amount, int tax1, int tax2, int tax3, int tax4,
            String text)
            throws Exception {
        logger.debug("printVoidCharge");

        AmountItem item = new AmountItem(amount, tax1, tax2, tax3, tax4, text);
        PrintVoidCharge command = new PrintVoidCharge(usrPassword, item);
        execute(command);
        return command;
    }

    public synchronized ReadFMLastRecordDate readFMLastRecordDate()
            throws Exception {
        logger.debug("getLastFmRecordDate");

        ReadFMLastRecordDate command = new ReadFMLastRecordDate();
        command.setPassword(sysPassword);
        execute(command);
        return command;
    }

    public synchronized PrintXReport printXReport()
            throws Exception {
        logger.debug("printXReport");

        PrintXReport command = new PrintXReport(sysPassword);
        execute(command);
        return command;
    }

    public synchronized PrintZReport printZReport()
            throws Exception {
        logger.debug("printZReport");

        PrintZReport command = new PrintZReport(sysPassword);
        execute(command);
        return command;
    }

    public synchronized int printDepartmentReport()
            throws Exception {
        logger.debug("printDepartmentReport");
        PrintDepartmentReport command = new PrintDepartmentReport();
        command.setPassword(sysPassword);
        return executeCommand(command);
    }

    public int printTaxReport()
            throws Exception {
        logger.debug("printTaxReport");
        PrintTaxReport command = new PrintTaxReport(sysPassword);
        return executeCommand(command);
    }

    public int printTotalizers()
            throws Exception {
        logger.debug("printTotalizers");
        PrintTotalizers command = new PrintTotalizers(sysPassword);
        return executeCommand(command);
    }

    public int bufferZReport()
            throws Exception {
        logger.debug("bufferZReport");
        BufferZReport command = new BufferZReport();
        command.setPassword(sysPassword);
        return executeCommand(command);
    }

    public int printBufferedZReport()
            throws Exception {
        logger.debug("printBufferedZReport");
        PrintBufferedZReport command = new PrintBufferedZReport();
        command.setPassword(sysPassword);
        return executeCommand(command);
    }

    public int printHeader()
            throws Exception {
        logger.debug("printHeader");
        PrintHeader command = new PrintHeader(sysPassword);
        return executeCommand(command);
    }

    public int printTrailer()
            throws Exception {
        logger.debug("printTrailer");
        PrintTrailer command = new PrintTrailer(sysPassword);
        return executeCommand(command);
    }

    public synchronized int writeDate(PrinterDate date)
            throws Exception {
        logger.debug("writeDate");

        SetDateCommand command = new SetDateCommand(sysPassword, date);
        return executeCommand(command);
    }

    public synchronized int confirmDate(PrinterDate date)
            throws Exception {
        logger.debug("confirmDate");

        ConfirmDate command = new ConfirmDate();
        command.setPassword(sysPassword);
        command.setDate(date);
        return executeCommand(command);
    }

    public synchronized void writeTime(PrinterTime time)
            throws Exception {
        logger.debug("setTime");

        WriteTime command =
                new WriteTime(sysPassword, time);
        execute(command);
    }

    public synchronized void writePortParams(int portNumber, int baudRate, int timeout)
            throws Exception {
        logger.debug("writePortParams");
        MethodParameter.checkByte(portNumber, "portNumber");
        MethodParameter.checkByte(baudRate, "baudRate");
        WritePortParams command = new WritePortParams(
                sysPassword, portNumber, baudRate, timeout);
        execute(command);
    }

    public synchronized void printBarcode(String barcode)
            throws Exception {
        logger.debug("printBarcode");

        PrintBarcode command = new PrintBarcode();
        command.setPassword(usrPassword);
        command.setBarcode(barcode);
        execute(command);
    }

    public synchronized void duplicateReceipt()
            throws Exception {
        logger.debug("duplicateReceipt");

        PrintReceiptCopy command =
                new PrintReceiptCopy(usrPassword);
        execute(command);
    }

    public synchronized BeginFiscalReceipt beginFiscalReceipt(int receiptType)
            throws Exception {
        logger.debug("openReceipt");

        BeginFiscalReceipt command = new BeginFiscalReceipt();
        command.setPassword(usrPassword);
        command.setReceiptType(receiptType);
        execute(command);
        return command;
    }

    public synchronized int loadGraphics(int lineNumber, byte[] data)
            throws Exception {
        logger.debug("loadGraphics");
        MethodParameter.checkRange(lineNumber, 0, 255, "lineNumber");


        LoadGraphics command = new LoadGraphics();
        command.setPassword(usrPassword);
        command.setLineNumber(lineNumber);
        command.setData(data);
        return executeCommand(command);
    }

    public synchronized int loadGraphicsEx(int lineNumber, byte[] data)
            throws Exception {
        logger.debug("loadExtendedGraphics");
        LoadExtendedGraphics command = new LoadExtendedGraphics();
        command.setPassword(usrPassword);
        command.setLineNumber(lineNumber);
        command.setData(data);
        return executeCommand(command);
    }

    public synchronized int printGraphics(int line1, int line2)
            throws Exception {
        logger.debug("printGraphics("
                + String.valueOf(line1) + ", "
                + String.valueOf(line2) + ")");

        PrintGraphics command = new PrintGraphics(
                usrPassword, line1, line2);
        return executeCommand(command);
    }

    public synchronized int printGraphicsEx(int line1, int line2)
            throws Exception {
        logger.debug("printGraphicsEx("
                + String.valueOf(line1) + ", "
                + String.valueOf(line2) + ")");

        PrintGraphicsEx command = new PrintGraphicsEx(
                usrPassword, line1, line2);
        return executeCommand(command);
    }

    public synchronized void endDump()
            throws Exception {
        logger.debug("endDump");
        EndDump command = new EndDump();
        command.setPassword(usrPassword);
        execute(command);
    }

    public synchronized void printGraphicLine(int height, byte[] data)
            throws Exception {
        logger.debug("printGraphicLine");

        PrintGraphicLine command =
                new PrintGraphicLine(usrPassword, height, data);
        execute(command);
    }

    // CutPaper
    public synchronized int cutPaper(int cutType)
            throws Exception {
        logger.debug("cutReceipt");

        CutPaper command = new CutPaper();
        command.setPassword(usrPassword);
        command.setCutType(cutType);
        return executeCommand(command);
    }

    // CutPaper
    public synchronized void openCashDrawer(int drawerNumber)
            throws Exception {
        logger.debug("openDrawer");

        OpenCashDrawer command = new OpenCashDrawer();
        command.setPassword(usrPassword);
        command.setDrawerNumber(drawerNumber);
        execute(command);
    }

    public synchronized boolean checkEcrMode(int mode)
            throws Exception, InterruptedException {
        logger.debug("checkEcrMode");
        switch (mode) {
            case MODE_FULLREPORT:
            case MODE_EJREPORT:
            case MODE_SLPPRINT: {
                Thread.sleep(TimeToSleep);
                break;
            }
            default:
                return true;
        }
        return false;
    }

    public synchronized PrinterStatus waitForPrinting()
            throws Exception {
        logger.debug("waitForPrinting");
        PrinterStatus status = null;
        try {
            for (;;) {
                status = readPrinterStatus();
                switch (status.getSubmode()) {
                    case ECR_SUBMODE_IDLE:
                        if (checkEcrMode(status.getMode())) {
                            return status;
                        }
                        break;

                    case ECR_SUBMODE_PASSIVE:
                    case ECR_SUBMODE_ACTIVE: {
                        checkPaper(status);
                        break;
                    }

                    case ECR_SUBMODE_AFTER: {
                        continuePrint();
                        break;
                    }

                    case ECR_SUBMODE_REPORT:
                    case ECR_SUBMODE_PRINT: {
                        Thread.sleep(TimeToSleep);
                        break;
                    }

                    default: {
                        Thread.sleep(TimeToSleep);
                        break;
                    }
                }
            }
        } catch (InterruptedException e) {
            // Restore the interrupted status
            logger.error("InterruptedException", e);
            Thread.currentThread().interrupt();
        }
        return status;
    }

    public int[] getSupportedBaudRates()
            throws Exception {
        return getModel().getSupportedBaudRates();
    }

    public synchronized boolean tryCancelReceipt(int password)
            throws Exception {
        VoidFiscalReceipt command = new VoidFiscalReceipt(password);
        if (executeCommand(command) == 0x59) {
            return false;
        } else {
            check(command.getResultCode());
            return true;
        }
    }

    public void writeDecimalPoint(int position)
            throws Exception {

        WriteDecimalPoint command = new WriteDecimalPoint(sysPassword, position);
        execute(command);
    }

    public void resetFM()
            throws Exception {
        ResetFM command = new ResetFM();
        execute(command);
    }

    public synchronized void sysAdminCancelReceipt()
            throws Exception {
        logger.debug("sysAdminCancelReceipt");
        String[] passwordString = new String[0];
        // try use known passwords
        if (tryCancelReceipt(usrPassword)) {
            return;
        }
        if (tryCancelReceipt(sysPassword)) {
            return;
        }
        // reading passwords from tables
        for (int i = 1; i < 30; i++) {
            check(readTable(2, i, 1, passwordString));
            int password = Integer.parseInt(passwordString[0]);
            if (tryCancelReceipt(password)) {
                return;
            }
        }
        throw new Exception(Localizer.getString(Localizer.FailedCancelReceipt));
    }

    public synchronized int getBaudRateIndex(int value)
            throws Exception {
        int[] deviceBaudRates = getSupportedBaudRates();
        for (byte i = 0; i < deviceBaudRates.length; i++) {
            if (value == deviceBaudRates[i]) {
                return i;
            }
        }
        return deviceBaudRates.length - 1;
    }

    public void setBaudRate(int baudRate)
            throws Exception {
        device.getPrinterPort().setBaudRate(baudRate);
    }

    public boolean connectDevice(
            int baudRate,
            int deviceBaudRate,
            int deviceByteTimeout)
            throws Exception {
        setBaudRate(baudRate);
        readLongStatus();
        writePortParams(0, getBaudRateIndex(deviceBaudRate),
                deviceByteTimeout);
        setBaudRate(baudRate);
        return true;
    }

    public void checkBaudRate(int value)
            throws Exception {
        int[] deviceBaudRates = getSupportedBaudRates();
        for (int i = 0; i < deviceBaudRates.length; i++) {
            if (value == deviceBaudRates[i]) {
                return;
            }
        }
        throw new Exception(
                Localizer.getString(Localizer.BaudrateNotSupported));
    }

    public void closePort()
            throws Exception {
        device.getPrinterPort().close();
    }

    public void writeTables(PrinterTables tables)
            throws Exception {
        for (int i = 0; i < tables.size(); i++) {
            PrinterTable table = tables.get(i);
            PrinterFields fields = table.getFields();
            for (int j = 0; j < fields.size(); j++) {
                PrinterField field = fields.get(j);
                check(writeTable(field.getTable(), field.getRow(),
                        field.getNumber(), field.getValue()));
            }
        }
    }

    public void writeFields(PrinterFields fields)
            throws Exception {
        String[] fieldValue = new String[1];
        for (int i = 0; i < fields.size(); i++) {
            fieldValue[0] = "";
            PrinterField field = fields.get(i);
            check(readTable(field.getTable(), field.getRow(),
                    field.getNumber(), fieldValue));

            if (fieldValue[0].compareTo(field.getValue()) != 0) {
                check(writeTable(field.getTable(), field.getRow(),
                        field.getNumber(), field.getValue()));
            }
        }
    }

    public void updateTableInfo(int tableNumber)
            throws Exception {
        if (tableNumber != tableInfo.getTableNumber()) {
            tableInfo = readTableInfo(tableNumber);
        }
    }

    public boolean isValidField(int tableNumber, int rowNumber, int fieldNumber)
            throws Exception {
        updateTableInfo(tableNumber);
        return (rowNumber >= 1) && (rowNumber <= tableInfo.getRowCount())
                && (fieldNumber >= 1) && (fieldNumber <= tableInfo.getFieldCount());
    }

    public void readTable(PrinterTable table)
            throws Exception {
        for (int rowNumber = 1; rowNumber <= table.getRowCount(); rowNumber++) {
            for (int fieldNumber = 1; fieldNumber <= table.getFieldCount(); fieldNumber++) {
                String[] fieldValue = new String[1];
                check(readTable(table.getNumber(), rowNumber, fieldNumber, fieldValue));
                PrinterField field = new PrinterField(
                        table.getNumber(),
                        rowNumber,
                        fieldNumber,
                        fieldInfo.getFieldSize(),
                        fieldInfo.getFieldType(),
                        fieldInfo.getMinValue(),
                        fieldInfo.getMaxValue(),
                        fieldInfo.getFieldName());
                field.setValue(fieldValue[0]);
                table.getFields().add(field);
            }
        }
    }

    public void readField(PrinterField field)
            throws Exception {
        String[] fieldValue = new String[1];
        check(readTable(field.getTable(),
                field.getRow(),
                field.getNumber(),
                fieldValue));
        field.setValue(fieldValue[0]);
    }

    public void writeField(PrinterField field)
            throws Exception {
        check(writeTable(field.getTable(),
                field.getRow(),
                field.getNumber(),
                field.getValue()));
    }

    public void readTables(PrinterTables tables)
            throws Exception {
        tables.clear();
        int tableNumber = 1;
        while (true) {
            ReadTableInfo command = new ReadTableInfo(
                    sysPassword, tableNumber);

            int result = executeCommand(command);
            if (result == SMFP_EFPTR_INVALID_TABLE) {
                break;
            }
            check(result);

            tableInfo = command;

            PrinterTable table = new PrinterTable(
                    tableInfo.getTableNumber(),
                    tableInfo.getTableName(),
                    tableInfo.getRowCount(),
                    tableInfo.getFieldCount());
            tables.add(table);

            for (int fieldNumber = 1; fieldNumber <= table.getFieldCount(); fieldNumber++) {
                for (int rowNumber = 1; rowNumber <= table.getRowCount(); rowNumber++) {
                    String[] fieldValue = new String[1];
                    check(readTable(tableNumber, rowNumber, fieldNumber, fieldValue));
                    PrinterField field = new PrinterField(
                            tableNumber,
                            rowNumber,
                            fieldNumber,
                            fieldInfo.getFieldSize(),
                            fieldInfo.getFieldType(),
                            fieldInfo.getMinValue(),
                            fieldInfo.getMaxValue(),
                            fieldInfo.getFieldName());
                    field.setValue(fieldValue[0]);
                    table.getFields().add(field);
                }
            }
            tableNumber++;
        }
    }

    public synchronized PrinterStatus readShortPrinterStatus()
            throws Exception {
        ShortPrinterStatus status = readShortStatus();
        int mode = status.getMode();
        int submode = status.getSubmode();
        int flags = status.getFlags();
        int operatorNumber = status.getOperatorNumber();

        loggerDebugMode(mode, submode);
        return new PrinterStatus(mode, submode, flags, operatorNumber);
    }

    public synchronized PrinterStatus readLongPrinterStatus()
            throws Exception {
        LongPrinterStatus status = readLongStatus();
        int mode = status.getMode();
        int submode = status.getSubmode();
        int flags = status.getFlags();
        int operatorNumber = status.getOperatorNumber();

        loggerDebugMode(mode, submode);
        return new PrinterStatus(mode, submode, flags, operatorNumber);
    }

    private void loggerDebugMode(int mode, int subMode) {
        logger.debug("Mode: 0x" + Hex.toHex(mode) + ", "
                + PrinterMode.getText(mode) + ". Flags: 0x"
                + Hex.toHex(subMode));

        logger.debug("Submode: " + String.valueOf(subMode) + ", "
                + PrinterSubmode.getText(subMode));
    }

    public synchronized PrinterStatus readPrinterStatus()
            throws Exception {
        PrinterStatus status;
        switch (params.statusCommand) {
            case SMFP_STATUS_COMMAND_DS: {
                if (getModel().getCapShortStatus()) {
                    status = readShortPrinterStatus();
                } else {
                    status = readLongPrinterStatus();
                }
            }
            break;

            case SMFP_STATUS_COMMAND_10H:
                status = readShortPrinterStatus();
                break;

            default:
                status = readLongPrinterStatus();
                break;
        }
        if (events != null) {
            events.printerStatusRead(status);
        }
        return status;
    }

    public synchronized DeviceMetrics readDeviceMetrics()
            throws Exception {
        ReadDeviceMetrics command = new ReadDeviceMetrics();
        execute(command);
        return command.getDeviceMetrics();
    }

    public PrinterModel getModel()
            throws Exception {
        if (model == null) {
            model = selectPrinterModel(readDeviceMetrics());
            logger.debug("Selected model: " + getModel().getName());
        }
        return model;
    }

    public PrinterModel selectPrinterModel(DeviceMetrics metrics)
            throws Exception {
        PrinterModel result = models.find(metrics.getModel(),
                metrics.getProtocolVersion(), metrics.getProtocolSubVersion());
        if (result == null) {
            result = models.itemByID(SMFP_MODELID_DEFAULT);
        }
        if (result == null) {
            throw new Exception("Printer model not found");
        }
        return result;
    }

    public boolean getWrapText() {
        return wrapText;
    }

    public void setWrapText(boolean value) {
        wrapText = value;
    }

    public synchronized void checkPaper(PrinterStatus status)
            throws Exception {
        int resultCode;
        if (getModel().getCapRecPresent()
                && status.getPrinterFlags().isRecEmpty()) {
            resultCode = SMFP_EFPTR_NO_REC_PAPER;
            throw new SmFiscalPrinterException(resultCode,
                    PrinterError.getFullText(resultCode));
        }
        if (getModel().getCapJrnPresent()
                && status.getPrinterFlags().isJrnEmpty()) {
            resultCode = SMFP_EFPTR_NO_JRN_PAPER;
            throw new SmFiscalPrinterException(resultCode,
                    PrinterError.getFullText(resultCode));
        }
    }

    public byte[] getTxData() {
        return device.getTxData();
    }

    public byte[] getRxData() {
        return device.getRxData();
    }

    public int initTables()
            throws Exception {
        InitTables command = new InitTables();
        command.setPassword(sysPassword);
        return executeCommand(command);
    }

    public void loadImageData(int lineNumber, byte[] data)
            throws Exception {
        if (getModel().getCapGraphicsEx()) {
            loadGraphicsEx(lineNumber, data);
        } else {
            if (getModel().getCapGraphics()) {
                loadGraphics(lineNumber, data);
            }
        }
    }

    public void printImage(int line1, int line2)
            throws Exception {
        if (getModel().getCapGraphicsEx()) {
            printGraphicsEx(line1, line2);
        } else {
            printGraphics(line1, line2);
        }
    }

    public static int boolToInt(boolean value) {
        if (value) {
            return 1;
        } else {
            return 0;
        }
    }

    public void writeParameter(int parameterID, boolean value)
            throws Exception {
        writeParameter(parameterID, boolToInt(value));
    }

    public void writeParameter(int parameterID, String value)
            throws Exception {
        logger.debug("writeParameter("
                + String.valueOf(parameterID) + ", "
                + String.valueOf(value) + ")");

        PrinterParameter parameter = getModel().getParameter(parameterID);
        if (parameter == null) {
            logger.error("Parameter not found");
            return;
        }

        check(writeTable(
                parameter.getTableNumber(),
                parameter.getRowNumber(),
                parameter.getFieldNumber(),
                value));
    }

    public void writeParameter(int parameterID, int value)
            throws Exception {
        logger.debug("writeParameter("
                + String.valueOf(parameterID) + ", "
                + String.valueOf(value) + ")");

        PrinterParameter parameter = getModel().getParameter(parameterID);
        if (parameter == null) {
            logger.error("Parameter not found");
            return;
        }
        int fieldValue = parameter.getFieldValue(value);
        check(writeTable(
                parameter.getTableNumber(),
                parameter.getRowNumber(),
                parameter.getFieldNumber(),
                String.valueOf(fieldValue)));
    }

    public String readParameter(int parameterID)
            throws Exception {

        logger.debug("readParameter("
                + String.valueOf(parameterID) + ")");

        PrinterParameter parameter = getModel().getParameter(parameterID);
        if (parameter == null) {
            throw new Exception("Parameter not found");
        }

        String[] fieldValue = new String[1];
        check(readTable(
                parameter.getTableNumber(),
                parameter.getRowNumber(),
                parameter.getFieldNumber(),
                fieldValue));
        return fieldValue[0];
    }

    public void printBarcode(PrinterBarcode barcode)
            throws Exception 
    {
        logger.debug("printBarcode");
        if (barcode.getHeight() <= 0) {
            throw new Exception(
                    Localizer.getString(Localizer.InvalidBarcodeHeight));
        }

        switch (barcode.getPrintType()) {
            case SmFptrConst.SMFPTR_PRINTTYPE_DEVICE:
                printBarcodeDevice(barcode);
                break;

            case SmFptrConst.SMFPTR_PRINTTYPE_DRIVER:
                printBarcodeDriver(barcode);
                break;

            default:
                throw new Exception(
                        Localizer.getString(Localizer.InvalidBarcodePrintType));
        }
    }

    private void printBarcodeDevice(PrinterBarcode barcode)
            throws Exception {
        if (barcode.getType() != PrinterBarcode.SM_BARCODE_EAN13) {
            throw new Exception(Localizer.getString(
                    Localizer.PrinterSupportesEAN13Only));
        }

        if (barcode.isTextAbove()) {
            printBarcodeLabel(barcode);
        }
        printBarcode(barcode.getData());
    }

    private void printBarcodeLabel(PrinterBarcode barcode)
            throws Exception 
    {
        logger.debug("printBarcodeLabel");
        String data = centerLine(barcode.getLabel());
        doPrintText(SMFP_STATION_REC, data, params.fontNumber);
    }

    public void printText(int station, String text, int fontNumber)
            throws Exception {
        String data = processEscCommands(text);
        if (data.length() > 0) {
            doPrintText(station, data, fontNumber);
        }
    }

    public synchronized void doPrintText(int station, String text,
            int fontNumber)
            throws Exception {
        String[] lines = splitText(text, fontNumber);
        for (int i = 0; i < lines.length; i++) {
            printLine(station, lines[i], fontNumber);
        }
    }

    
    public String processEscCommands(String text)
            throws Exception {

        String data = text;
        if (params.escCommandsEnabled) {
            NCR7167Printer escPrinter = new NCR7167Printer(this);
            data = escPrinter.parse(
                    text.getBytes(params.stringEncoding), params.stringEncoding);
            escPrinter.execute();
        }
        return data;
    }

    public String centerLine(String data)
            throws Exception {
        return StringUtils.centerLine(data, getMessageLength());
    }

    private void printBarcodeDriver(PrinterBarcode barcode)
            throws Exception 
    {
        logger.debug("printBarcodeDriver");
        byte[] data = null;
        SmBarcodeEncoder encoder = BarcodeEncoderFactory.getBarcodeEncoder();

        if (barcode.isTextAbove()) {
            printBarcodeLabel(barcode);
        }
        if (barcode.isLinear() && getModel().getCapPrintGraphicsLine()) {
            data = encoder.encode(barcode, getModel().getPrintWidth());
            printGraphicLine(barcode.getHeight(), data);
            sleep(params.barcodePrintTime);
            printText(SMFP_STATION_REC, " ", params.fontNumber);
            waitForPrinting();
        } else {
            data = encoder.encode(barcode, getMaxGraphicsWidth());
            printBarcodeGraphics(data, barcode);
        }
        if (barcode.isTextBelow()) {
            printBarcodeLabel(barcode);
        }
    }

    public int getMessageLength()
            throws Exception {
        return getModel().getTextLength(params.fontNumber);
    }

    public void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            // Restore the interrupted status
            logger.error("InterruptedException", e);
            Thread.currentThread().interrupt();
        }
    }

    public int getMaxGraphicsWidth()
            throws Exception {
        return getModel().getMaxGraphicsWidth();
    }

    // use area after images to print barcode
    private void printBarcodeGraphics(byte[] data, PrinterBarcode barcode)
            throws Exception {
        int firstLine = printerImages.getTotalSize();
        int height = firstLine + barcode.getHeight();
        if (height > getMaxGraphicsHeight()) {
            throw new Exception(
                    Localizer.getString(Localizer.InvalidImageHeight) + ", "
                    + String.valueOf(height) + " > "
                    + String.valueOf(getMaxGraphicsHeight()));
        }

        for (int i = 1; i <= barcode.getHeight(); i++) {
            loadGraphics(firstLine + i, barcode.getHeight(), data);
        }
        printGraphics(firstLine + 1, firstLine + barcode.getHeight());
    }

    public int getMaxGraphicsHeight()
            throws Exception {
        return getModel().getMaxGraphicsHeight();
    }

    public void loadGraphics(int lineNumber, int lineCount, byte[] data)
            throws Exception {
        if ((lineNumber + lineCount) > getMaxGraphicsHeight()) {
            throw new Exception(
                    Localizer.getString(Localizer.InvalidImageHeight) + ", "
                    + String.valueOf(lineCount) + " > "
                    + String.valueOf(getMaxGraphicsHeight()));
        }

        if (getModel().getCapGraphicsEx()) {
            loadGraphicsEx(lineNumber, data);
        } else {
            if (getModel().getCapGraphics()) {
                loadGraphics(lineNumber, data);
            }
        }
    }
}