from datetime import datetime, timedelta
from dateutil import parser as dateParser  # pip install python-datetutil
import re
import json
import pytz


class KakaoTalkParse():
    """
    Parses KakaoTalk Chat TXT Logs
    """

    def __init__(self):
        # set timezone
        self.srcTZ = pytz.timezone("Asia/Saigon")
        self.reportTz = pytz.timezone("Asia/Seoul")

        # 2020-01-01 00:00:00부터 현재까지
        self.reportStart = self.reportTz.localize(
            datetime(2020, 1, 1, 0, 0, 0))
        self.reportEnd = self.reportTz.localize(
            datetime.now())

        # global variables
        self._prev_speaker = None
        self._prev_msgTime = None
        self.contents = None

    def _parse_day(self, line):
        """
        한글 형식 날짜를 읽어서 datetime 형식으로 변환한다.
        """
        datestring = line.replace("년 ", "-").replace("월 ",
                                                     "-").replace("일", " ").split(" ")
        try:
            dateDate = datetime.strptime(datestring[1], "%Y-%m-%d").date()
        except:
            dateDate = datetime.strptime(datestring[0], "%Y-%m-%d").date()
        return dateDate

    def _generateDateIndex(self, contents):
        """
        파일을 전체 스캔하여 날짜별로 시작행과 종료행을 리턴한다
        """

        # 파일을 전체 스캔하여 날짜 형식이 있는 행을 저장한다.
        index = list()
        cnt = 1  # lineno starts from 1
        for line in contents:
            if line.startswith("--------------- ") and line.endswith(" ---------------\r\n"):
                index.append({'date': self._parse_day(line), 'lineStart': cnt})
            elif re.search(r'(^[0-9][0-9][0-9][0-9])년\ ([0-9]+)월\ ([0-9]+)일\ (.)요일\r\n$', line):
                index.append({'date': self._parse_day(line), 'lineStart': cnt})
            cnt += 1

        # index를 다시 한번 돌면서 종료라인을 구한다.
        for i in range(0, len(index)):
            # to avoid IndexError
            if len(index)-1 == i:
                lineEnd = cnt-1
            else:
                lineEnd = index[i+1]['lineStart']
            index[i]['lineEnd'] = lineEnd-1

        return index

    def _parseLine(self, baseDate, line):
        """
        line을 regex를 이용하여 이름, 시간, 내용으로 변환하고,
        baseDate를 기준으로 날짜+시간을 생성한다.

        Type1 Line:
        "[어피치] [오후 10:38] ㄷㄷㄷ"

        Type2 Line:
        "2019. 12. 22. 오후 2:22, 산들바람 : 컴퓨터 키보드 사이사이에 낀 먼지를 바람으로 청소하는 에어 스프레이 같은거 어디서 살 수 있을까요?"

        """
        speaker = None
        timeStr = None
        item = re.search(r'^\[(.*)\]\ \[(.*[0-9]+:[0-9][0-9])\]\ (.*)\n', line)
        message = None
        if item is not None:
            speaker = item.group(1)
            timeStr = item.group(2)
            message = item.group(3).replace("\r", "").replace("\n", "")

        if item is None:
            item = re.search(
                r'(^[0-9]+.*\ .*\.)\ (.*[0-9]), (.*) : (.*$)', line)
            if item is not None:
                speaker = item.group(3)
                timeStr = item.group(2)
                baseDate = dateParser.parse(item.group(1))
                message = item.group(4).replace("\r", "").replace("\n", "")

        if speaker is not None and timeStr is not None:
            # Convert timeStr to datetime
            if timeStr.startswith("오전"):
                timeStr = timeStr.replace("오전 ", "")+" AM"
            elif timeStr.startswith("오후"):
                timeStr = timeStr.replace("오후 ", "")+" PM"
            msgTime = dateParser.parse(baseDate.strftime(
                "%Y-%m-%d ")+timeStr)
            msgTime = self.srcTZ.localize(msgTime)

            self._prev_speaker = speaker
            self._prev_msgTime = msgTime
            return {"speaker": speaker, "msgTime": msgTime, "message": message}
        else:
            message = line.replace("\r", "").replace("\n", "")
            return {"type": "append", "speaker": self._prev_speaker, "msgTime": self._prev_msgTime, "message": message}

    def _generateMessageIndex(self, indexItem, contents):
        cnt = indexItem['lineStart']
        retval = []
        for line in contents:
            cnt += 1
            d = self._parseLine(indexItem['date'], line)
            d['lineno'] = cnt
            retval.append(d)
        return retval

    def open(self, filename):
        """
        Reads kakaotalk export text file.

        filename: filepath and name.
        eg: KakaoTalk_20210108_0846_18_555_group.txt
        """
        with open(filename, 'rt', newline='', encoding='utf-8') as fp:
            self.contents = fp.readlines()
        return self.contents

    def setReportRange(self, startTime, endTime):
        """
        Set date range
        """
        self.reportStart = self.reportTz.localize(
            dateParser.parse(startTime + " " + "00:00:00.000"))
        self.reportEnd = self.reportTz.localize(
            dateParser.parse(endTime + " " + "23:59:59.999"))

    def setSrcTZ(self, timezone="Asia/Seoul"):
        self.srcTZ = pytz.timezone(timezone)

    def setReportTz(self, timezone="Asia/Seoul"):
        self.reportTz = pytz.timezone(timezone)

    def parse(self, contents=None):
        """
        parse contents

        contents: all text data from file.
        """
        if contents == None:
            contents = self.contents
        indexItems = self._generateDateIndex(contents)
        data = list()

        for indexItem in indexItems:
            item = self._generateMessageIndex(
                indexItem, contents[indexItem['lineStart']:indexItem['lineEnd']])
            data = data + item

        return data

    def stats(self, chatData):
        retval = dict()
        """
        {
            'type': 'append',
            'speaker': '어피치', 
            'msgTime': datetime.datetime(2021, 1, 8, 7, 39), 
            'message': '사실적이고 관대하며 개방적이고 사람이나 사물에 대한 선입견이 별로 없다. 강한 현실 감각으로 타협책을 모색하고 문제를 해결하는 능력이 뛰어나다. 센스 있고 유머러스하다. 어디서든 적응을 잘 하고 친구와 어울리기를 좋아한다.', 
            'lineno': 24179
        }
        """

        for item in chatData:
            if item['msgTime'] is not None:
                # convert and replace to reportTz timezone
                item['msgTime'] = item['msgTime'].astimezone(self.reportTz)

            # report range condition
            if item['msgTime'] >= self.reportStart and item['msgTime'] <= self.reportEnd:
                # print(f"{self.reportStart} <= {item['msgTime']} >= {self.reportEnd}")

                speaker = item['speaker']
                if speaker not in retval.keys():
                    # initialize speaker
                    retval[speaker] = {
                        'totalCount': 0, 'characters': 0, 'urls': 0, 'photos': 0, 'files': 0, 'videos': 0, 'emoticons': 0, 'deletes': 0,
                        'activeHour': {
                            "00": 0,  "01": 0,  "02": 0,  "03": 0,  "04": 0,  "05": 0,
                            "06": 0,  "07": 0,  "08": 0,  "09": 0,  "10": 0,  "11": 0,
                            "12": 0,  "13": 0,  "14": 0,  "15": 0,  "16": 0,  "17": 0,
                            "18": 0,  "19": 0,  "20": 0,  "21": 0,  "22": 0,  "23": 0,
                        },
                        'activeWeek': {
                            "Mon": 0,
                            "Tue": 0,
                            "Wed": 0,
                            "Thu": 0,
                            "Fri": 0,
                            "Sat": 0,
                            "Sun": 0,
                        },
                        'activeMonth': {
                            "Jan": 0,  "Feb": 0,  "Mar": 0,  "Apr": 0,  "May": 0, "Jun": 0,
                            "Jul": 0,  "Aug": 0,  "Sep": 0,  "Oct": 0,  "Nov": 0, "Dec": 0,
                        },
                        'activeDays': {
                        }
                    }

                    activeDaysList = [self.reportStart + timedelta(days=x) for x in range(
                        0, (self.reportEnd-self.reportStart).days)]
                    activeDaysKeys = [x.strftime("%Y-%m-%d")
                                      for x in activeDaysList]
                    for activeDay in activeDaysKeys:
                        retval[speaker]['activeDays'][activeDay] = 0

                else:
                    # message counter
                    retval[speaker]['totalCount'] += 1
                    if item['message'].find("http://") >= 0 or item['message'].find("https://") >= 0:
                        retval[speaker]['urls'] += 1
                    elif item['message'] == "사진":
                        retval[speaker]['photos'] += 1
                    elif item['message'] == "동영상":
                        retval[speaker]['videos'] += 1
                    elif item['message'] == "이모티콘":
                        retval[speaker]['emoticons'] += 1
                    elif item['message'].startswith("파일: "):
                        retval[speaker]['files'] += 1
                    elif item['message'] == "삭제된 메시지입니다.":
                        retval[speaker]['deletes'] += 1
                    else:
                        retval[speaker]['characters'] += len(item['message'])

                    hourKey = item['msgTime'].strftime("%H")
                    retval[speaker]['activeHour'][hourKey] += 1

                    weekKey = item['msgTime'].strftime("%a")
                    retval[speaker]['activeWeek'][weekKey] += 1

                    monthKey = item['msgTime'].strftime("%b")
                    retval[speaker]['activeMonth'][monthKey] += 1

                    dayKey = item['msgTime'].strftime("%Y-%m-%d")
                    if dayKey not in retval[speaker]['activeDays'].keys():
                        retval[speaker]['activeDays'][dayKey] = 0
                    retval[speaker]['activeDays'][dayKey] += 1

        return retval

    def conv2chartJS(self, stats):
        retval = {
            'reportInfo': {
                'reportStart': self.reportStart.strftime("%Y-%m-%d"),
                'reportEnd': self.reportEnd.strftime("%Y-%m-%d"),
                'reportTZ': self.reportTz.zone,
                'reportSpeakers': len(stats.keys()),
            },
        }
        borderColors = [
            ("blue", "rgb(54, 162, 235)"),
            ("green", "rgb(75, 192, 192)"),
            ("grey", "rgb(201, 203, 207)"),
            ("orange", "rgb(255, 159, 64)"),
            ("purple", "rgb(153, 102, 255)"),
            ("red", "rgb(255, 99, 132)"),
            ("yellow", "rgb(255, 205, 86)"),
        ]
        backgroundColors = [
            ("blue", "rgba(54, 162, 235, 0.5)"),
            ("green", "rgba(75, 192, 192, 0.5)"),
            ("grey", "rgba(201, 203, 207, 0.5)"),
            ("orange", "rgba(255, 159, 64, 0.5)"),
            ("purple", "rgba(153, 102, 255, 0.5)"),
            ("red", "rgba(255, 99, 132, 0.5)"),
            ("yellow", "rgba(255, 205, 86, 0.5)")
        ]

        # return if nothing inside
        if stats == {}:
            return retval

        # totalCount, characters, urls, photos, files, videos, emoticons
        for category in ['totalCount', 'characters', 'urls', 'photos', 'files', 'videos', 'emoticons', 'deletes']:
            retval[category] = {
                'labels': [],
                'datasets': []
            }
            p = {
                'label': 'Dataset 1',
                'data': [],
                'backgroundColor': [],
                'borderColor': [],
                'borderWidth': 1,
            }

            counter = 0
            for speaker in stats.keys():
                colorIndex = counter % len(backgroundColors)
                counter += 1
                retval[category]['labels'].append(speaker)
                p['data'].append(stats[speaker][category])
                p['backgroundColor'].append(backgroundColors[colorIndex][1])
                p['borderColor'].append(borderColors[colorIndex][1])

            retval[category]['datasets'].append(p)

        # activeTime, activeWeek, activeMonth, activeDays
        for category in ['activeHour', 'activeWeek', 'activeMonth', 'activeDays']:
            retval[category] = {
                'labels': list(stats[list(stats.keys())[0]][category].keys()),
                'datasets': []
            }

            counter = 0
            for speaker in stats.keys():
                colorIndex = counter % len(backgroundColors)
                counter += 1
                p = {
                    'label': speaker,
                    'data': list(stats[speaker][category].values()),
                    'backgroundColor': backgroundColors[colorIndex][1],
                    'borderColor': borderColors[colorIndex][1],
                    'borderWidth': 1,
                }
                retval[category]['datasets'].append(p)
        return retval


if __name__ == '__main__':
    """Example Code"""
    ktparse = KakaoTalkParse()
    filename = "KakaoTalk_20210108_0846_18_555_group.txt"
    # filename = "Talk_2021.1.10 08_15-1.txt"

    ktparse.open(filename)
    data = ktparse.parse()
    # print(data)

    ktparse.setSrcTZ("Asia/Saigon")
    ktparse.setReportTz("Asia/Seoul")
    ktparse.setReportRange(startTime='2020-1-1', endTime='2020-12-31')

    stats = ktparse.stats(data)
    report = ktparse.conv2chartJS(stats)
    with open('report.json', 'w', encoding='utf-8') as fp:
        json.dump(report, fp, ensure_ascii=False, indent=0)

    print(json.dumps(report))
