import {Readable} from 'stream';
import DxfArrayScanner, {IGroup} from './DxfArrayScanner';
import AUTO_CAD_COLOR_INDEX from './AutoCadColorIndex';
import dimStyleCodes from './DimStyleCodes';

import Face from './entities/3dface';
import Arc from './entities/arc';
import AttDef from './entities/attdef';
import Attribute from './entities/attribute'
import Circle from './entities/circle';
import Dimension from './entities/dimension';
import Ellipse from './entities/ellipse';
import Hatch from './entities/hatch';
import Insert from './entities/insert';
import Line from './entities/line';
import LWPolyline from './entities/lwpolyline';
import MText from './entities/mtext';
import Point from './entities/point';
import Polyline from './entities/polyline';
import Solid from './entities/solid';
import Spline from './entities/spline';
import Text from './entities/text';
import Vertex from './entities/vertex';

import log from 'loglevel';
import IGeometry, {EntityName, IEntity, IPoint} from './entities/geomtry';

//log.setLevel('trace');
//log.setLevel('debug');
//log.setLevel('info');
//log.setLevel('warn');
log.setLevel('error');

//log.setLevel('silent');

export interface IBlock {
    entities: IEntity[];
    type: number;
    ownerHandle: string;
    // entities: any[];
    xrefPath: string;
    name: string;
    name2: string;
    handle: string;
    layer: string;
    position: IPoint;
    paperSpace: boolean;
}

export interface IViewPort {
    name: string;
    lowerLeftCorner: IPoint;
    upperRightCorner: IPoint;
    center: IPoint;
    snapBasePoint: IPoint;
    snapSpacing: IPoint;
    gridSpacing: IPoint;
    viewDirectionFromTarget: IPoint;
    viewTarget: IPoint;
    lensLength: number;
    frontClippingPlane: string | number | boolean;
    backClippingPlane: string | number | boolean;
    viewHeight: number;
    snapRotationAngle: number;
    viewTwistAngle: number;
    orthographicType: string;
    ucsOrigin: IPoint;
    ucsXAxis: IPoint;
    ucsYAxis: IPoint;
    renderMode: string;
    defaultLightingType: string;
    defaultLightingOn: string;
    ownerHandle: string;
    ambientColor: number;
    viewMode: string;
}

export interface IViewPortTableDefinition {
    tableRecordsProperty: 'viewPorts';
    tableName: 'viewPort';
    dxfSymbolName: 'VPORT';

    parseTableRecords(): IViewPort[];
}

export interface ILineType {
    name: string;
    description: string;
    pattern: string[];
    patternLength: number;
}

export interface ILineTypeTableDefinition {
    tableRecordsProperty: 'lineTypes';
    tableName: 'lineType';
    dxfSymbolName: 'LTYPE';

    parseTableRecords(): Record<string, ILineType>;
}

export interface ILayer {
    name: string;
    visible: boolean;
    colorIndex: number;
    color: number;
    frozen: boolean;
}

export interface ILayerTableDefinition {
    tableRecordsProperty: 'layers';
    tableName: 'layer';
    dxfSymbolName: 'LAYER';

    parseTableRecords(): Record<string, ILayer>;
}

export interface ITableDefinitions {
    VPORT: IViewPortTableDefinition;
    LTYPE: ILineTypeTableDefinition;
    LAYER: ILayerTableDefinition;
    DIMSTYLE: any;
    STYLE: any;
}

export interface IBaseTable {
    handle: string;
    ownerHandle: string;
}

export interface IViewPortTable extends IBaseTable {
    viewPorts: IViewPort[];
}

export interface ILayerTypesTable extends IBaseTable {
    lineTypes: Record<string, ILineType>;
}

export interface ILayersTable extends IBaseTable {
    layers: Record<string, ILayer>;
}

export interface ITables {
    viewPort: IViewPortTable;
    lineType: ILayerTypesTable;
    layer: ILayersTable;
}

export type ITable = IViewPortTable | ILayerTypesTable | ILayersTable;

export interface IDxf {
    header: Record<string, IPoint | number>;
    entities: IEntity[];
    blocks: Record<string, IBlock>;
    tables: ITables;
}

function registerDefaultEntityHandlers(dxfParser: DxfParser) {
    // 这里支持的实体(一些实体代码仍然被重构到这个流中)
    dxfParser.registerEntityHandler(Face);
    dxfParser.registerEntityHandler(Arc);
    dxfParser.registerEntityHandler(AttDef);
    dxfParser.registerEntityHandler(Attribute);
    dxfParser.registerEntityHandler(Circle);
    dxfParser.registerEntityHandler(Dimension);
    dxfParser.registerEntityHandler(Ellipse);
    dxfParser.registerEntityHandler(Hatch);
    dxfParser.registerEntityHandler(Insert);
    dxfParser.registerEntityHandler(Line);
    dxfParser.registerEntityHandler(LWPolyline);
    dxfParser.registerEntityHandler(MText);
    dxfParser.registerEntityHandler(Point);
    dxfParser.registerEntityHandler(Polyline);
    dxfParser.registerEntityHandler(Solid);
    dxfParser.registerEntityHandler(Spline);
    dxfParser.registerEntityHandler(Text);
    dxfParser.registerEntityHandler(Vertex);
}

export default class DxfParser {
    private _entityHandlers = {} as Record<EntityName, IGeometry>;

    constructor() {
        registerDefaultEntityHandlers(this);
    }

    public parse(source: string | Readable) {
        if (typeof source === 'string') {
            return this._parse(source);
        } else {
            console.error('无法读取dxf源的类型: `' + typeof (source));
            return null;
        }
    }

    public registerEntityHandler(handlerType: new () => IGeometry) {
        const instance = new handlerType();
        this._entityHandlers[instance.ForEntityName] = instance;
    }

    public parseSync(source: string) {
        return this.parse(source);
    }

    public parseStream(stream: Readable) {
        let dxfString = "";
        const self = this;
        return new Promise<IDxf>((res, rej) => {

            stream.on('data', (chunk) => {
                dxfString += chunk;
            });
            stream.on('end', () => {
                try {
                    res(self._parse(dxfString));
                } catch (err) {
                    rej(err);
                }
            });
            stream.on('error', (err) => {
                rej(err);
            });
        });
    }

    private _parse(dxfString: string) {
        const dxf = {} as IDxf;
        let lastHandle = 0;
        const dxfLinesArray = dxfString.split(/\r\n|\r|\n/g);

        const scanner = new DxfArrayScanner(dxfLinesArray);
        if (!scanner.hasNext()) throw Error('Empty file');

        const self = this;
        let curr: IGroup;

        function parseAll() {
            curr = scanner.next();
            while (!scanner.isEOF()) {
                if (curr.code === 0 && curr.value === 'SECTION') {
                    curr = scanner.next();

                    // 确保我们读的是段代码
                    if (curr.code !== 2) {
                        console.error('Unexpected code %s after 0:SECTION', debugCode(curr));
                        curr = scanner.next();
                        continue;
                    }

                    if (curr.value === 'HEADER') {
                        log.debug('> HEADER');
                        dxf.header = parseHeader();
                        log.debug('<');
                    } else if (curr.value === 'BLOCKS') {
                        log.debug('> BLOCKS');
                        dxf.blocks = parseBlocks();
                        log.debug('<');
                    } else if (curr.value === 'ENTITIES') {
                        log.debug('> ENTITIES');
                        dxf.entities = parseEntities(false);
                        log.debug('<');
                    } else if (curr.value === 'TABLES') {
                        log.debug('> TABLES');
                        dxf.tables = parseTables();
                        log.debug('<');
                    } else if (curr.value === 'EOF') {
                        log.debug('EOF');
                    } else {
                        log.warn('Skipping section \'%s\'', curr.value);
                    }
                } else {
                    curr = scanner.next();
                }
                // If is a new section
            }
        }

        /**
         *
         * @return {object} header
         */
        function parseHeader() {
            // interesting variables:
            //  $ACADVER, $VIEWDIR, $VIEWSIZE, $VIEWCTR, $TDCREATE, $TDUPDATE
            // http://www.autodesk.com/techpubs/autocad/acadr14/dxf/header_section_al_u05_c.htm
            // Also see VPORT table entries
            let currVarName = null as null | string;
            let currVarValue = null as null | IPoint | number;
            const header = {} as Record<string, IPoint | number>;
            // loop through header variables
            curr = scanner.next();

            while (true) {
                if (groupIs(curr, 0, 'ENDSEC')) {
                    if (currVarName) header[currVarName as string] = currVarValue!;
                    break;
                } else if (curr.code === 9) {
                    if (currVarName) header[currVarName as string] = currVarValue!;
                    currVarName = curr.value as string;
                    // Filter here for particular variables we are interested in
                } else {
                    if (curr.code === 10) {
                        currVarValue = {x: curr.value as number} as IPoint;
                    } else if (curr.code === 20) {
                        (currVarValue as IPoint).y = curr.value as number;
                    } else if (curr.code === 30) {
                        (currVarValue as IPoint).z = curr.value as number;
                    } else {
                        currVarValue = curr.value as number;
                    }
                }
                curr = scanner.next();
            }
            // console.log(util.inspect(header, { colors: true, depth: null }));
            curr = scanner.next(); // swallow up ENDSEC
            return header;
        }

        function parseBlocks() {
            const blocks = {} as Record<string, IBlock>;

            curr = scanner.next();

            while (curr.value !== 'EOF') {
                if (groupIs(curr, 0, 'ENDSEC')) {
                    break;
                }

                if (groupIs(curr, 0, 'BLOCK')) {
                    log.debug('block {');
                    const block = parseBlock();
                    log.debug('}');
                    ensureHandle(block);
                    if (!block.name) {
                        // log.error('block with handle "' + block.handle + '" is missing a name.');
                        // block.name = 'InvalidBlockName-' + block.ownerHandle;
                    }else{
                        blocks[block.name] = block;
                    }
                } else {
                    logUnhandledGroup(curr);
                    curr = scanner.next();
                }
            }
            return blocks;
        }

        function parseBlock() {
            const block = {} as IBlock;
            curr = scanner.next();

            while (curr.value !== 'EOF') {
                switch (curr.code) {
                    case 1:
                        block.xrefPath = curr.value as string;
                        curr = scanner.next();
                        break;
                    case 2:
                        block.name = curr.value as string;
                        curr = scanner.next();
                        break;
                    case 3:
                        block.name2 = curr.value as string;
                        curr = scanner.next();
                        break;
                    case 5:
                        block.handle = curr.value as string;
                        curr = scanner.next();
                        break;
                    case 8:
                        block.layer = curr.value as string;
                        curr = scanner.next();
                        break;
                    case 10:
                        block.position = parsePoint(curr);
                        curr = scanner.next();
                        break;
                    case 67:
                        block.paperSpace = (curr.value && curr.value == 1) ? true : false;
                        curr = scanner.next();
                        break;
                    case 70:
                        if (curr.value != 0) {
                            //if(curr.value & BLOCK_ANONYMOUS_FLAG) console.log('  Anonymous block');
                            //if(curr.value & BLOCK_NON_CONSTANT_FLAG) console.log('  Non-constant attributes');
                            //if(curr.value & BLOCK_XREF_FLAG) console.log('  Is xref');
                            //if(curr.value & BLOCK_XREF_OVERLAY_FLAG) console.log('  Is xref overlay');
                            //if(curr.value & BLOCK_EXTERNALLY_DEPENDENT_FLAG) console.log('  Is externally dependent');
                            //if(curr.value & BLOCK_RESOLVED_OR_DEPENDENT_FLAG) console.log('  Is resolved xref or dependent of an xref');
                            //if(curr.value & BLOCK_REFERENCED_XREF) console.log('  This definition is a referenced xref');
                            block.type = curr.value as number;
                        }
                        curr = scanner.next();
                        break;
                    case 100:
                        // ignore class markers
                        curr = scanner.next();
                        break;
                    case 330:
                        block.ownerHandle = curr.value as string;
                        curr = scanner.next();
                        break;
                    case 0:
                        if (curr.value == 'ENDBLK')
                            break;
                        block.entities = parseEntities(true);
                        break;
                    default:
                        logUnhandledGroup(curr);
                        curr = scanner.next();
                }

                if (groupIs(curr, 0, 'ENDBLK')) {
                    curr = scanner.next();
                    break;
                }
            }
            return block;
        }

        /**
         * parseTables
         * @return {Object} Object representing tables
         */
        function parseTables() {
            const tables = {} as ITables;
            curr = scanner.next();
            while (curr.value !== 'EOF') {
                if (groupIs(curr, 0, 'ENDSEC'))
                    break;

                if (groupIs(curr, 0, 'TABLE')) {
                    curr = scanner.next();

                    const tableDefinition = tableDefinitions[curr.value as keyof ITableDefinitions];
                    if (tableDefinition) {
                        log.debug(curr.value + ' Table {');
                        tables[tableDefinitions[curr.value as keyof ITableDefinitions].tableName] = parseTable(curr);
                        log.debug('}');
                    } else {
                        log.debug('Unhandled Table ' + curr.value);
                    }
                } else {
                    // else ignored
                    curr = scanner.next();
                }
            }

            curr = scanner.next();
            return tables;
        }

        const END_OF_TABLE_VALUE = 'ENDTAB';

        function parseTable<T extends IBaseTable = ITable>(group: IGroup) {
            const tableDefinition = tableDefinitions[group.value as keyof ITableDefinitions];
            const table = {} as T;
            let expectedCount = 0;

            curr = scanner.next();
            while (!groupIs(curr, 0, END_OF_TABLE_VALUE)) {
                switch (curr.code) {
                    case 5:
                        table.handle = curr.value as string;
                        curr = scanner.next();
                        break;
                    case 330:
                        table.ownerHandle = curr.value as string;
                        curr = scanner.next();
                        break;
                    case 100:
                        if (curr.value === 'AcDbSymbolTable') {
                            // ignore
                            curr = scanner.next();
                        } else {
                            logUnhandledGroup(curr);
                            curr = scanner.next();
                        }
                        break;
                    case 70:
                        expectedCount = curr.value as number;
                        curr = scanner.next();
                        break;
                    case 0:
                        if (curr.value === tableDefinition.dxfSymbolName) {
                            table[tableDefinition.tableRecordsProperty] = tableDefinition.parseTableRecords();
                        } else {
                            logUnhandledGroup(curr);
                            curr = scanner.next();
                        }
                        break;
                    default:
                        logUnhandledGroup(curr);
                        curr = scanner.next();
                }
            }
            const tableRecords = table[tableDefinition.tableRecordsProperty];
            if (tableRecords) {
                let actualCount = (() => {
                    if (tableRecords.constructor === Array) {
                        return tableRecords.length;
                    } else if (typeof (tableRecords) === 'object') {
                        return Object.keys(tableRecords).length;
                    }
                    return undefined;
                })();
                if (expectedCount !== actualCount) log.warn('Parsed ' + actualCount + ' ' + tableDefinition.dxfSymbolName + '\'s but expected ' + expectedCount);
            }
            curr = scanner.next();
            return table;
        }

        function parseViewPortRecords() {
            const viewPorts = [] as IViewPort[]; // 多个表项可以具有相同的名称，表示多个视区配置
            let viewPort = {} as IViewPort;

            log.debug('ViewPort {');
            curr = scanner.next();
            while (!groupIs(curr, 0, END_OF_TABLE_VALUE)) {

                switch (curr.code) {
                    case 2: // 视口名
                        viewPort.name = curr.value as string;
                        curr = scanner.next();
                        break;
                    case 10: // 视口的左下角
                        viewPort.lowerLeftCorner = parsePoint(curr);
                        curr = scanner.next();
                        break;
                    case 11: // 视口右上角
                        viewPort.upperRightCorner = parsePoint(curr);
                        curr = scanner.next();
                        break;
                    case 12: // 视口中心点（在 DCS 中）
                        viewPort.center = parsePoint(curr);
                        curr = scanner.next();
                        break;
                    case 13: // 捕捉基点（在 DCS 中）
                        viewPort.snapBasePoint = parsePoint(curr);
                        curr = scanner.next();
                        break;
                    case 14: // 捕捉间距 X 和 Y
                        viewPort.snapSpacing = parsePoint(curr);
                        curr = scanner.next();
                        break;
                    case 15: // 栅格间距 X 和 Y
                        viewPort.gridSpacing = parsePoint(curr);
                        curr = scanner.next();
                        break;
                    case 16: // 相对于目标点的观察方向（在 WCS 中）
                        viewPort.viewDirectionFromTarget = parsePoint(curr);
                        curr = scanner.next();
                        break;
                    case 17: //观察目标点（在 WCS 中）
                        viewPort.viewTarget = parsePoint(curr);
                        curr = scanner.next();
                        break;
                    case 42: // 焦距
                        viewPort.lensLength = curr.value as number;
                        curr = scanner.next();
                        break;
                    case 43: //前向剪裁平面（距目标点的偏移）
                        viewPort.frontClippingPlane = curr.value;
                        curr = scanner.next();
                        break;
                    case 44: // 后向剪裁平面（距目标点的偏移）
                        viewPort.backClippingPlane = curr.value;
                        curr = scanner.next();
                        break;
                    case 45: // 视图高度
                        viewPort.viewHeight = curr.value as number;
                        curr = scanner.next();
                        break;
                    case 50: // 捕捉旋转角度
                        viewPort.snapRotationAngle = curr.value as number;
                        curr = scanner.next();
                        break;
                    case 51: // 视图扭转角度
                        viewPort.viewTwistAngle = curr.value as number;
                        curr = scanner.next();
                        break;
                    case 71: // 视图模式（参见 VIEWMODE 系统变量）
                        viewPort.viewMode = curr.value as string;
                        curr = scanner.next();
                        break;
                    case 79: // UCS 的正交类型  0 = UCS 不正交 1 = 俯视图；2 = 仰视图 3 = 主视图；4 = 后视图 5 = 左视图；6 = 右视图
                        viewPort.orthographicType = curr.value as string;
                        curr = scanner.next();
                        break;
                    case 110: // UCS 原点（在 DCS 中）
                        viewPort.ucsOrigin = parsePoint(curr);
                        curr = scanner.next();
                        break;
                    case 111: // UCS X 轴
                        viewPort.ucsXAxis = parsePoint(curr);
                        curr = scanner.next();
                        break;
                    case 112: // UCS Y 轴
                        viewPort.ucsYAxis = parsePoint(curr);
                        curr = scanner.next();
                        break;
                    case 281:
                        /*
                        * 渲染模式： 0 = 二维优化（传统二维） 1 = 线框图 2 = 隐藏线 3 = 平面着色 4 = 体着色 5 = 带线框平面着色 6 = 带线框体着色
                        * 所有非二维优化渲染模式均使用新三维图形管道。这些值直接与 SHADEMODE 命令和 AcDbAbstractViewTableRecord::RenderMode 枚举相对应
                        */
                        viewPort.renderMode = curr.value as string;
                        curr = scanner.next();
                        break;
                    case 292:
                        viewPort.defaultLightingOn = curr.value as string;
                        curr = scanner.next();
                        break;
                    case 330:
                        viewPort.ownerHandle = curr.value as string;
                        curr = scanner.next();
                        break;
                    case 63: // These are all ambient color. Perhaps should be a gradient when multiple are set.
                    case 421:
                    case 431:
                        viewPort.ambientColor = curr.value as number;
                        curr = scanner.next();
                        break;
                    case 0:
                        // New ViewPort
                        if (curr.value === 'VPORT') {
                            log.debug('}');
                            viewPorts.push(viewPort);
                            log.debug('ViewPort {');
                            viewPort = {} as IViewPort;
                            curr = scanner.next();
                        }
                        break;
                    default:
                        logUnhandledGroup(curr);
                        curr = scanner.next();
                        break;
                }
            }
            // Note: do not call scanner.next() here,
            //  parseTable() needs the current group
            log.debug('}');
            viewPorts.push(viewPort);

            return viewPorts;
        }

        function parseLineTypes() {
            const ltypes = {} as Record<string, ILineType>;
            let ltype = {} as ILineType;
            let length = 0;
            let ltypeName: string;

            log.debug('LType {');
            curr = scanner.next();
            while (!groupIs(curr, 0, 'ENDTAB')) {

                switch (curr.code) {
                    case 2:
                        ltype.name = curr.value as string;
                        ltypeName = curr.value as string;
                        curr = scanner.next();
                        break;
                    case 3:
                        ltype.description = curr.value as string;
                        curr = scanner.next();
                        break;
                    case 73: // Number of elements for this line type (dots, dashes, spaces);
                        length = curr.value as number;
                        if (length > 0) ltype.pattern = [];
                        curr = scanner.next();
                        break;
                    case 40: // total pattern length
                        ltype.patternLength = curr.value as number;
                        curr = scanner.next();
                        break;
                    case 49:
                        ltype.pattern.push(curr.value as string);
                        curr = scanner.next();
                        break;
                    case 0:
                        log.debug('}');
                        if (length > 0 && length !== ltype.pattern.length) log.warn('lengths do not match on LTYPE pattern');
                        ltypes[ltypeName!] = ltype;
                        ltype = {} as ILineType;
                        log.debug('LType {');
                        curr = scanner.next();
                        break;
                    default:
                        curr = scanner.next();
                }
            }

            log.debug('}');
            ltypes[ltypeName!] = ltype;
            return ltypes;
        }

        function parseLayers() {
            const layers = {} as Record<string, ILayer>;
            let layer = {} as ILayer;
            let layerName: string | undefined;

            log.debug('Layer {');
            curr = scanner.next();
            while (!groupIs(curr, 0, 'ENDTAB')) {

                switch (curr.code) {
                    case 2: // layer name
                        layer.name = curr.value as string;
                        layerName = curr.value as string;
                        curr = scanner.next();
                        break;
                    case 62: // color, visibility
                        // @ts-ignore
                        layer.visible = curr.value >= 0;
                        // TODO 0 and 256 are BYBLOCK and BYLAYER respectively. Need to handle these values for layers?.
                        layer.colorIndex = Math.abs(curr.value as number);
                        layer.color = getAcadColor(layer.colorIndex as number);
                        curr = scanner.next();
                        break;
                    case 70: // frozen layer
                        layer.frozen = (((curr.value as number) & 1) != 0 || ((curr.value as number) & 2) != 0);
                        curr = scanner.next();
                        break;
                    case 0:
                        // New Layer
                        if (curr.value === 'LAYER') {
                            log.debug('}');
                            layers[layerName!] = layer;
                            log.debug('Layer {');
                            layer = {} as ILayer;
                            layerName = undefined;
                            curr = scanner.next();
                        }
                        break;
                    default:
                        logUnhandledGroup(curr);
                        curr = scanner.next();
                        break;
                }
            }
            // Note: do not call scanner.next() here,
            //  parseLayerTable() needs the current group
            log.debug('}');
            layers[layerName!] = layer;

            return layers;
        }

        function parseDimStyles() {
            let dimStyles = {},
                styleName: string | undefined,
                style: any = {};

            log.debug('DimStyle {');
            curr = scanner.next();
            while (!groupIs(curr, 0, 'ENDTAB')) {
                if (dimStyleCodes.has(curr.code)) {
                    style[dimStyleCodes.get(curr.code) as string] = curr.value
                    curr = scanner.next();
                } else {
                    switch (curr.code) {
                        case 2: // style name
                            style.name = curr.value;
                            styleName = curr.value as string;
                            curr = scanner.next();
                            break;
                        case 0:
                            // New style
                            if (curr.value === 'DIMSTYLE') {
                                log.debug('}');
                                // @ts-ignore
                                dimStyles[styleName] = style;
                                log.debug('DimStyle {');
                                style = {};
                                styleName = undefined;
                                curr = scanner.next();
                            }
                            break;
                        default:
                            logUnhandledGroup(curr);
                            curr = scanner.next();
                            break;
                    }
                }
            }
            // Note: do not call scanner.next() here,
            //  parseLayerTable() needs the current group
            log.debug('}');
            dimStyles[styleName as string] = style;

            return dimStyles;
        }

        const parseStyles = function () {
            const styles = {};
            let style: any = {};
            let styleName;

            log.debug('Style {');
            curr = scanner.next();
            while (!groupIs(curr, 0, END_OF_TABLE_VALUE)) {
                switch (curr.code) {
                    case 100:
                        style.subClassMarker = curr.value;
                        curr = scanner.next();
                        break;
                    case 2:
                        style.styleName = curr.value;
                        styleName = curr.value;
                        curr = scanner.next();
                        break;
                    case 70:
                        style.standardFlag = curr.value;
                        curr = scanner.next();
                        break;
                    case 40:
                        style.fixedTextHeight = curr.value;
                        curr = scanner.next();
                        break;
                    case 41:
                        style.widthFactor = curr.value;
                        curr = scanner.next();
                        break;
                    case 50:
                        style.obliqueAngle = curr.value;
                        curr = scanner.next();
                        break;
                    case 71:
                        style.textGenerationFlag = curr.value;
                        curr = scanner.next();
                        break;
                    case 42:
                        style.lastHeight = curr.value;
                        curr = scanner.next();
                        break;
                    case 3:
                        style.font = curr.value;
                        curr = scanner.next();
                        break;
                    case 4:
                        style.bigFont = curr.value;
                        curr = scanner.next();
                        break;
                    case 1071:
                        style.extendedFont = curr.value;
                        curr = scanner.next();
                        break;
                    case 0:
                        if (curr.value === 'STYLE') {
                            log.debug('}');
                            styles[styleName] = style;
                            log.debug('Style {');
                            style = {};
                            styleName = undefined;
                            curr = scanner.next();
                        }
                        break;
                    default:
                        logUnhandledGroup(curr);
                        curr = scanner.next();
                        break;
                }
            }
            log.debug('}');
            styles[styleName] = style;
            return styles;
        };

        const tableDefinitions = {
            VPORT: {
                tableRecordsProperty: 'viewPorts',
                tableName: 'viewPort',
                dxfSymbolName: 'VPORT',
                parseTableRecords: parseViewPortRecords
            },
            LTYPE: {
                tableRecordsProperty: 'lineTypes',
                tableName: 'lineType',
                dxfSymbolName: 'LTYPE',
                parseTableRecords: parseLineTypes
            },
            LAYER: {
                tableRecordsProperty: 'layers',
                tableName: 'layer',
                dxfSymbolName: 'LAYER',
                parseTableRecords: parseLayers
            },
            DIMSTYLE: {
                tableRecordsProperty: 'dimStyles',
                tableName: 'dimstyle',
                dxfSymbolName: 'DIMSTYLE',
                parseTableRecords: parseDimStyles
            },
            STYLE: {
                tableRecordsProperty: 'styles',
                tableName: 'style',
                dxfSymbolName: 'STYLE',
                parseTableRecords: parseStyles,
            },
        } as ITableDefinitions;

        /**
         * 在解析器首次读取0:ENTITIES组后调用。扫描器应该已经在第一个实体的开始。
         * @return {Array} the resulting entities
         */
        function parseEntities(forBlock: boolean) {
            const entities = [] as IEntity[];

            const endingOnValue = forBlock ? 'ENDBLK' : 'ENDSEC';

            if (!forBlock) {
                curr = scanner.next();
            }
            while (true) {
                if (curr.code === 0) {
                    if (curr.value === endingOnValue) {
                        break;
                    }

                    const handler = self._entityHandlers[curr.value as EntityName];
                    if (handler != null) {
                        log.debug(curr.value + ' {');
                        const entity = handler.parseEntity(scanner, curr);
                        curr = scanner.lastReadGroup!;
                        log.debug('}');
                        ensureHandle(entity);
                        entities.push(entity);
                    } else {
                        log.warn('Unhandled entity ' + curr.value);
                        curr = scanner.next();
                        continue;
                    }
                } else {
                    // ignored lines from unsupported entity
                    curr = scanner.next();
                }
            }
            if (endingOnValue == 'ENDSEC') curr = scanner.next(); // swallow up ENDSEC, but not ENDBLK
            return entities;
        }

        /**
         * Parses a 2D or 3D point, returning it as an object with x, y, and
         * (sometimes) z property if it is 3D. It is assumed the current group
         * is x of the point being read in, and scanner.next() will return the
         * y. The parser will determine if there is a z point automatically.
         * @return {Object} The 2D or 3D point as an object with x, y[, z]
         */
        function parsePoint(curr: IGroup) {
            const point = {} as IPoint;
            let code = curr.code;

            point.x = curr.value as number;

            code += 10;
            curr = scanner.next();
            if (curr.code != code)
                throw new Error('Expected code for point value to be ' + code +
                    ' but got ' + curr.code + '.');
            point.y = curr.value as number;

            code += 10;
            curr = scanner.next();
            if (curr.code != code) {
                scanner.rewind();
                return point;
            }
            point.z = curr.value as number;

            return point;
        }

        function ensureHandle(entity: IEntity | IBlock) {
            if (!entity) throw new TypeError('entity cannot be undefined or null');

            if (!entity.handle) entity.handle = lastHandle++;
        }

        parseAll();
        return dxf;
    }
}

function groupIs(group: IGroup, code: number, value: string | number | boolean) {
    return group.code === code && group.value === value;
}

function logUnhandledGroup(curr: IGroup) {
    log.debug('unhandled group ' + debugCode(curr));
}


function debugCode(curr: IGroup) {
    return curr.code + ':' + curr.value;
}

/**
 * Returns the truecolor value of the given AutoCad color index value
 * @return {Number} truecolor value as a number
 */
function getAcadColor(index: number) {
    return AUTO_CAD_COLOR_INDEX[index];
}

// const BLOCK_ANONYMOUS_FLAG = 1;
// const BLOCK_NON_CONSTANT_FLAG = 2;
// const BLOCK_XREF_FLAG = 4;
// const BLOCK_XREF_OVERLAY_FLAG = 8;
// const BLOCK_EXTERNALLY_DEPENDENT_FLAG = 16;
// const BLOCK_RESOLVED_OR_DEPENDENT_FLAG = 32;
// const BLOCK_REFERENCED_XREF = 64;


/* Notes */
// Code 6 of an entity indicates inheritance of properties (eg. color).
//   BYBLOCK means inherits from block
//   BYLAYER (default) mean inherits from layer
