package mobvista.dmp.datasource.age_gender

import com.alibaba.fastjson.{JSON, JSONObject}
import com.google.common.collect.Sets
import mobvista.dmp.datasource.age.mapreduce.Util
import mobvista.dmp.util.MRUtils
import org.apache.commons.lang.StringUtils
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.sql.Row
import org.apache.spark.sql.types.{StringType, StructField, StructType}
import org.codehaus.jackson.map.ObjectMapper

import java.math.BigDecimal
import java.text.SimpleDateFormat
import java.util
import java.util.Random
import java.util.regex.Pattern
import scala.collection.JavaConverters._

/**
 * @package: mobvista.dmp.datasource.age
 * @author: wangjf
 * @create: 2018-09-13 16:01
 * */
object Logic {

  private val wellSplit = Pattern.compile("#")
  private val colonSplit = Pattern.compile(":")
  private val verticalLine = Pattern.compile("\\|")
  private val lineSplit: Pattern = Pattern.compile("-")
  private val `match`: Pattern = Pattern.compile("^0*-0*-0*-0*-0*$")
  private val regex: Pattern = Pattern.compile("""^\d+$""")
  private val ageRegex: Pattern = Pattern.compile("""\d{4}$""")
  private val matchingAgeSet: util.HashSet[String] = Sets.newHashSet("", "0", "1970", "GB", "null", "-")
  private val matchingGenderSet: util.HashSet[String] = Sets.newHashSet("f", "m")


  def pkg_keys(install_list: String): String = {
    val installMap = JSON.parse(install_list).asInstanceOf[java.util.Map[String, String]].asScala
    installMap.retain((k, _) => StringUtils.isNotBlank(k)).keySet.mkString("#")
  }

  def split_keys(install_list: String): String = {
    val set = new util.HashSet[String]()
    install_list.split("#").foreach(k => {
      if (StringUtils.isNotBlank(k)) {
        set.add(k)
      }
    })
    set.asScala.mkString("#")
  }

  //  dm_device_age schema
  def dm_device_age_schema: StructType = {
    StructType(StructField("device_id", StringType) ::
      StructField("device_type", StringType) ::
      StructField("package_names", StringType) ::
      StructField("update_date", StringType) ::
      StructField("age", StringType) ::
      StructField("tag", StringType) ::
      Nil)
  }

  //  merge device age
  def mergeAgePart(iter: Iterator[(String, Iterable[String])]): Iterator[Row] = {
    val res = new util.ArrayList[Row]()
    while (iter.hasNext) {
      val irs = iter.next
      val key = irs._1
      val value = irs._2
      val outList = new util.ArrayList[String]()
      val outputValue: StringBuilder = new StringBuilder
      for (vals <- value) {
        val array = MRUtils.SPLITTER.split(vals, -1)
        if (array(0).equals("A")) {
          outputValue.append("$")
          outputValue.append(array(1))
          outputValue.append("#")
          outputValue.append(array(2))
        } else {
          outList.add(vals)
        }
      }
      for (i <- 0 until outList.size()) {
        val array = MRUtils.SPLITTER.split(outList.get(i), -1)
        if (outputValue.isEmpty) {
          res.add(Row(key, array(1), "null", array(2)))
        } else {
          res.add(Row(key, array(1), outputValue.substring(1), array(2)))
        }
      }
    }
    res.asScala.iterator
  }

  //  merge install
  def mergeInstallPart(iter: Iterator[(String, Iterable[String])]): Iterator[(String, String)] = {
    val res = new util.ArrayList[(String, String)]()
    val sdf = new SimpleDateFormat("yyyy-MM-dd")
    while (iter.hasNext) {
      val irs = iter.next
      var new_date = ""
      val packageName = new StringBuilder("");
      val key = irs._1
      val value = irs._2
      val genderSet = Sets.newHashSet[String]
      for (vals <- value) {
        val arr = MRUtils.SPLITTER.split(vals)
        val package_names = wellSplit.split(arr(0), -1)
        for (pkg <- package_names) {
          genderSet.add(pkg)
        }
        if (!StringUtils.isNotBlank(new_date) || sdf.parse(arr(1)).getTime > sdf.parse(new_date).getTime) {
          new_date = arr(1)
        }
      }
      if (genderSet.size() >= 2) {
        val set = genderSet.iterator()
        while (set.hasNext) {
          val pkg = set.next
          packageName.append("#" + pkg)
        }
        res.add((key, MRUtils.JOINER.join("B", packageName.substring(1), new_date)))
      }
    }
    res.asScala.iterator
  }

  //  merge device gender
  def mergeGenderPart(iter: Iterator[(String, Iterable[String])]): Iterator[Row] = {
    val res = new util.ArrayList[Row]()
    while (iter.hasNext) {
      val irs = iter.next
      val device_id = MRUtils.SPLITTER.split(irs._1, -1)(0)
      val device_type = MRUtils.SPLITTER.split(irs._1, -1)(1)
      val value = irs._2
      val outList = new util.ArrayList[String]()
      val genderSet = Sets.newHashSet[String]
      for (vals <- value) {
        val array = MRUtils.SPLITTER.split(vals, -1)
        if (array(0).equals("A")) {
          genderSet.add(array(1))
        } else if (array(0).equals("B")) {
          outList.add(vals)
        }
      }
      for (i <- 0 until outList.size()) {
        val array = MRUtils.SPLITTER.split(outList.get(i), -1)
        if (genderSet.size() != 1) {
          res.add(Row(device_id, array(1), "null", device_type + "#" + array(2)))
        } else {
          var gender = ""
          val genderIter = genderSet.iterator
          while (genderIter.hasNext) {
            gender = genderIter.next
          }
          res.add(Row(device_id, array(1), gender, device_type + "#" + array(2)))
        }
      }
    }
    res.asScala.iterator
  }

  //  计算 device age
  def calcDeviceAgeLogicJson(rows: Iterator[Row], bPackageMap: Broadcast[scala.collection.Map[String, String]]): Iterator[DmpDeviceAge] = {
    import scala.collection.JavaConversions._
    rows.map(row => {
      val mergeAge = MergeCommon(row.getAs("device_id"), row.getAs("package_names"), row.getAs("label"), row.getAs("device_type"), row.getAs("update_date"))
      var age = ""
      var age_type = "unknown"
      if (StringUtils.isNotBlank(mergeAge.label) && !mergeAge.label.equals("null")) {
        val ageTags = mergeAge.label.split("\\$", -1)
        for (ageTag <- ageTags) {
          if (StringUtils.isNotBlank(ageTag)) {
            val ageForm = wellSplit.split(ageTag, -1)
            val generation = Util.getAge(ageForm(0).toInt)
            if (StringUtils.isNotBlank(generation)) {
              age = ageForm(0)
              age_type = "org"
            }
          }
        }
      }

      if (StringUtils.isBlank(age)) {
        val array = wellSplit.split(mergeAge.packaga_names, -1)
        var score: String = "-1.0"
        var num: Int = 0
        //  var tag: String = "unknown"
        val stage = new JSONObject()
        val stageNum = new JSONObject()
        val st = new JSONObject()
        var flag = true
        for (packageName <- array if flag) {
          if (bPackageMap.value.keySet.contains(packageName)) {
            val ageRatioSource = bPackageMap.value(packageName)
            val dictValue: Array[String] = MRUtils.SPLITTER.split(ageRatioSource)
            if (dictValue(1).equals("confirm")) {
              score = dictValue(0)
              age_type = dictValue(1)
              num = 1
              flag = false
            } else {
              num += 1
              val item = verticalLine.split(dictValue(0), -1)
              for (ageRatios <- item) {
                if (StringUtils.isNotBlank(ageRatios)) {
                  val generation = colonSplit.split(ageRatios, -1)(0).toInt
                  val ageRatio = colonSplit.split(ageRatios, -1)(1).toDouble
                  if (!stage.keySet.contains(generation.toString)) {
                    stage.put(generation.toString, ageRatio)
                    stageNum.put(generation.toString, 1)
                  } else {
                    val stageAgeRatio = stage(generation.toString)
                    val vals = stageAgeRatio.toString.toDouble + ageRatio
                    stage.put(generation.toString, vals)
                    stageNum.put(generation.toString, stageNum(generation.toString).toString.toInt + 1)
                  }
                }
              }
              score = ""
              age_type = "calc"
            }
          }
        }
        if (num == 1 && age_type.equals("confirm")) {
          score = "|" + score
        }
        if (num == 1 && age_type.equals("calc")) {
          for (entry <- stage.entrySet) {
            score = score + '|' + entry.getKey + ':' + entry.getValue
          }
        }
        if (num > 1) {
          for (entry <- stage.entrySet) {
            val gen = entry.getKey
            //  归一化处理 entry.getValue / num
            val rat = entry.getValue.toString.toDouble / stageNum(gen).toString.toInt
            st.put(gen, rat)
          }
          for (entry <- st.entrySet) {
            score = score + '|' + entry.getKey + ':' + entry.getValue
          }
        }

        val outMap = new JSONObject()
        outMap.put("1", 0.0)
        outMap.put("2", 0.0)
        outMap.put("3", 0.0)
        outMap.put("4", 0.0)
        outMap.put("5", 0.0)
        val scoreArr = score.split("\\|", -1)
        var ageRatioDouble = new BigDecimal(0) //小数转换
        for (ageRatio <- scoreArr) {
          if (!ageRatio.equals("") && !ageRatio.equals("-1.0")) {
            val tmpRatio = colonSplit.split(ageRatio, -1)
            val generation = Util.getAge(tmpRatio(0).toInt)
            if (StringUtils.isNotBlank(generation)) {
              ageRatioDouble = new BigDecimal(java.lang.Double.parseDouble(tmpRatio(1))).setScale(6, BigDecimal.ROUND_HALF_UP)
              outMap.put(tmpRatio(0), ageRatioDouble.doubleValue)
            }
          }
        }
        var tmp_max = 0.0
        for (age_k <- outMap.keys) {
          if (tmp_max < outMap.getDouble(age_k).doubleValue()) {
            tmp_max = outMap.getDouble(age_k).doubleValue()
            age = age_k
          }
        }
        //  val s = Json(DefaultFormats).write(mutable.ListMap(outMap.asInstanceOf[java.util.Map[String, Double]].asScala.toList.sortWith(_._2 > _._2): _*))
        //  s
        /*
        val ageMap = new JSONObject()
        if (!mergeAge.label.equals("null")) {
          val ageTags = mergeAge.label.split("\\$", -1)
          for (ageTag <- ageTags) {
            if (StringUtils.isNotBlank(ageTag)) {
              val ageForm = wellSplit.split(ageTag, -1)
              val generation = Util.getAge(ageForm(0).toInt)
              if (StringUtils.isNotBlank(generation)) {
                ageMap.put(generation, ageForm(1))
                age = ageForm(0)
                age_type = "org"
              }
            }
          }
        }
        */
      }
      val device_id = mergeAge.device_id
      val device_type = mergeAge.device_type
      /*
      val lastMap = new JSONObject()
      lastMap.put("age_and_source", ageMap)
      lastMap.put("age_and_proportion", outMap)
      val device_id = mergeAge.device_id
      val device_type = mergeAge.device_type
      val package_names = mergeAge.packaga_names.replace("#", ",")
      val update_date = mergeAge.update_date
      val age = lastMap.toString
      */
      //  DeviceAge(device_id, device_type, package_names, age, tag, update_date)

      DmpDeviceAge(device_id, device_type, age, age_type)
    })
  }

  def calcDeviceAgeLogic(rows: Iterator[Row], bPackageMap: Broadcast[util.Map[String, String]]): Iterator[DeviceAge] = {
    import scala.collection.JavaConversions._
    rows.map(row => {
      val mergeAge = MergeCommon(row.getAs("device_id"), row.getAs("package_names"), row.getAs("label"), row.getAs("device_type"), row.getAs("update_date"))
      val array = wellSplit.split(mergeAge.packaga_names, -1)
      var score: String = "-1.0"
      var num: Int = 0
      var tag: String = "unknown"
      val stage: java.util.Map[Integer, Double] = new java.util.HashMap[Integer, Double]
      val stageNum: java.util.Map[Integer, Integer] = new java.util.HashMap[Integer, Integer]
      val st: java.util.Map[Integer, Double] = new java.util.HashMap[Integer, Double]
      var flag = true
      for (packageName <- array if flag) {
        if (bPackageMap.value.keySet.contains(packageName)) {
          val ageRatioSource = bPackageMap.value(packageName)
          val dictValue: Array[String] = MRUtils.SPLITTER.split(ageRatioSource)
          if (dictValue(1).equals("confirm")) {
            score = dictValue(0)
            tag = dictValue(1)
            num = 1
            flag = false
          } else if (dictValue(1).equals("unbelievable")) {
            if (tag.equals("unknown")) {
              score = "-1.0"
              tag = dictValue(1)
            }
          } else {
            num += 1
            val item = verticalLine.split(dictValue(0), -1)
            for (ageRatios <- item) {
              if (StringUtils.isNotBlank(ageRatios)) {
                val generation = colonSplit.split(ageRatios, -1)(0).toInt
                val ageRatio = colonSplit.split(ageRatios, -1)(1).toDouble
                if (!stage.keySet.contains(generation)) {
                  stage.put(generation, ageRatio)
                  stageNum.put(generation, 1)
                } else {
                  val stageAgeRatio = stage(generation)
                  val vals = stageAgeRatio.toDouble + ageRatio
                  stage.put(generation, vals)
                  stageNum.put(generation, stageNum(generation) + 1)
                }
              }
            }
            score = ""
            tag = dictValue(1)
          }
        }
      }
      if (num == 1 && tag.equals("confirm")) {
        score = "|" + score
      }
      if (num == 1 && tag.equals("calc")) {
        for (entry <- stage.entrySet) {
          score = score + '|' + entry.getKey + ':' + entry.getValue
        }
      }
      if (num > 1) {
        for (entry <- stage.entrySet) {
          val gen = entry.getKey
          //  归一化处理 entry.getValue / num
          val rat = entry.getValue / stageNum(gen)
          st.put(gen, rat)
        }
        for (entry <- st.entrySet) {
          score = score + '|' + entry.getKey + ':' + entry.getValue
        }
      }

      val outMap: java.util.Map[String, java.lang.Double] = new java.util.HashMap[String, java.lang.Double]
      outMap.put("0-17", 0.0)
      outMap.put("18-24", 0.0)
      outMap.put("25-44", 0.0)
      outMap.put("45-59", 0.0)
      outMap.put("60+", 0.0)
      val scoreArr = score.split("\\|", -1)
      var ageRatioDouble = new BigDecimal(0) //小数转换
      for (ageRatio <- scoreArr) {
        if (!ageRatio.equals("") && !ageRatio.equals("-1.0")) {
          val tmpRatio = colonSplit.split(ageRatio, -1)
          val generation = Util.getAge(tmpRatio(0).toInt)
          if (StringUtils.isNotBlank(generation)) {
            ageRatioDouble = new BigDecimal(java.lang.Double.parseDouble(tmpRatio(1))).setScale(6, BigDecimal.ROUND_HALF_UP)
            outMap.put(generation, ageRatioDouble.doubleValue)
          }
        } else if (ageRatio.equals("-1.0")) {
          outMap.clear()
          outMap.put("unbelievable", -1.0)
        }
      }
      val ageMap: java.util.Map[String, String] = new java.util.HashMap[String, String]
      if (!mergeAge.label.equals("null")) {
        val ageTags = mergeAge.label.split("\\$", -1)
        for (ageTag <- ageTags) {
          if (StringUtils.isNotBlank(ageTag)) {
            val ageForm = wellSplit.split(ageTag, -1)
            val generation = Util.getAge(ageForm(0).toInt)
            if (StringUtils.isNotBlank(generation))
              ageMap.put(generation, ageForm(1))
          }
        }
      } else {
        ageMap.put("null", "null")
      }
      val objectMapper: ObjectMapper = new ObjectMapper //转换器
      val lastMap: java.util.Map[String, java.lang.Object] = new java.util.LinkedHashMap[String, java.lang.Object]
      lastMap.put("age_and_source", ageMap)
      lastMap.put("age_and_proportion", outMap)
      val device_id = mergeAge.device_id
      val device_type = mergeAge.device_type
      val package_names = mergeAge.packaga_names.replace("#", ",")
      val update_date = mergeAge.update_date
      val age = objectMapper.writeValueAsString(lastMap)
      DeviceAge(device_id, device_type, package_names, age, tag, update_date)
    })
  }

  //  计算 device gender
  def calcDeviceGenderLogicJson(rows: Iterator[Row], bPackageMap: Broadcast[scala.collection.Map[String, String]]): Iterator[DmpDeviceGender] = {
    rows.map(row => {
      val mergeGender = MergeCommon(row.getAs("device_id"), row.getAs("package_names"), row.getAs("label"), row.getAs("device_type"), row.getAs("update_date"))

      var gender = ""
      var gender_type = ""
      var ratio: Double = -1
      if (StringUtils.isNotBlank(mergeGender.label) && !mergeGender.label.equals("null")) {
        val genderTags = mergeGender.label.split("\\$", -1)
        var flag = true
        for (genderTag <- genderTags if flag) {
          if (StringUtils.isNotBlank(genderTag)) {
            val genderForm = wellSplit.split(genderTag, -1)
            if (StringUtils.isNotBlank(genderForm(0))) {
              if (genderForm(0).toLowerCase.equals("f")) {
                flag = false
              }
              gender = genderForm(0)
              gender_type = "org"
            }
          }
        }
      } else {
        val array = wellSplit.split(mergeGender.packaga_names, -1)
        var score: Double = 0.0
        var num: Int = 0
        //  var tag: String = "unknown"
        //  var label: String = ""
        var flag = true
        for (packageName <- array if flag) {
          if (bPackageMap.value.keySet.contains(packageName)) {
            val ageRatioSource = bPackageMap.value(packageName)
            val dictValue: Array[String] = MRUtils.SPLITTER.split(ageRatioSource, -1)
            if (dictValue(1).equals("confirm")) {
              score = dictValue(0).toDouble
              gender_type = "confirm"
              num = 1
              flag = false
            } else {
              num += 1
              score += dictValue(0).toDouble
              gender_type = "calc"
            }
          }
        }

        if (num >= 1) {
          ratio = score / num
        }
        if (gender_type.equals("confirm") && ratio >= 0.5) {
          ratio = ratio * randFloat(0.575, 0.580)
        }
        if (ratio == -1) {
          gender = ""
        } else if (gender_type.equals("calc")) {
          if (ratio >= 0.425) {
            gender = "m"
          } else {
            gender = "f"
          }
        } else {
          if (ratio >= 0.5) {
            gender = "m"
          } else {
            gender = "f"
          }
        }
      }
      if (StringUtils.isBlank(gender_type)) {
        gender_type = "calc"
      }

      val device_id = mergeGender.device_id
      val device_type = mergeGender.device_type
      //  val update_date = mergeGender.update_date
      //  DeviceGender(device_id, device_type, package_names, mergeGender.label, label, ratio.toString, tag, update_date)
      DmpDeviceGender(device_id, device_type, gender, gender_type)
    })
  }

  def randFloat(min: Double, max: Double): Double = {
    val rand = new Random()
    val res = min + rand.nextDouble() * (max - min)
    res
  }

  def check_deviceId(device_id: String): Boolean = {
    StringUtils.isNotBlank(device_id) && lineSplit.split(device_id, -1).length == 5 && !`match`.matcher(device_id)
      .matches()
  }

  def check_birthday(now: Int, birthday: String): Boolean = {
    StringUtils.isNotBlank(birthday) && !matchingAgeSet.contains(birthday) && ageRegex.matcher(birthday).matches() && (now - Integer.parseInt(birthday)) > 0 &&
      (now - Integer.parseInt(birthday)) < 100
  }

  def check_gender(gender: String): Boolean = {
    StringUtils.isNotBlank(gender) && matchingGenderSet.contains(gender.toLowerCase)
  }

  def calcLabel(now: Int, birthday: String): Int = {
    val age = now - Integer.parseInt(birthday)
    var label = 0
    if (age <= 17) label = 1
    else if (age >= 18 && age <= 24) label = 2
    else if (age >= 25 && age <= 44) label = 3
    else if (age >= 45 && age <= 59) label = 4
    else if (age >= 60) label = 5
    label
  }
}
