package baidunavi

import (
	"crypto/md5"
	"encoding/json"
	"errors"
	"fmt"
	"git.rosy.net.cn/baseapi/platformapi"
	"git.rosy.net.cn/baseapi/utils"
	"io/ioutil"
	"net/http"
	"net/url"
	"sort"
	"strings"
	"time"
)

const (
	signKey   = "sn"
	resultKey = "result"
	prodURL   = "http://api.map.baidu.com"
	prodURL2  = "https://api.map.baidu.com"
)

const (
	StatusCodeSuccess                    = 0
	StatusCodeInternalErr                = 1   // 服务器内部错误
	StatusCodeParamsErr                  = 2   //参数无效
	StatusCodeNoReturn                   = 7   //无返回结果
	StatusCodeExceedDailyQuota           = 301 // 永久配额超限，限制访问
	StatusCodeExceedQuota                = 302 // 天配额超限，限制访问
	StatusCodeExceedDailyConcurrentQuota = 401 // 当前并发量已经超过约定并发配额，限制访问
	StatusCodeExceedConcurrentQuota      = 402 // 当前并发量已经超过约定并发配额，并且服务总并发量也已经超过设定的总并发配额，限制访问
)

//bd09ll表示百度经纬度坐标，
//bd09mc表示百度墨卡托坐标，
//gcj02表示经过国测局加密的坐标，
//wgs84表示gps获取的坐标，

const (
	CoordSysWGS84 = 1 // GPS设备获取的角度坐标，WGS84坐标
	CoordSysGCJ02 = 3 // google地图、soso地图、aliyun地图、mapabc地图和amap地图所用坐标，国测局（GCJ02）坐标
	CoordSysBaiDu = 5 // 百度地图采用的经纬度坐标

	CoordSysGaoDe2Baidu = "1" // 高德坐标转百度坐标
	CoordSysBaidu2Gaode = "5" // 百度坐标转高德坐标
)

const (
	MaxCoordsConvBatchSize = 100
)

var (
	exceedLimitCodes = map[int]int{
		StatusCodeExceedDailyQuota:           1,
		StatusCodeExceedQuota:                1,
		StatusCodeExceedDailyConcurrentQuota: 1,
		StatusCodeExceedConcurrentQuota:      1,
	}

	canRetryCodes = map[int]int{
		StatusCodeInternalErr: 1,
	}
)

type Coordinate struct {
	Lng float64 `json:"x"`
	Lat float64 `json:"y"`
}

type Coordinate2 struct {
	Lng string `json:"lng"`
	Lat string `json:"lat"`
}

type RidingResp struct {
	Status  int    `json:"status"`  //状态码
	Message string `json:"message"` //	状态码对应的信息
	Result  struct {
		Origin struct { //起点
			Lng float64 `json:"lng"`
			Lat float64 `json:"lat"`
		}
		Destination struct { //终点
			Lng float64 `json:"lng"`
			Lat float64 `json:"lat"`
		}
		Routers struct {
			Distance float64 `json:"distance"` //方案距离，单位：米
			Duration int     `json:"duration"` //线路耗时，单位：秒
			Steps    []struct {
			}
			Name          string `json:"name"`        //道路名称
			Instruction   string `json:"instruction"` //路段描述
			StartLocation struct {
				Lng float64 `json:"lng"`
				Lat float64 `json:"lat"`
			}
			EndLocation struct {
				Lng float64 `json:"lng"`
				Lat float64 `json:"lat"`
			}
			Path []struct {
				Lng float64 `json:"lng"`
				Lat float64 `json:"lat"`
			}
		}
	} `json:"result"`
}

type API struct {
	client *http.Client
	config *platformapi.APIConfig
	ak     string
	sk     string
}

func New(ak, sk string, config ...*platformapi.APIConfig) *API {
	curConfig := platformapi.DefAPIConfig
	if len(config) > 0 {
		curConfig = *config[0]
	}
	return &API{
		ak:     ak,
		sk:     sk,
		client: &http.Client{Timeout: curConfig.ClientTimeout},
		config: &curConfig,
	}
}

func (a *API) signParams(apiStr string, mapData map[string]interface{}) string {
	keys := make([]string, 0)
	for k := range mapData {
		if k != signKey {
			keys = append(keys, k)
		}
	}
	sort.Strings(keys)

	strList := []string{}
	for _, k := range keys {
		strList = append(strList, k+"="+url.QueryEscape(fmt.Sprint(mapData[k])))
	}
	finalStr := "/" + apiStr + "?" + strings.Join(strList, "&") + a.sk
	finalStr = url.QueryEscape(finalStr)
	return fmt.Sprintf("%x", md5.Sum([]byte(finalStr)))
}

func genGetURL(baseURL, apiStr string, params map[string]interface{}) string {
	keys := make([]string, 0)
	for k := range params {
		if k != signKey {
			keys = append(keys, k)
		}
	}
	sort.Strings(keys)

	strList := []string{}
	for _, k := range keys {
		strList = append(strList, k+"="+url.QueryEscape(fmt.Sprint(params[k])))
	}
	strList = append(strList, signKey+"="+url.QueryEscape(fmt.Sprint(params[signKey])))
	queryString := "?" + strings.Join(strList, "&")
	if apiStr != "" {
		return baseURL + "/" + apiStr + queryString
	}
	return baseURL + queryString
}

func (a *API) AccessAPI2(apiStr string, param url.Values) (retVal map[string]interface{}, err error) {

	for _, v := range BaiduAKList {
		param.Add("ak", v)
		param.Add("output", "json")
		params2 := map[string]interface{}{}

		for i, j := range param {
			params2[i] = j
		}
		params2[signKey] = a.signParams(apiStr, params2)

		param.Add("timestamp", utils.Int64ToStr(time.Now().Unix()))

		// 发起请求
		request, err := url.Parse(prodURL2 + "/" + apiStr + "?" + param.Encode())
		if err != nil {
			continue
		}

		resp, err1 := http.Get(request.String())
		body, err2 := ioutil.ReadAll(resp.Body)
		if err1 != nil || err2 != nil {
			continue
		}
		err = json.Unmarshal(body, &retVal)
		if err == nil {
			return retVal, nil
		}
	}
	return nil, errors.New("所有百度应用额度均用完")
}

// BatchCoordinateConvert 坐标转换
func (a *API) BatchCoordinateConvert(coords []*Coordinate, changeType string) (outCoords []*Coordinate, err error) {
	var coordsStrList []string
	for _, v := range coords {
		coordsStrList = append(coordsStrList, fmt.Sprintf("%.6f,%.6f", v.Lng, v.Lat))
	}

	params := url.Values{
		"coords": []string{strings.Join(coordsStrList, ";")},
		"model":  []string{changeType}, // 1:高德转百度 5:百度转高德
		//"from":   []string{utils.Int2Str(fromCoordSys)},
		//"to":     []string{utils.Int2Str(toCoordSys)},
	}

	result, err := a.AccessAPI2("geoconv/v2/", params)
	if err == nil {
		err = utils.Map2StructByJson(result[resultKey], &outCoords, false)
	}
	return outCoords, err
}

// DirectionLiteRide 获取骑行距离
func (a *API) DirectionLiteRide(coords []*Coordinate) (retVal interface{}, err error) {
	var (
		sCoords string
		uCoords string
		apiStr  = "directionlite/v1/riding"
	)
	sCoords = utils.Float64ToStr(coords[0].Lat) + "," + utils.Float64ToStr(coords[0].Lng)
	uCoords = utils.Float64ToStr(coords[1].Lat) + "," + utils.Float64ToStr(coords[1].Lng)

	param := url.Values{
		"origin":      []string{sCoords},
		"destination": []string{uCoords},
	}

	resp, err := a.AccessAPI2(apiStr, param)
	if err == nil {
		return resp[resultKey], nil
	}

	return nil, nil
}
