import gnoetics
import random, string

import template

class Fragment:

    def __init__(self, is_break=0, syllables=0,
                 iambs=0, meter=None, rhyme=None,
                 is_start=0, is_stop=0):

        if iambs > 0:
            meter = "u-" * iambs

        if meter and not syllables:
            syllables = len(meter)

        self.__is_break     = is_break
        self.__syllables    = syllables
        self.__meter        = meter
        self.__rhyme_key    = rhyme
        self.__rhymes_with  = None
        self.__token        = None
        self.__metric_error = 0

        if is_start:
            self.__token = gnoetics.token_get_start()
        elif is_stop:
            self.__token = gnoetics.token_get_stop()
            

    def is_bound(self):
        return self.__token is not None

    def is_start(self):
        return self.__token and self.__token.is_start()

    def is_stop(self):
        return self.__token and self.__token.is_stop()

    def is_start_or_stop(self):
        return self.__token and \
               (self.__token.is_start() or self.__token.is_stop())

    def is_break(self):
        return self.__is_break

    def is_text(self):
        return self.is_bound() and not self.is_start_or_stop()

    def get_priority(self):
        if self.is_bound() or self.is_break():
            return 0
        if self.__rhyme_key:
            return 100
        return 10

    def get_syllables(self):
        return self.__syllables

    def get_meter(self):
        return self.__meter

    def get_rhyme_key(self):
        return self.__rhyme_key

    def get_rhymes_with(self):
        return self.__rhymes_with

    def get_token(self):
        return self.__token

    def get_metric_error(self):
        return self.__metric_error

    def bind(self, tok):
        assert tok is None or self.__token is None
        self.__token = tok
        self.__metric_error = 0
        if tok and tok.meter() and self.__meter:
            self.__metric_error = gnoetics.metric_error_left(tok.meter(),
                                                             self.__meter)

    def __copy(self):

        cpy = Fragment()
        
        cpy.__is_break    = self.__is_break
        cpy.__syllables   = self.__syllables
        cpy.__meter       = self.__meter
        cpy.__rhyme_key   = self.__rhyme_key
        cpy.__rhymes_with = self.__rhymes_with
        cpy.__token       = self.__token

        return cpy
    

    # n = number of syllables in left part of split
    def __split(self, n):
        assert 0 <= n <= self.__syllables
        assert not self.is_break()
        assert not self.is_bound()

        L = Fragment()
        L.__syllables = n
        if self.__meter:
            L.__meter = self.__meter[:n]

        R = Fragment()
        R.__syllables = self.__syllables - n
        if self.__meter:
            R.__meter = self.__meter[n:]

        return L, R

    def evolve(self, markov, tpl, pos):

        replace = []
        broadcast_rhyme = 0
        choice = None
        left_rooted = 1

        def closest_token(delta):
            i = pos + delta
            while 0 <= i < tpl.length():
                f = tpl.get(i)
                if not f.is_break():
                    return (f.is_bound() and f.get_token()) or None
                i += delta

        def distance_to_start_or_stop(delta):
            i = pos + delta
            distance = 0
            while 0 <= i < tpl.length():
                f = tpl.get(i)
                if not f.is_break():
                    if f.is_start_or_stop():
                        return distance
                    elif f.is_bound():
                        distance += 1
                    else:
                        return 666
                i += delta
            return distance

        prev_is = closest_token(-1)
        next_is = closest_token(+1)
        if next_is and not prev_is:
            left_rooted = 0

        if self.get_rhymes_with() or self.get_rhyme_key():
            left_rooted = 0

        if self.get_rhyme_key() and not self.get_rhymes_with():
            broadcast_rhyme = 1

        dist_to_start = distance_to_start_or_stop(-1)
        dist_to_stop  = distance_to_start_or_stop(+1)

        # Assemble our filter

        filter = {}

        filter["prev_is"]            = prev_is
        filter["next_is"]            = next_is
        filter["left_rooted"]        = left_rooted
        filter["no_start"]           = dist_to_stop < 4
        filter["no_stop"]            = dist_to_start < 4
        filter["rhymes_with"]        = self.get_rhymes_with()
        filter["minimum_rhyme_type"] = 3
        filter["min_syllables"]      = 0
        filter["max_syllables"]      = self.get_syllables()
        
        num_choices = 1
        if self.get_meter():
            num_choices = 100

        choices, fc, ufc = markov.choose_many(num_choices, filter)

        # Damn
        if not choices:
            return None

        # Pick out a token that minimizes the metric error
        if self.get_meter() and len(choices) > 1:

            choice_err = 1000
            for c in choices:
                if c.meter():
                    if left_rooted:
                        err = gnoetics.metric_error_left(c.meter(),
                                                         self.get_meter())
                    else:
                        err = gnoetics.metric_error_right(c.meter(),
                                                          self.get_meter())

                    if err < choice_err:
                        choice = c
                        choice_err = err
                        if err == 0:
                            break

        else:
            choice = choices[0]


        # This is just paranoid
        if not choice:
            return None

        # Construct the fragment(s) that will replace the current
        # one in the new, evolved template.

        syl = choice.syllables()
        
        if self.get_syllables() == syl:
            f = self.__copy()
            f.bind(choice)
            replace = [f]
        else:
            if left_rooted:
                ss = syl
            else:
                ss = self.get_syllables() - syl

            L, R = self.__split(ss)

            if left_rooted:
                L.bind(choice)
            else:
                R.bind(choice)

            replace = [L, R]
            

        # Assemble the new template
        
        new_template = template.Template()
        for i in xrange(tpl.length()):
            if i != pos:
                f = tpl.get(i)
                new_template.append(f.__copy())
            else:
                for f in replace:
                    new_template.append(f)


        # Inform other fragments about rhyme requirements
        
        if choice and broadcast_rhyme:
            for i in xrange(new_template.length()):
                f = new_template.get(i)
                if f.get_rhyme_key() == self.get_rhyme_key():
                    f.__rhymes_with = choice

        return new_template



# class Fragment:

#     def __init__(self, priority=0, complete=0):
#         self.__priority = priority
#         self.__complete = complete

#     def get_priority(self):
#         return self.__priority

#     def is_complete(self):
#         return self.__complete

#     def is_ready(self, template, i):
#         return 1

#     def to_string(self):
#         assert 0

#     def clone(self):
#         assert 0

#     def evolve(self, markov, template, i):
#         assert 0


# class Token(Fragment):

#     def __init__(self, token, meter=""):
#         Fragment.__init__(self, complete=1)
#         self.__token = token
#         self.__meter = meter
#         self.__token_meter = ""

#     def to_string(self):
#         return self.__token.to_string()

#     def clone(self):
#         return Token(self.__token, meter=self.__meter)

#     def get_token(self):
#         return self.__token

#     def get_meter(self):
#         return self.__meter

#     def get_token_meter(self):
#         if not self.__token_meter:
#             self.__token_meter = self.__token.meter()
#         return self.__token_meter

#     def get_metric_error(self):
#         if not self.__meter:
#             return 0
#         return gnoetics.metric_error_left(self.__meter,
#                                           self.get_token_meter())


# class Start(Token):

#     def __init__(self):
#         Token.__init__(self, gnoetics.token_get_start())


# class Stop(Token):

#     def __init__(self):
#         Token.__init__(self, gnoetics.token_get_stop())


# class Break(Fragment):

#     def __init__(self):
#         Fragment.__init__(self, complete=1)

#     def clone(self):
#         return Break()

#     def to_string(self):
#         return "\n"


# class Language(Fragment):

#     def __init__(self,
#                  syllables=-1, meter="", iambs=0,
#                  rhyme=""):

#         if rhyme:
#             priority = 100
#         else:
#             priority = 0
            
#         Fragment.__init__(self, priority=priority)

#         if iambs > 0:
#             meter = "u-" * iambs
#         self.__meter = meter
#         if meter:
#             self.__syllables = len(meter)
#         else:
#             assert syllables > 0
#             self.__syllables = syllables

#         self.__rhyme_key = rhyme
#         self.__rhymes_with = None

#     def get_syllables(self):
#         return self.__syllables

#     def get_meter(self):
#         return self.__meter

#     def to_string(self):
#         L = []
#         L.append("syl=%d" % self.__syllables)
#         if self.__meter:
#             L.append("meter=\"%s\"" % self.__meter)
#         if self.__rhyme_key:
#             L.append("rhyme=%s" % self.__rhyme_key)
#         return "[language %s]" % string.join(L)

#     def clone(self):
#         x = Language(syllables=self.__syllables,
#                      meter=self.__meter,
#                      rhyme=self.__rhyme_key)
#         x.__rhymes_with = self.__rhymes_with
#         return x

#     def evolve(self, markov, template, i):

#         # discard any zero-syllable language items
#         if self.__syllables == 0:
#             template.pop(i)
#             return 0

#         def closest_token(delta):
#             j = i + delta
#             while 0 <= j < len(template):
#                 if isinstance(template[j], Token):
#                     return template[j].get_token()
#                 elif isinstance(template[j], Language):
#                     return None
#                 j += delta
#             return None

#         def distance_to_sentence_boundary(delta):
#             j = i
#             count = 0
#             while 0 <= j < len(template):
#                 if isinstance(template[j], Token):
#                     t = template[j].get_token()
#                     if t.is_start() or t.is_stop():
#                         return count
#                     else:
#                         count += 1
#                 elif isinstance(template[j], Language):
#                     return 666
#                 j += delta
#             assert 0

#         dist_to_start = distance_to_sentence_boundary(-1)
#         dist_to_stop = distance_to_sentence_boundary(+1)

#         filter = {}
#         filter["no_start"] = dist_to_stop < 4
#         filter["no_stop"] = dist_to_start < 4 


#         begin_sentence = 0
#         end_sentence = 0


#         filter["prev_is"] = closest_token(-1)
#         filter["next_is"] = closest_token(+1)
#         filter["left_rooted"] = 1

#         if filter["next_is"] and not filter["prev_is"]:
#             filter["left_rooted"] = 0

#         if self.__rhymes_with:
#             filter["rhymes_with"] = self.__rhymes_with
#             filter["minimum_rhyme_type"] = 3
#             filter["left_rooted"] = 0
#         elif self.__rhyme_key:
#             filter["left_rooted"] = 0

#         filter["min_syllables"] = 0
#         filter["max_syllables"] = self.__syllables

#         if 0 and filter["next_is"] is None and random.random() < 0.1:
#             filter["next_is"] = gnoetics.token_get_stop()
#             end_sentence = 1

#         if 0 and filter["prev_is"] is None and random.random() < 0.1:
#             filter["prev_is"] = gnoetics.token_get_start()
#             begin_sentence = 1


#         num_choices = 1
#         if self.__meter:
#             num_choices = 100

#         choices, fc, ufc = markov.choose_many(num_choices, filter)

#         if not choices:
#             return 0

#         if self.__meter:
#             if choices[0].is_start() or choices[0].is_stop():
#                 choice = choices[0]
#             else:
#                 choice_err = 1000
#                 choice = None
#                 for c in choices:
#                     if c.meter():
#                         if filter["left_rooted"]:
#                             err = gnoetics.metric_error_left(c.meter(),
#                                                              self.__meter)
#                         else:
#                             err = gnoetics.metric_error_right(c.meter(),
#                                                               self.__meter)
                            
#                         if err < choice_err:
#                             choice = c
#                             choice_err = err
#                             if err == 0:
#                                 break

#         else:
#             choice = choices[0]

#         if not choice:
#             return 0
        
#         syl = choice.syllables()
#         diff = self.__syllables - syl

#         token_meter = None
#         lang_meter = None

#         template.pop(i)

#         if end_sentence:
#             template.insert(i, Token(gnoetics.token_get_stop()))
#             template.insert(i, Token(gnoetics.token_get_start()))

#         if filter["left_rooted"]:

#             if self.__meter:
#                 token_meter = self.__meter[:syl]
#                 lang_meter  = self.__meter[syl:]

#             if diff > 0:
#                 lang = Language(syllables=diff, meter=lang_meter)
#                 lang.__rhyme_key = self.__rhyme_key
#                 template.insert(i, lang)
#             template.insert(i, Token(choice, meter=token_meter))

#         else: # right-rooted

#             if self.__meter:
#                 token_meter = self.__meter[len(self.__meter)-syl:]
#                 lang_meter = self.__meter[:len(self.__meter)-syl]

#             template.insert(i, Token(choice, meter=token_meter))
#             if diff > 0:
#                 lang = Language(syllables=diff, meter=lang_meter)
#                 template.insert(i, lang)

#         if begin_sentence:
#             template.insert(i, Token(gnoetics.token_get_stop()))
#             template.insert(i, Token(gnoetics.token_get_start()))

#         if self.__rhyme_key and not self.__rhymes_with and \
#                (diff == 0 or not filter["left_rooted"]):
#             for i in xrange(len(template)):
#                 if isinstance(template[i], Language) \
#                    and self.__rhyme_key == template[i].__rhyme_key:
#                     template[i].__rhymes_with = choice

#         return 1




#     def evolve_old(self, markov, template, i):

#         if self.__syllables == 0:
#             template.pop(i)
#             return 1

#         def closest_token(delta):
#             j = i + delta
#             while 0 <= j < len(template):
#                 if isinstance(template[j], Token):
#                     return template[j].get_token()
#                 elif isinstance(template[j], Language):
#                     return None
#                 j += delta
#             return None

#         filter = {}
#         filter["prev_is"] = closest_token(-1)
#         filter["next_is"] = closest_token(+1)
#         filter["left_rooted"] = 1

#         if not filter["prev_is"]:
#             filter["left_rooted"] = 0

#         if self.__rhymes_with:
#             filter["rhymes_with"] = self.__rhymes_with
#             filter["minimum_rhyme_type"] = 3
#             filter["left_rooted"] = 0
#         elif self.__rhyme_key:
#             filter["left_rooted"] = 0
#             pass
#             # the rhyme type exists check is very expensive
#             #filter["rhyme_type_exists"] = 3
        
#         filter["min_syllables"] = 0
#         filter["max_syllables"] = self.__syllables

#         if self.__meter:
#             filter["target_meter"] = self.__meter


#         num_choices = 1
#         if self.__meter:
#             num_choices = 5
            

#         lang_meter = self.__meter

#         choices, fc, ufc = markov.choose_many(num_choices, filter)
#         if filter["prev_is"] and filter["next_is"]:
#             print fc, ufc
#         if choices:
#             if self.__meter:
#                 random.shuffle(choices)
#                 choice = choices[0]
#             else:
#                 choice = choices[0]

#             syl = choice.get_token().syllables()

#             token_meter = ""
#             if lang_meter:
#                 if filter["left_rooted"]:
#                     token_meter = lang_meter[:syl]
#                     lang_meter = lang_meter[syl:]
#                 else:
#                     token_meter = lang_meter[len(lang_meter)-syl:]
#                     lang_meter = lang_meter[:len(lang_meter)-syl]

#             new_token = Token(choice, meter=token_meter)
#             diff = self.__syllables - syl
#             if diff > 0:
#                 new_lang  = Language(syllables=diff,
#                                      meter=lang_meter)
#                 new_lang.__rhymes_with = self.__rhymes_with
#             else:
#                 new_lang = None

#             template.pop(i)
#             if filter["left_rooted"]:
#                 if new_lang:
#                     new_lang.__rhyme_key = self.__rhyme_key
#                     new_lang.__rhymes_with = self.__rhymes_with
#                     template.insert(i, new_lang)
#                 template.insert(i, new_token)
#             else:
#                 template.insert(i, new_token)
#                 if new_lang:
#                     template.insert(i, new_lang)

#             if self.__rhyme_key and not self.__rhymes_with and diff == 0:
#                 for i in xrange(len(template)):
#                     if isinstance(template[i], Language) \
#                            and self.__rhyme_key == template[i].__rhyme_key:
#                         template[i].__rhymes_with = choice

#             return 1

#         return 0

