import logging
from uuid import uuid4
from datetime import datetime

from harvest.drivers.csv_driver import CsvDriver
from harvest.exception import DuplicatePlaceNameExistsError, MaxHierarchyExceededError, ParentPlaceNotFoundError

MAX_HIERARCHY_NUM = 5

class CsvOptimizer():
  items =None
  places = None
  targets = None
  project_id = None

  logger = logging.getLogger(__name__)
  logger.addHandler(logging.NullHandler())
  logger.setLevel(logging.DEBUG)

  def __init__(self, filename=None):
    if filename:
      self.open_csv(filename)

  def open_csv(self, filename):
    self.logger.info("input csv filename: {}".format(filename))
    csv = CsvDriver()
    self.items = csv.open_csv(filename)
    self.logger.info("loaded open csv contents: {}".format(self.items))

    self.places, self.targets = self.split_types()
    self.logger.info("loaded places after type split from csv file: {}".format(self.places))
    self.logger.info("loaded targets after type split from csv file: {}".format(self.places))

    dup = self.check_duplicate(self.places)
    if dup:
      self.logger.error("detected duplicate place names: {}".format(str(dup)))
      raise DuplicatePlaceNameExistsError(dup)

  def set_project_id(self, project_id):
    self.project_id = project_id

  def get_places(self):
    return [ item for item in self.items if item["type"] == "place" ]
    
  def get_targets(self):
    return [ item for item in self.items if item["type"] == "target" ]
    
  def split_types(self):
    return self.get_places(), self.get_targets()

  def get_root_places(self):
    return [ place for place in self.places if not place["parent"] ]

  def get_child_places(self):
    return [ place for place in self.places if place["parent"] ]

  def parent_sort(self, places):
    return sorted(places, key=lambda x: x["parent"])

  def check_duplicate(self, places):
    place_names = [ place["name"] for place in places ]
    return [ x for x in set(place_names) if place_names.count(x) > 1 ]

  def batch_gen_place_id(self):
    return list(map(lambda x: self.append_id(x, "place"), self.places))

  def batch_gen_target_id(self):
    return list(map(lambda x: self.append_id(x, "target"), self.targets))

  def batch_set_project_id(self, obj, project_id=None):
    if not project_id:
      project_id = self.project_id
    return list(map(lambda x: self.append_id(x, "project", project_id), obj))

  def append_id(self, obj, _type, _id=None):
    if not _id:
      _id = str(uuid4())
    obj.update({_type + "_id": _id})
    return obj

  def conv_list_to_dict(obj):
    return { item["name"]: dict(item) for item in obj }
  
  def batch_gen_parent_place_id(self, obj):
    if not self.project_id:
      raise ValueError

    rel = []
    for item in obj:
      if item["parent"]:
        parent = list(filter(lambda x: x["name"] == item["parent"], self.items))
        if parent and "place_id" in parent[0]:
          item.update({"parent_place_id": parent[0]["place_id"]})
        else:
          raise ParentPlaceNotFoundError()
      else:
          item.update({"parent_place_id": self.project_id})
      rel.append(item)
    return rel

  def batch_gen_photos_required_num(self, obj):
    ret = []
    for item in obj:
      item.update({"photos_required_num": 0})
      ret.append(item)
    return ret


  def get_place(self, place_id):
    for item in self.places:
      if place_id == item["place_id"]:
        return item

  def update_place(self, place):
    for i, item in enumerate(self.places):
      if place["place_id"] == item["place_id"]:
        self.places[i] = place
        return
    
  def gen_hierarchy(self, place):
    hierarchy = []
    place_id = place["parent_place_id"]
    if place_id == place["project_id"]:
      hierarchy.insert(0, place["project_id"])
    else:
      for i in range(MAX_HIERARCHY_NUM):
        parent = self.get_place(place_id)
        if parent:
          hierarchy.insert(0, parent["place_id"])
          place_id = parent["parent_place_id"]
        else:
          break
      else:
        raise MaxHierarchyExceededError()
    return hierarchy

  def increment_hierarchy(self, target):
    parent_place_id = target["parent_place_id"]
    parent = self.get_place(parent_place_id)
    if parent and parent["hierarchy"]:
      hierarchy = parent["hierarchy"].split("#")
      hierarchy.append(parent_place_id)
      if hierarchy[0] == parent["project_id"]:
        del hierarchy[0]
      for place_id in hierarchy:
        place = self.get_place(place_id)
        if place:
          place["photos_required_num"] += 1
          self.update_place(place)
    
  def optimize(self):
    if not self.places or not self.targets:
      raise ValueError
    
    self.logger.info("before sort places: {}".format(self.places))
    # parentでソートした一覧を生成
    sorted_places = self.parent_sort(self.places)
    self.logger.info("after sorted places: {}".format(sorted_places))

    # project_idを全体に足す
    self.places = self.batch_set_project_id(self.places, self.project_id)
    self.targets = self.batch_set_project_id(self.targets, self.project_id)
    
    # idを生成して足す
    self.places = self.batch_gen_place_id()
    self.targets = self.batch_gen_target_id()
    self.logger.info("append place_id to places: {}".format(self.places))
    self.logger.info("append target_ud to targes: {}".format(self.targets))

    # parent_place_idを皆に付与
    self.places = self.batch_gen_parent_place_id(self.places)
    self.targets = self.batch_gen_parent_place_id(self.targets)
    self.logger.info("calculate parent_place_id to places: {}".format(self.places))
    self.logger.info("calculate parent_place_id to targes: {}".format(self.targets))

    self.places = self.batch_gen_photos_required_num(self.places)
    
    optimized = []
    for place in self.places:
      hierarchy = self.gen_hierarchy(place)
      if hierarchy:
        place.update( {"hierarchy": "#".join(hierarchy)} )
      else:
        place.update( {"hierarchy": ""} )
      optimized.append(place)
    self.places = optimized

    for target in self.targets:
      self.increment_hierarchy(target)

    self.logger.info("generate hierarchy relationship that append to places: {}".format(optimized))
    return self.places, self.targets

