const protobuf = require('protobufjs');
const parseArgs = require('minimist');
const fs = require('fs');
const assert = require('assert');

class PBTools {

  constructor() {
    this.csMsgIdPb = null;
    this.csProtoPb = null;
    this.ssMsgIdPb = null;
    this.ssProtoPb = null;
    this.mtPb = null;
    this.protoDir = './proto/';
  }

  async loadCsPb() {
    if (!fs.existsSync(this.protoDir + 'cs_proto.proto')) {
      console.error('cs_proto.proto file not exists');
      return;
    }
    {
      this.csProtoPb = await (new protobuf.Root()).load(
        this.protoDir + 'cs_proto.proto',
        {
          'keepCase': true
        }
      );
    }
    {
      this.csMsgIdPb = await (new protobuf.Root()).load(
        this.protoDir + 'cs_msgid.proto',
        {
          'keepCase': true
        }
      );
      this.csCmMsgId = this.csMsgIdPb.lookup('CMMessageId_e');
      this.csSmMsgId = this.csMsgIdPb.lookup('SMMessageId_e');
    }
  }

  async loadSsPb() {
    if (!fs.existsSync(this.protoDir + 'ss_proto.proto')) {
      console.error('ss_proto.proto file not exists');
      return;
    }
    {
      this.ssProtoPb = await (new protobuf.Root()).load(
        this.protoDir + 'ss_proto.proto',
        {
          'keepCase': true
        }
      );
    }
    {
      this.ssMsgIdPb = await (new protobuf.Root()).load(
        this.protoDir + 'ss_msgid.proto',
        {
          'keepCase': true
        }
      );
      this.ssSSmMsgId = this.ssMsgIdPb.lookup('SSMMessageId_e');
    }
  }

  async loadMtPb() {
    if (!fs.existsSync(this.protoDir + 'mt.proto')) {
      console.error('mt.proto file not exists');
      return;
    }
    {
      this.mtPb = await (new protobuf.Root()).load(
        this.protoDir + 'mt.proto',
        {
          'keepCase': true
        }
      );
    }
  }

  async init() {
    await this.loadCsPb();
    await this.loadSsPb();
    await this.loadMtPb();
    if (this.csProtoPb) {
      await this.genCsAutoGen();
    }
    if (this.mtPb) {
      await this.genMtbAutoGen();
    }
    if (this.ssProtoPb) {
      await this.genSsAutoGen();
    }
  }

  async genCsAutoGen() {
    let data = `package cs

import (
        "f5"
        proto "github.com/golang/protobuf/proto"
)

type CsNetMsgHandler f5.NetMsgHandler[MsgHandler];

type MsgHandlerImpl struct {
}

var handlers [2000]*CsNetMsgHandler

func GetNetMsgHandler(msgId uint16) *CsNetMsgHandler {
        handler := handlers[msgId]
        return handler
}

func DispatchMsg(handler *CsNetMsgHandler, hdr *f5.MsgHdr, msgHandler MsgHandler) {
        handler.Cb(hdr, msgHandler)
}

func RegHandlerId(msgId int, handlerId int) {
        handler := handlers[msgId]
        handler.HandlerId = handlerId
}

func ParsePb(msgId uint16, data []byte) interface{} {
        handler := handlers[msgId]
        if handler == nil {
                return nil
        }
        return handler.ParseCb(data)
}
`;
    data += `
type MsgHandler interface {`;
    this.csProtoPb.nested.cs.nestedArray.forEach(
      (item) => {
        if (item.name[0] == 'C' &&
            item.name[1] == 'M') {
          data += `
        ${item.name}(*f5.MsgHdr, *${item.name})`;
        }
      });
    data += `
}
`;
    this.csProtoPb.nested.cs.nestedArray.forEach(
      (item) => {
        if (item.name[0] == 'C' &&
            item.name[1] == 'M') {
          data += `
func (this *MsgHandlerImpl) ${item.name}(hdr *f5.MsgHdr, msg *${item.name}) {
}
`;
        }
      });
    this.csProtoPb.nested.cs.nestedArray.forEach(
      (item) => {
        if (item.name[0] == 'C' &&
            item.name[1] == 'M') {
          data += `
func (this *${item.name}) GetNetMsgId() uint16 {
        return uint16(CMMessageIdE__${item.name})
}
`;
        } else if (item.name[0] == 'S' &&
                   item.name[1] == 'M') {
          data += `
func (this *${item.name}) GetNetMsgId() uint16 {
        return uint16(SMMessageIdE__${item.name})
}
`;
        }
      });
    this.csProtoPb.nested.cs.nestedArray.forEach(
      (item) => {
        if (item.name[0] == 'S' &&
            item.name[1] == 'M') {
          let hasErrCodeField = false;
          let hasErrMsgField = false;
          item.fieldsArray.forEach(
            (item2) => {
              if (item2.name == 'errcode') {
                hasErrCodeField = true;
              } else if (item2.name == 'errmsg') {
                hasErrMsgField = true;
              }
            });
          if (hasErrCodeField && hasErrMsgField) {
            data += `
func (this *${item.name}) Err(errCode int32, errMsg string) *${item.name} {
        this.Errcode = proto.Int32(errCode)
        this.Errmsg = proto.String(errMsg)
        return this
}
`;
          }
        }
      });
    data += `
func init() {
`;
    this.csProtoPb.nested.cs.nestedArray.forEach(
      (item) => {
        if (item.name[0] == 'C' &&
            item.name[1] == 'M') {
          data += `
        handlers[int(CMMessageIdE__${item.name})] = &CsNetMsgHandler{
                MsgId: int(CMMessageIdE__${item.name}),
                ParseCb: func (data []byte) interface{} {
                        msg := &${item.name}{}
                        proto.Unmarshal(data, msg)
                        return msg
                },
                Cb: func (hdr *f5.MsgHdr, handler MsgHandler) {
                        handler.${item.name}(hdr, hdr.Msg.(*${item.name}))
                },
        }
`;
        }
      });
    data += `
}`;
    fs.writeFileSync('./cs/cs.auto_gen.go', data);
  }

  async genSsAutoGen() {
    let data = `package ss

import (
        "f5"
        proto "github.com/golang/protobuf/proto"
)

type SsNetMsgHandler f5.NetMsgHandler[MsgHandler];

type MsgHandlerImpl struct {
}

var handlers [2000]*SsNetMsgHandler

func GetNetMsgHandler(msgId uint16) *SsNetMsgHandler {
        handler := handlers[msgId]
        return handler
}

func DispatchMsg(handler *SsNetMsgHandler, hdr *f5.MsgHdr, msgHandler MsgHandler) {
        handler.Cb(hdr, msgHandler)
}

func RegHandlerId(msgId int, handlerId int) {
        handler := handlers[msgId]
        handler.HandlerId = handlerId
}

func ParsePb(msgId uint16, data []byte) interface{} {
        handler := handlers[msgId]
        if handler == nil {
                return nil
        }
        return handler.ParseCb(data)
}
`;
    data += `
type MsgHandler interface {`;
    this.ssProtoPb.nested.ss.nestedArray.forEach(
      (item) => {
        if (item.name[0] == 'S' &&
            item.name[1] == 'S') {
          data += `
        ${item.name}(*f5.MsgHdr, *${item.name})`;
        }
      });
    data += `
}
`;
    this.ssProtoPb.nested.ss.nestedArray.forEach(
      (item) => {
        if (item.name[0] == 'S' &&
            item.name[1] == 'S') {
          data += `
func (this *MsgHandlerImpl) ${item.name}(hdr *f5.MsgHdr, msg *${item.name}) {
}
`;
        }
      });
    this.ssProtoPb.nested.ss.nestedArray.forEach(
      (item) => {
        if (item.name[0] == 'S' &&
            item.name[1] == 'S') {
          data += `
func (this *${item.name}) GetNetMsgId() uint16 {
        return uint16(SSMessageIdE__${item.name})
}
`;
        } else if (item.name[0] == 'S' &&
                   item.name[1] == 'M') {
          data += `
func (this *${item.name}) GetNetMsgId() uint16 {
        return uint16(SMMessageIdE__${item.name})
}
`;
        }
      });
    data += `
func init() {
`;
    this.ssProtoPb.nested.ss.nestedArray.forEach(
      (item) => {
        if (item.name[0] == 'S' &&
            item.name[1] == 'S') {
          data += `
        handlers[int(SSMessageIdE__${item.name})] = &SsNetMsgHandler{
                MsgId: int(SSMessageIdE__${item.name}),
                ParseCb: func (data []byte) interface{} {
                        msg := &${item.name}{}
                        proto.Unmarshal(data, msg)
                        return msg
                },
                Cb: func (hdr *f5.MsgHdr, handler MsgHandler) {
                        handler.${item.name}(hdr, hdr.Msg.(*${item.name}))
                },
        }
`;
        }
      });
    data += `
}`;
    fs.writeFileSync('./ss/ss.auto_gen.go', data);
  }

  async genMtbAutoGen() {
    let data = `package mtb

import (
        "f5"
)

`;
    this.mtPb.nested.mt.nestedArray.forEach(
      (item) => {
        data += `type ${item.name} struct {\n`;
        item.fieldsArray.forEach
        (
          (item2, index) =>  {
            assert(item2.id <= 127);
            const newFieldName = this.keyWordReplace(item2.name);
            data += `        ${newFieldName} ` + this.dumpClassField(item, item2, index) + `\n`;
          }
        );
        data += `
        _flags1_ uint64
        _flags2_ uint64
}

`;
      }
    );
    data += '';
    this.mtPb.nested.mt.nestedArray.forEach(
      (item) => {
        item.fieldsArray.forEach
        (
          (item2, index) => {
            const newName = this.converUpperCamelCaseName(item2.name);
            data += `func (this *${item.name}) Get${newName}() ` +
              this.dumpClassField(item, item2, index) + ` {\n`;
            const newFieldName = this.keyWordReplace(item2.name);
            data += `        return this.${newFieldName}\n`;
            data += `}\n\n`;
            data += `func (this *${item.name}) Has${newName}() bool {\n`;
            if (item2.id < 64) {
              data += `        return (this._flags1_ & (uint64(1) << ${item2.id})) > 0\n`;
            } else {
              data += `        return (this._flags2_ & (uint64(1) << (${item2.id} - 64))) > 0\n`;
            }
            data += `}\n\n`;
          }
        );
      }
    );
    this.mtPb.nested.mt.nestedArray.forEach(
      (item) =>  {
        data += `
func (this *${item.name}) LoadFromKv(kv map[string]interface{}) {
`;
        item.fieldsArray.forEach
        (
          (item2, index) => {
            const newFieldName = this.keyWordReplace(item2.name);
            if (item2.id < 64) {
              data += `        f5.ReadMetaTableField(&this.${newFieldName}, "${item2.name}", &this._flags1_, ${item2.id}, kv)\n`;
            } else {
              data += `        f5.ReadMetaTableField(&this.${newFieldName}, "${item2.name}", &this._flags2_, ${item2.id} - 64, kv)\n`;
            }
          }
        );
        data += '}\n';
      }
    );
    fs.writeFileSync('./mtb/mtb.auto_gen.go', data);
  }

  dumpClassField(cls, field, index) {
    const fieldName = field.name;
    switch (field.type) {
    case 'int32':
      {
        return `int32`;
      }
      break;
    case 'int64':
      {
        return `int64`;
      }
      break;
    case 'float':
      {
        return `float32`;
      }
      break;
    case 'double':
      {
        return `float64`;
      }
      break;
    case 'string':
      {
        return `string`;
      }
      break;
    default:
      console.log(field);
      assert(false, 'error field type:' + field.type);
      break;
    }
  }

  converUpperCamelCaseName(name) {
    let newName = '';
    let preIs_ = false;
    for (let i = 0; i < name.length; ++i) {
      if (i == 0) {
        newName += name[i].toUpperCase();
        preIs_ = name[i] == '_';
      } else {
        if (preIs_) {
          if (name[i] != '_') {
            newName += name[i].toUpperCase();
          }
        } else {
          if (name[i] != '_') {
            newName += name[i];
          }
        }
        preIs_ = name[i] == '_';
      }
    }
    return newName;
  }

  keyWordReplace(name) {
    const keyWords = {
      "type": 1
    };
    if (keyWords[name]) {
      return name + "_";
    } else {
      return name;
    }
  }

}

(new PBTools).init();
