/*
* Copyright (c) 2015, zheng-ji.info
* */
package gophone

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"strconv"
	"strings"
)

const (
	IntLen           = 4
	CharLen          = 1
	PhoneIndexLength = 9
	Chunk            = 100
)

type GOPhone struct {
	phoneDat []byte
}

type PhoneRecord struct {
	PhoneNum string
	Province string
	City     string
	ZipCode  string
	AreaZone string
	CardType string
}

func New(phoneDat []byte) *GOPhone {
	if nil == phoneDat || len(phoneDat) == 0 {
		panic(errors.New("phoneDat is required"))
	}
	return &GOPhone{
		phoneDat: phoneDat,
	}
}

func (g *GOPhone) Display() {
	fmt.Println(g.getVersion())
	fmt.Println(g.getTotalRecord())
	fmt.Println(g.getFirstRecordOffset())
}

func (pr PhoneRecord) String() string {
	_str := fmt.Sprintf("PhoneNum: %s\nAreaZone: %s\nCardType: %s\nCity: %s\nZipCode: %s\nProvince: %s\n", pr.PhoneNum, pr.AreaZone, pr.CardType, pr.City, pr.ZipCode, pr.Province)
	return _str
}

func (g *GOPhone) getVersion() string {
	return string(g.phoneDat[0:IntLen])
}

func (g *GOPhone) getTotalRecord() int32 {
	total := (int32(len(g.phoneDat)) - g.getFirstRecordOffset()) / PhoneIndexLength
	return total
}

func (g *GOPhone) getFirstRecordOffset() int32 {
	var offset int32
	buffer := bytes.NewBuffer(g.phoneDat[IntLen : IntLen*2])
	_ = binary.Read(buffer, binary.LittleEndian, &offset)
	return offset
}

func (g *GOPhone) getIndexRecord(offset int32) (phonePrefix int32, recordOffset int32, cardType byte) {
	buffer := bytes.NewBuffer(g.phoneDat[offset : offset+IntLen])
	_ = binary.Read(buffer, binary.LittleEndian, &phonePrefix)
	buffer = bytes.NewBuffer(g.phoneDat[offset+IntLen : offset+IntLen*2])
	_ = binary.Read(buffer, binary.LittleEndian, &recordOffset)
	buffer = bytes.NewBuffer(g.phoneDat[offset+IntLen*2 : offset+IntLen*2+CharLen])
	_ = binary.Read(buffer, binary.LittleEndian, &cardType)
	return
}

func (g *GOPhone) getOpCompany(cardtype byte) string {
	var cardStr = ""
	switch cardtype {
	case '1':
		cardStr = "移动"
	case '2':
		cardStr = "联通"
	case '3':
		cardStr = "电信"
	case '4':
		cardStr = "电信虚拟运营商"
	case '5':
		cardStr = "联通虚拟运营商"
	default:
		cardStr = "移动虚拟运营商"
	}
	return cardStr
}

// BinarySearch
func (g *GOPhone) Find(phoneNum string) (pr *PhoneRecord, err error) {
	err = nil
	if len(phoneNum) < 7 || len(phoneNum) > 11 {
		return nil, errors.New("illegal phone length")
	}

	var left int32 = 0
	phoneSevenInt, _ := strconv.ParseInt(phoneNum[0:7], 10, 32)
	phoneSevenInt32 := int32(phoneSevenInt)
	totalLen := int32(len(g.phoneDat))
	right := g.getTotalRecord()
	firstPhoneRecordOffset := g.getFirstRecordOffset()
	for {
		if left > right {
			break
		}
		mid := (left + right) / 2
		currentOffset := firstPhoneRecordOffset + mid*PhoneIndexLength

		if currentOffset >= totalLen {
			break
		}
		curPhone, recordOffset, cardType := g.getIndexRecord(currentOffset)
		if curPhone > phoneSevenInt32 {
			right = mid - 1
		} else if curPhone < phoneSevenInt32 {
			left = mid + 1
		} else {
			s := recordOffset
			e := recordOffset + int32(strings.Index(string(g.phoneDat[recordOffset:recordOffset+Chunk]), "\000"))
			recordContent := string(g.phoneDat[s:e])
			_tmp := strings.Split(recordContent, "|")
			cardStr := g.getOpCompany(cardType)
			pr = &PhoneRecord{
				PhoneNum: phoneNum,
				Province: _tmp[0],
				City:     _tmp[1],
				ZipCode:  _tmp[2],
				AreaZone: _tmp[3],
				CardType: cardStr,
			}
			return
		}
	}
	return nil, errors.New("num not found")
}