text: const codepageBySortId = require('./collation').codepageBySortId;
  const codepageByLcid = require('./collation').codepageByLcid;
  const TYPE = require('./data-type').TYPE;
  const sprintf = require('sprintf-js').sprintf;
  
  module.exports = metadataParse;
  module.exports.readPrecision = readPrecision;
  module.exports.readScale = readScale;
  module.exports.readCollation = readCollation;
  
  function readDataLength(parser, type, callback) {
    if ((type.id & 0x30) === 0x20) {
      // xx10xxxx - s2.2.4.2.1.3
      // Variable length
      if (type.dataLengthFromScale) {
        return callback(0); // dataLength is resolved from scale
      } else if (type.fixedDataLength) {
        return callback(type.fixedDataLength);
      }
  
      switch (type.dataLengthLength) {
        case 0:
          return callback(undefined);
  
        case 1:
          return parser.readUInt8(callback);
  
        case 2:
          return parser.readUInt16LE(callback);
  
        case 4:
          return parser.readUInt32LE(callback);
  
        default:
          return parser.emit(new Error('Unsupported dataLengthLength ' + type.dataLengthLength + ' for data type ' + type.name));
      }
    } else {
      callback(undefined);
    }
  }
  
  function readPrecision(parser, type, callback) {
    if (type.hasPrecision) {
      parser.readUInt8(callback);
    } else {
      callback(undefined);
    }
  }
  
  function readScale(parser, type, callback) {
    if (type.hasScale) {
      parser.readUInt8(callback);
    } else {
      callback(undefined);
    }
  }
  
  function readCollation(parser, type, callback) {
    if (type.hasCollation) {
      // s2.2.5.1.2
      parser.readBuffer(5, (collationData) => {
        const collation = {};
  
        collation.lcid = (collationData[2] & 0x0F) << 16;
        collation.lcid |= collationData[1] << 8;
        collation.lcid |= collationData[0];
  
        // This may not be extracting the correct nibbles in the correct order.
        collation.flags = collationData[3] >> 4;
        collation.flags |= collationData[2] & 0xF0;
  
        // This may not be extracting the correct nibble.
        collation.version = collationData[3] & 0x0F;
  
        collation.sortId = collationData[4];
  
        collation.codepage = codepageBySortId[collation.sortId] || codepageByLcid[collation.lcid] || 'CP1252';
  
        callback(collation);
      });
    } else {
      callback(undefined);
    }
  }
  
  function readSchema(parser, type, callback) {
    if (type.hasSchemaPresent) {
      // s2.2.5.5.3
      parser.readUInt8((schemaPresent) => {
        if (schemaPresent === 0x01) {
          parser.readBVarChar((dbname) => {
            parser.readBVarChar((owningSchema) => {
              parser.readUsVarChar((xmlSchemaCollection) => {
                callback({
                  dbname: dbname,
                  owningSchema: owningSchema,
                  xmlSchemaCollection: xmlSchemaCollection
                });
              });
            });
          });
        } else {
          callback(undefined);
        }
      });
    } else {
      callback(undefined);
    }
  }
  
  function readUDTInfo(parser, type, callback) {
    if (type.hasUDTInfo) {
      parser.readUInt16LE((maxByteSize) => {
        parser.readBVarChar((dbname) => {
          parser.readBVarChar((owningSchema) => {
            parser.readBVarChar((typeName) => {
              parser.readUsVarChar((assemblyName) => {
                callback({
                  maxByteSize: maxByteSize,
                  dbname: dbname,
                  owningSchema: owningSchema,
                  typeName: typeName,
                  assemblyName: assemblyName
                });
              });
            });
          });
        });
      });
    } else {
      return callback();
    }
  }
  
  function metadataParse(parser, options, callback) {
    (options.tdsVersion < '7_2' ? parser.readUInt16LE : parser.readUInt32LE).call(parser, (userType) => {
      parser.readUInt16LE((flags) => {
        parser.readUInt8((typeNumber) => {
          const type = TYPE[typeNumber];
  
          if (!type) {
            return parser.emit(new Error(sprintf('Unrecognised data type 0x%02X', typeNumber)));
          }
  
          readDataLength(parser, type, (dataLength) => {
            readPrecision(parser, type, (precision) => {
              readScale(parser, type, (scale) => {
                if (scale && type.dataLengthFromScale) {
                  dataLength = type.dataLengthFromScale(scale);
                }
  
                readCollation(parser, type, (collation) => {
                  readSchema(parser, type, (schema) => {
                    readUDTInfo(parser, type, (udtInfo) => {
                      callback({
                        userType: userType,
                        flags: flags,
                        type: type,
                        collation: collation,
                        precision: precision,
                        scale: scale,
                        dataLength: dataLength,
                        schema: schema,
                        udtInfo: udtInfo
                      });
                    });
                  });
                });
              });
            });
          });
        });
      });
    });
  }
  