require 'stringio'

# Encapsulates all character formatting methods shared between style types
module RRTF::CharacterFormatting
  CHARACTER_ATTRIBUTES = {
    # toggable attributes
    "bold" => {
      "default" => nil,
      "to_rtf" => lambda{ |value, document| (value ? '\b' : '\b0') unless value.nil? }
    },
    "italic" => {
      "default" => nil,
      "to_rtf" => lambda{ |value, document| (value ? '\i' : '\i0') unless value.nil? }
    },
    "underline" => {
      "default" => nil,
      "dictionary" => {
        "SINGLE" => "",
        "DOUBLE" => "db",
        "THICK" => "th",
        "DASH" => "dash",
        "LONG_DASH" => "ldash",
        "DOT" => "d",
        "DASH_DOT" => "dashd",
        "DASH_DOT_DOT" => "dashdd",
        "WAVE" => 'wave',
        "THICK_DASH" => "thdash",
        "THICK_LONG_DASH" => "thldash",
        "THICK_DOT" => "thd",
        "THICK_DASH_DOT" => "thdashd",
        "THICK_DASH_DOT_DOT" => "thdashdd",
        "THICK_WAVE" => 'hwave',
        "DOUBLE_WAVE" => 'uldbwave'
      },
      "to_rtf" => lambda do |value, document|
        return if value.nil?
        case value
        when TrueClass
          '\ul'
        when FalseClass
          '\ulnone'
        when String
          "\\ul#{value}"
        end # case
      end
    },
    "uppercase" => {
      "default" => nil,
      "to_rtf" => lambda{ |value, document| (value ? '\caps' : '\caps0') unless value.nil? }
    },
    "superscript" => {
      "default" => nil,
      "to_rtf" => lambda{ |value, document| (value ? '\super' : '\super0') unless value.nil? }
    },
    "subscript" => {
      "default" => nil,
      "to_rtf" => lambda{ |value, document| (value ? '\sub' : '\sub0') unless value.nil? }
    },
    "strike" => {
      "default" => nil,
      "to_rtf" => lambda{ |value, document| (value ? '\strike' : '\strike0') unless value.nil? }
    },
    "emboss" => {
      "default" => nil,
      "to_rtf" => lambda{ |value, document| (value ? '\embo' : '\embo0') unless value.nil? }
    },
    "imprint" => {
      "default" => nil,
      "to_rtf" => lambda{ |value, document| (value ? '\impr' : '\impr0') unless value.nil? }
    },
    "outline" => {
      "default" => nil,
      "to_rtf" => lambda{ |value, document| (value ? '\outl' : '\outl0') unless value.nil? }
    },
    "hidden" => {
      "default" => nil,
      "to_rtf" => lambda{ |value, document| (value ? '\v' : '\v0') unless value.nil? }
    },
    "kerning" => {
      "default" => nil,
      "to_rtf" => lambda{ |value, document| (value.is_a?(Integer) ? "\\kerning#{value}" : '\kerning0') unless value.nil? }
    },
    # non-toggable attributes
    "character_spacing_offset" => {
      "default" => nil,
      "to_rtf" => lambda{ |value, document| "\\expnd#{value}" unless value.nil? }
    },
    "foreground_color" => {
      "default" => nil,
      "from_user" => lambda{ |value| RRTF::Colour.from_string(value) },
      "to_rtf" => lambda{ |value, document| "\\cf#{document.colours.index(value)}" unless value.nil? }
    },
    "background_color" => {
      "default" => nil,
      "from_user" => lambda{ |value| RRTF::Colour.from_string(value) },
      "to_rtf" => lambda{ |value, document| "\\cb#{document.colours.index(value)}" unless value.nil? }
    },
    "underline_color" => {
      "default" => nil,
      "from_user" => lambda{ |value| RRTF::Colour.from_string(value) },
      "to_rtf" => lambda{ |value, document| "\\ulc#{document.colours.index(value)}" unless value.nil? }
    },
    "font" => {
      "default" => nil,
      "from_user" => lambda{ |value| RRTF::Font.from_string(value) },
      "to_rtf" => lambda{ |value, document| "\\f#{document.fonts.index(value)}" unless value.nil? }
    },
    "font_size" => {
      "default" => nil,
      "to_rtf" => lambda{ |value, document| "\\fs#{value}" unless value.nil? }
    }
  }

  def self.included(base)
    # define accessors in base for paragraph attributes
    base.class_eval do
      CHARACTER_ATTRIBUTES.each do |key, options|
        attr_accessor :"#{key}"
      end # each
    end # class_eval
  end

  def initialize_character_formatting(options = {})
    # load default attribute values
    CHARACTER_ATTRIBUTES.each do |key, options|
      send("#{key}=", options["default"])
    end # each
    # overwrite default attribute values with given values
    set_character_formatting_from_hashmap(options)
  end

  def set_character_formatting_from_hashmap(hash)
    hash.each do |attribute, value|
      # skip unreconized attributes
      next unless(CHARACTER_ATTRIBUTES.keys.include?(attribute))
      # preprocess value if nessesary
      if CHARACTER_ATTRIBUTES[attribute].has_key?("from_user")
        value = CHARACTER_ATTRIBUTES[attribute]["from_user"].call(value)
      elsif CHARACTER_ATTRIBUTES[attribute].has_key?("dictionary") && value.is_a?(String)
        value = CHARACTER_ATTRIBUTES[attribute]["dictionary"][value]
      end # if
      # set attribute value
      send("#{attribute}=", value)
    end # each
  end

  def push_colours(colours)
    colours << foreground_color unless foreground_color.nil?
    colours << background_color unless background_color.nil?
    colours << underline_color unless underline_color.nil?
  end

  def push_fonts(fonts)
    fonts << font unless font.nil?
  end

  def character_formatting_to_rtf(document)
     text = StringIO.new

     # accumulate RTF representations of attributes
     CHARACTER_ATTRIBUTES.each do |key, options|
       text << options["to_rtf"].call(send(key), document) if options.has_key?("to_rtf")
     end # each

     text.string
  end
end # module CharacterFormatting

# Encapsulates all paragraph formatting methods shared between style types
module RRTF::ParagraphFormatting
  PARAGRAPH_ATTRIBUTES = {
    "justification" => {
      "default" => "l",
      "dictionary" => {
        "LEFT" => "l",
        "RIGHT" => "r",
        "CENTER" => "c",
        "CENTRE" => "c",
        "FULL" => "j"
      },
      "to_rtf" => lambda{ |value, document| "\\q#{value}" }
    },
    "left_indent" => {
      "default" => nil,
      "to_rtf" => lambda{ |value, document| "\\li#{value}" unless value.nil? }
    },
    "right_indent" => {
      "default" => nil,
      "to_rtf" => lambda{ |value, document| "\\ri#{value}" unless value.nil? }
    },
    "first_line_indent" => {
      "default" => nil,
      "to_rtf" => lambda{ |value, document| "\\fi#{value}" unless value.nil? }
    },
    "space_before" => {
      "default" => nil,
      "to_rtf" => lambda{ |value, document| "\\sb#{value}" unless value.nil? }
    },
    "space_after" => {
      "default" => nil,
      "to_rtf" => lambda{ |value, document| "\\sa#{value}" unless value.nil? }
    },
    "line_spacing" => {
      "default" => nil,
      "to_rtf" => lambda{ |value, document| "\\sl#{value}" unless value.nil? }
    },
    "widow_orphan_ctl" => {
      "default" => nil,
      "to_rtf" => lambda{ |value, document| (value ? "\\widctlpar" : "\\nowidctlpar") unless value.nil? }
    },
    "no_break" => {
      "default" => false,
      "to_rtf" => lambda{ |value, document| "\\keep" if value }
    },
    "no_break_with_next" => {
      "default" => false,
      "to_rtf" => lambda{ |value, document| "\\keepn" if value }
    },
    "hyphenate" => {
      "default" => nil,
      "to_rtf" => lambda{ |value, document| (value ? "\\hyphpar" : "\\hyphpar0") unless value.nil? }
    },
    "paragraph_flow" => {
      "default" => 'ltr',
      "dictionary" => {
        "LEFT_TO_RIGHT" => 'ltr',
        "RIGHT_TO_LEFT" => 'rtl'
      },
      "to_rtf" => lambda{ |value, document| "\\#{value}par" unless value.nil? }
    }
  }

  def self.included(base)
    # define accessors in base for paragraph attributes
    base.class_eval do
      PARAGRAPH_ATTRIBUTES.each do |key, options|
        attr_accessor :"#{key}"
      end # each
    end # class_eval
  end

  def initialize_paragraph_formatting(options = {})
    # load default attribute values
    PARAGRAPH_ATTRIBUTES.each do |key, options|
      send("#{key}=", options["default"])
    end # each
    # overwrite default attribute values with given values
    set_paragraph_formatting_from_hashmap(options)
  end

  def set_paragraph_formatting_from_hashmap(hash)
    hash.each do |attribute, value|
      # skip unreconized attributes
      next unless(PARAGRAPH_ATTRIBUTES.keys.include?(attribute))
      # preprocess value if nessesary
      if PARAGRAPH_ATTRIBUTES[attribute].has_key?("from_user")
        value = PARAGRAPH_ATTRIBUTES[attribute]["from_user"].call(value)
      elsif PARAGRAPH_ATTRIBUTES[attribute].has_key?("dictionary") && value.is_a?(String)
        value = PARAGRAPH_ATTRIBUTES[attribute]["dictionary"][value]
      end # if
      # set attribute value
      send("#{attribute}=", value)
    end # each
  end

  def paragraph_formatting_to_rtf(document)
    text = StringIO.new

    # accumulate RTF representations of paragraph attributes
    PARAGRAPH_ATTRIBUTES.each do |key, options|
      text << options["to_rtf"].call(send(key), document) if options.has_key?("to_rtf")
    end # each

    text.string
  end
end # module ParagraphFormatting