from __future__ import print_function
import argparse
import json
import os
import string
import sys

class InstagramFilter:
    ''''''
    def __init__(self, options):
        ''''''
        self.options = options

        self.input_json = []
        self.keyword_set = []
        self.output_json = []
        self.selfie_set = set(['selfie', 'selfieee', 'selfiedrunk'])
        self.keyword_text = ''
        self.min_ratio = 999999  # for fuzzywuzzy
        self.max_ratio = 0       # ditto

        # Load keyword file
        with open (self.options.keyword_file) as f:
            keyword_list = f.read().splitlines()
            self.keyword_set = set(keyword_list)
            self.keyword_text = ' '.join(keyword_list)
            print('Imported keyword count: {}'.format(len(self.keyword_set)))
        if not keyword_list:
            raise Exception('No keyword_list')

    @classmethod
    def make_parser(cls):
        parser = argparse.ArgumentParser()
        parser.add_argument('-i', '--input_filename', required=True, \
            help='Input file (json)')
        parser.add_argument('-o', '--output_filename', help='Output filename (json)')
        parser.add_argument('-f', '--fuzzy', action='store_true', \
            help='use fuzzy text matching instead of exact')

        source_dir = os.path.abspath(os.path.dirname(__file__))
        default_keyword_file = os.path.join(source_dir, 'alcohol_keywords.txt')
        parser.add_argument('-k', '--keyword_file', default=default_keyword_file, \
            help='keyword file')

        parser.add_argument('-l', '--limit', type=int, \
            help='Limit number of items processed')
        parser.add_argument('-p', '--partial_ratio', action='store_true', \
            help='for fuzzy matching, use partial ratio instead of token set ratio')
        parser.add_argument('-r', '--fuzzy_ratio', type=int, default=80, \
            help='ratio to use with fuzzywuzzy text matching')
        parser.add_argument('-s', '--filter_selfie_tags', action='store_true', \
            help='exclude posts with no tags matching selfie words')
        parser.add_argument('-t', '--tag', help='tag to include in print messages')
        parser.add_argument('-v', '--verbose_level', type=int, default=0, \
            help='Verbose output level (default 0)')
        return parser

    def load_input(self):
        '''Loads and parses input file
        '''
        # As of March 2018, there are now 2 formats: json and csv


        if not self.options.input_filename:
            raise Exception('No input_filename specified in options')

        with open(self.options.input_filename) as f:
            text = f.read()
            self.input_json = json.loads(text)

        if not text:
            raise Exception('Input file missing, empty, or unreadable ({})' \
                .format(self.options.input_filename))
        print('Number of input posts: {}'.format(len(self.input_json)))


    def filter_post(self, post):
        '''Returns True if post matches the keyword set

        '''
        # Optionally EXCLUDE tags missing selfie words
        if self.options.filter_selfie_tags:
            tag_list = post.get('tags')
            tag_set = set(tag_list)
            match_set = self.selfie_set.intersection(tag_set)
            if not match_set:
                return False

        # Captions matching keywords
        caption_text = post.get('caption', {}).get('text')
        ascii_text = self._to_ascii_string(caption_text)
        if not ascii_text:
            return False

        ascii_set = set(ascii_text.split())
        match_set = ascii_set.intersection(self.keyword_set)
        if match_set and (self.options.verbose_level > 1):
            print('Matching keywords: {}'.format(match_set))
        return len(match_set) > 0

    def fuzzy_filter_post(self, post):
        if self.options.partial_ratio:
            fuzzy_calc = fuzz.partial_ratio
        else:
            fuzzy_calc = fuzz.token_set_ratio

        # Caption - which includes hash tags
        caption_text = post.get('caption', {}).get('text')
        if not caption_text:
            return False
        ascii_text = self._to_ascii_string(caption_text)
        #print('ascii text type {}'.format(type(ascii_text)))
        if not ascii_text:
            return False

        for keyword in self.keyword_set:
            ratio = fuzzy_calc(ascii_text, self.keyword_text)
            self._update_minmax_ratios(ratio)
            #print('ratio {} for ascii_text: {}'.format(ratio, ascii_text))
            if ratio >= self.options.fuzzy_ratio:
                return True

        # No ratio found above the threshold, so....
        return False

    def filter_posts(self):
        ''''''
        if self.options.fuzzy:
            self.min_ratio = 999999
            self.max_ratio = 0
        match_count = 0
        for i,post in enumerate(self.input_json):
            if self.options.limit and i > self.options.limit:
                break
            if (self.options.verbose_level > 0) and (i % 1000 == 0):
                log = '{}{} - {} matches'.format(self.options.tag, i, match_count)
                if self.options.fuzzy:
                    log = '{}  min {}  max {}'.format(log, self.min_ratio, self.max_ratio)
                print(log)
            if self.options.fuzzy:
                match = self.fuzzy_filter_post(post)
            else:
                match = self.filter_post(post)
            if match:
                match_count += 1
                # if self.options.verbose_level > 0:
                #     print('match {}'.format(post.get('id')))
            if match and self.options.output_filename:
                self.output_json.append(post)
        print('{}Number of input posts:    {:>10,}'.format(self.options.tag, len(self.input_json)))
        print('{}Number of matching posts: {:>10,}'.format(self.options.tag, match_count))
        if self.options.fuzzy:
            print('Min ratio {}'.format(self.min_ratio))
            print('Max ratio {}'.format(self.max_ratio))

    def write_output(self):
        ''''''
        with open(self.options.output_filename, 'w') as f:
            output_text = json.dumps(self.output_json)
            f.write(output_text)
            f.write('\n')
            print('Wrote {}'.format(self.options.output_filename))


    def _to_ascii_string(self, text):
        '''Also converts to lowercase and removes punctuation

        '''
        if not text:
            return None
        # Remove chars that are not ascii letters or whitespace
        filter_chars = set(string.ascii_letters + string.whitespace)
        filter_text = filter(lambda x: x in filter_chars, text)
        ascii_text = filter_text.encode('ascii', 'ignore')
        return ascii_text.lower()

    def _update_minmax_ratios(self, ratio):
        if ratio < self.min_ratio:
            self.min_ratio = ratio
        elif ratio > self.max_ratio:
            self.max_ratio = ratio


if __name__ == '__main__':
    parser = InstagramFilter.make_parser()
    options = parser.parse_args()

    print(options)
    ig_filter = InstagramFilter(options)

    if options.fuzzy:
        from fuzzywuzzy import fuzz

    print('Loading input file {}'.format(options.input_filename))
    ig_filter.load_input()

    print('Running filter')
    ig_filter.filter_posts()

    if options.output_filename:
        ig_filter.write_output()
