<commit_before>//
//  Appearance.swift
//  LeeGo
//
//  Created by <NAME> on 13/03/16.
//  Copyright © 2016 <NAME>. All rights reserved.
//

import Foundation

public typealias Attributes = [String: AnyObject]

/// Represent an appearance property of a UIView or it's subclass.
/// For all supported Appearance: 
/// 
/// [Please find the reference](https://github.com/wangshengjia/LeeGo/blob/master/Docs/Appearance.md)
public enum Appearance {
    // UIView
    case userInteractionEnabled(Bool), translatesAutoresizingMaskIntoConstraints(Bool), backgroundColor(UIColor), tintColor(UIColor), tintAdjustmentMode(UIViewTintAdjustmentMode), cornerRadius(CGFloat), borderWidth(CGFloat), borderColor(UIColor), multipleTouchEnabled(Bool), exclusiveTouch(Bool), clipsToBounds(Bool), alpha(CGFloat), opaque(Bool), clearsContextBeforeDrawing(Bool), hidden(Bool), contentMode(UIViewContentMode)

    // UIControl
    case enabled(Bool), selected(Bool), highlighted(Bool)

    // UILabel & UITextView & UITextField
    case font(UIFont), textColor(UIColor), textAlignment(NSTextAlignment), numberOfLines(Int), lineBreakMode(NSLineBreakMode), selectedRange(NSRange), editable(Bool), selectable(Bool), dataDetectorTypes(UIDataDetectorTypes), allowsEditingTextAttributes(Bool), clearsOnInsertion(Bool), textContainerInset(UIEdgeInsets), linkTextAttributes(Attributes), lineFragmentPadding(CGFloat), minimumScaleFactor(CGFloat), adjustsFontSizeToFitWidth(Bool), baselineAdjustment(UIBaselineAdjustment), shadowColor(UIColor), shadowOffset(CGSize), highlightedTextColor(UIColor)

    case attributedText([Attributes]) // TODO: handle also NSParagrapheStyle

    case text(String), borderStyle(UITextBorderStyle), defaultTextAttributes(Attributes), placeholder(String), clearsOnBeginEditing(Bool), background(UIImage), disabledBackground(UIImage), typingAttributes(Attributes), clearButtonMode(UITextFieldViewMode)

    // UIButton
    case buttonType(UIButtonType), buttonTitle(String, UIControlState), buttonTitleColor(UIColor, UIControlState), buttonTitleShadowColor(UIColor, UIControlState), buttonImage(UIImage, UIControlState), buttonBackgroundImage(UIImage, UIControlState), buttonAttributedTitle([Attributes], UIControlState), contentEdgeInsets(UIEdgeInsets), titleEdgeInsets(UIEdgeInsets), reversesTitleShadowWhenHighlighted(Bool), imageEdgeInsets(UIEdgeInsets), adjustsImageWhenHighlighted(Bool), adjustsImageWhenDisabled(Bool), showsTouchWhenHighlighted(Bool)

    // UIImageView
    case ratio(CGFloat)

    // UIScrollView
    case scrollEnabled(Bool), contentOffset(CGPoint), contentSize(CGSize), contentInset(UIEdgeInsets), directionalLockEnabled(Bool), bounces(Bool), alwaysBounceVertical(Bool), alwaysBounceHorizontal(Bool), pagingEnabled(Bool), showsHorizontalScrollIndicator(Bool), showsVerticalScrollIndicator(Bool), scrollIndicatorInsets(UIEdgeInsets), indicatorStyle(UIScrollViewIndicatorStyle), decelerationRate(CGFloat), delaysContentTouches(Bool), canCancelContentTouches(Bool), minimumZoomScale(CGFloat), maximumZoomScale(CGFloat), zoomScale(CGFloat), bouncesZoom(Bool), scrollsToTop(Bool), keyboardDismissMode(UIScrollViewKeyboardDismissMode)

    // ...

    // Custom
    case custom([String: AnyObject])
    case none
}

extension Appearance: Hashable {
    /// Same name, same hash value
    public var hashValue: Int {
        return asString().hashValue
    }
}

///  Equatable
///
///  - parameter lhs: an `Appearance` instance
///  - parameter rhs: anther `Appearance` instance
///
///  - returns: true if they have same name, even they have different associated value.
///
///  Ex:
///  ``` 
///  textColor(UIColor.whiteColor()) == textColor(UIColor.blackColor()) 
///  ```
public func ==(lhs: Appearance, rhs: Appearance) -> Bool {
    return lhs.asString() == rhs.asString()
}

extension Appearance {
    internal func apply<View: UIView>(to targetView: View, useDefaultValue: Bool = false) {

        switch (self, targetView) {

        // UIView
        case (let .backgroundColor(color), _):
            targetView.backgroundColor = !useDefaultValue ? color : nil
        case (let .userInteractionEnabled(userInteractionEnabled), _):
            targetView.isUserInteractionEnabled = !useDefaultValue ? userInteractionEnabled : !((targetView is UILabel) || (targetView is UIImageView))
        case (let .translatesAutoresizingMaskIntoConstraints(should), _):
            targetView.translatesAutoresizingMaskIntoConstraints = !useDefaultValue ? should : false
        case (let .tintColor(color), _):
            targetView.tintColor = !useDefaultValue ? color : nil
        case (let .tintAdjustmentMode(mode), _):
            targetView.tintAdjustmentMode = !useDefaultValue ? mode : .automatic
        case (let .cornerRadius(radius), _):
            targetView.layer.cornerRadius = !useDefaultValue ? radius : 0
        case (let .borderWidth(borderWidth), _):
            targetView.layer.borderWidth = !useDefaultValue ? borderWidth : 0
        case (let .borderColor(borderColor), _):
            targetView.layer.borderColor = !useDefaultValue ? borderColor.cgColor : nil
        case (let .multipleTouchEnabled(multipleTouchEnabled), _):
            targetView.isMultipleTouchEnabled = !useDefaultValue ? multipleTouchEnabled : false
        case (let .exclusiveTouch(exclusiveTouch), _):
            targetView.isExclusiveTouch = !useDefaultValue ? exclusiveTouch : false
        case (let .clipsToBounds(clipsToBounds), _):
            targetView.clipsToBounds = !useDefaultValue ? clipsToBounds : false
        case (let .alpha(alpha), _):
            targetView.alpha = !useDefaultValue ? alpha : 1.0
        case (let .opaque(opaque), _):
            targetView.isOpaque = !useDefaultValue ? opaque : true
        case (let .hidden(hidden), _):
            targetView.isHidden = !useDefaultValue ? hidden : false
        case (let .clearsContextBeforeDrawing(clearsContextBeforeDrawing), _):
            targetView.clearsContextBeforeDrawing = !useDefaultValue ? clearsContextBeforeDrawing : true
        case (let .contentMode(contentMode), _):
            targetView.contentMode = !useDefaultValue ? contentMode : .scaleToFill

        // UIControl
        case (let .selected(selected), let control as UIControl):
            control.isSelected = !useDefaultValue ? selected : false

        // UILabel
        case (let .shadowColor(color), let label as UILabel):
            label.shadowColor = !useDefaultValue ? color : nil
        case (let .shadowOffset(size), let label as UILabel):
            label.shadowOffset = !useDefaultValue ? size : CGSize(width: 0, height: -1)
        case (let .highlightedTextColor(color), let label as UILabel):
            label.highlightedTextColor = !useDefaultValue ? color : nil
        case (let .minimumScaleFactor(factor), let label as UILabel):
            label.minimumScaleFactor = !useDefaultValue ? factor : 0.0
        case (let .baselineAdjustment(adjustment), let label as UILabel):
            label.baselineAdjustment = !useDefaultValue ? adjustment : .alignBaselines

        // UILabel & UITextField & UITextView
        case (let .enabled(enabled), let view):
            if let label = view as? UILabel {
                label.isEnabled = !useDefaultValue ? enabled : true
            } else if let control = view as? UIControl {
                control.isEnabled = !useDefaultValue ? enabled : true
            } else {
                warningUnknownAppearance(targetView)
            }
        case (let .highlighted(highlighted), let view):
            if let label = view as? UILabel {
                label.isHighlighted = !useDefaultValue ? highlighted : false
            } else if let control = view as? UIControl {
                control.isHighlighted = !useDefaultValue ? highlighted : false
            } else {
                warningUnknownAppearance(targetView)
            }
        case (let .adjustsFontSizeToFitWidth(should), let view):
            if let label = view as? UILabel {
                label.adjustsFontSizeToFitWidth = !useDefaultValue ? should : false
            } else if let textField = view as? UITextField {
                textField.adjustsFontSizeToFitWidth = !useDefaultValue ? should : false
            } else {
                warningUnknownAppearance(targetView)
            }
        case (let .font(font), let view):
            if let label = view as? UILabel {
                label.font = !useDefaultValue ? font : nil
            } else if let textField = view as? UITextField {
                textField.font = !useDefaultValue ? font : nil
            } else if let textView = view as? UITextView {
                textView.font = !useDefaultValue ? font : nil
            } else {
                warningUnknownAppearance(targetView)
            }
        case (let .textColor(color), let view):
            if let label = view as? UILabel {
                label.textColor = !useDefaultValue ? color : nil
            } else if let textField = view as? UITextField {
                textField.textColor = !useDefaultValue ? color : nil
            } else if let textView = view as? UITextView {
                textView.textColor = !useDefaultValue ? color : nil
            } else {
                warningUnknownAppearance(targetView)
            }
        case (let .textAlignment(align), let view):
            if let label = view as? UILabel {
                label.textAlignment = !useDefaultValue ? align : .left
            } else if let textField = view as? UITextField {
                textField.textAlignment = !useDefaultValue ? align : .left
            } else if let textView = view as? UITextView {
                textView.textAlignment = !useDefaultValue ? align : .left
            } else {
                warningUnknownAppearance(targetView)
            }
        case (let .numberOfLines(number), let view):
            if let label = view as? UILabel {
                label.numberOfLines = !useDefaultValue ? number : 1
            } else if let textView = view as? UITextView {
                textView.textContainer.maximumNumberOfLines = !useDefaultValue ? number : 0
            } else {
                warningUnknownAppearance(targetView)
            }
        case (let .text(text), let view):
            if let label = view as? UILabel {
                label.text = !useDefaultValue ? text : nil
            } else if let textField = view as? UITextField {
                textField.text = !useDefaultValue ? text : nil
            } else if let textView = view as? UITextView {
                textView.text = !useDefaultValue ? text : nil
            } else {
                warningUnknownAppearance(targetView)
            }
        case (let .attributedText(attributes), let view):
            if let label = view as? UILabel {
                label.lg_setAttributedString(with: !useDefaultValue ? attributes : [])
            } else if let textField = view as? UITextField {
                textField.lg_setAttributedString(with: !useDefaultValue ? attributes : [])
            } else if let textView = view as? UITextView {
                textView.lg_setAttributedString(with: !useDefaultValue ? attributes : [])
            } else {
                warningUnknownAppearance(targetView)
            }
        case (let .lineBreakMode(mode), let view):
            if let label = view as? UILabel {
                label.lineBreakMode = !useDefaultValue ? mode : .byWordWrapping
            } else if let textView = view as? UITextView {
                textView.textContainer.lineBreakMode = !useDefaultValue ? mode : .byWordWrapping
            } else {
                warningUnknownAppearance(targetView)
            }

        case (let .allowsEditingTextAttributes(allows), let view):
            if let textField = view as? UITextField {
                textField.allowsEditingTextAttributes = !useDefaultValue ? allows : false
            } else if let textView = view as? UITextView {
                textView.allowsEditingTextAttributes = !useDefaultValue ? allows : false
            } else {
                warningUnknownAppearance(targetView)
            }

        case (let .clearsOnInsertion(clearsOnInsertion), let view):
            if let textField = view as? UITextField {
                textField.clearsOnInsertion = !useDefaultValue ? clearsOnInsertion : false
            } else if let textView = view as? UITextView {
                textView.clearsOnInsertion = !useDefaultValue ? clearsOnInsertion : false
            } else {
                warningUnknownAppearance(targetView)
            }

        // UITextView
        case (let .selectedRange(range), let textView as UITextView):
            textView.selectedRange = !useDefaultValue ? range : NSRange(location: 0, length: 0)
        case (let .editable(editable), let textView as UITextView):
            textView.isEditable = !useDefaultValue ? editable : false
        case (let .selectable(selectable), let textView as UITextView):
            textView.isSelectable = !useDefaultValue ? selectable : true
        case (let .dataDetectorTypes(types), let textView as UITextView):
            textView.dataDetectorTypes = !useDefaultValue ? types : UIDataDetectorTypes()
        case (let .textContainerInset(inset), let textView as UITextView):
            textView.textContainerInset = !useDefaultValue ? inset : UIEdgeInsets.zero
        case (let .linkTextAttributes(attrs), let textView as UITextView):
            textView.linkTextAttributes = !useDefaultValue ? attrs : nil
        case (let .lineFragmentPadding(padding), let textView as UITextView):
            textView.textContainer.lineFragmentPadding = !useDefaultValue ? padding : 5

        // UITextField
        case (let .borderStyle(style), let textField as UITextField):
            textField.borderStyle = !useDefaultValue ? style : .none
        case (let .defaultTextAttributes(attributes), let textField as UITextField):
            textField.defaultTextAttributes = !useDefaultValue ? attributes : [:]
        case (let .placeholder(text), let textField as UITextField):
            textField.placeholder = !useDefaultValue ? text : nil
        case (let .clearsOnBeginEditing(should), let textField as UITextField):
            textField.clearsOnBeginEditing = !useDefaultValue ? should : false
        case (let .background(image), let textField as UITextField):
            textField.background = !useDefaultValue ? image : nil
        case (let .disabledBackground(image), let textField as UITextField):
            textField.disabledBackground = !useDefaultValue ? image : nil
        case (let .typingAttributes(attributes), let textField as UITextField):
            textField.typingAttributes = !useDefaultValue ? attributes : nil
        case (let .clearButtonMode(mode), let textField as UITextField):
            textField.clearButtonMode = !useDefaultValue ? mode : .never

        // UIButton
        case (let .buttonType(type), _ as UIButton):
            print("For the moment, LeeGo only support default button type. Can't handle \(type)")
            break
        case (let .buttonTitle(title, state), let button as UIButton):
            button.setTitle(!useDefaultValue ? title : nil, for: state)
        case (let .buttonTitleColor(color, state), let button as UIButton):
            button.setTitleColor(!useDefaultValue ? color : nil, for: state)
        case (let .buttonTitleShadowColor(color, state), let button as UIButton):
            button.setTitleShadowColor(!useDefaultValue ? color : nil, for: state)
        case (let .buttonImage(image, state), let button as UIButton):
            button.setImage(!useDefaultValue ? image : nil, for: state)
        case (let .buttonBackgroundImage(image, state), let button as UIButton):
            button.setBackgroundImage(!useDefaultValue ? image : nil, for: state)
        case (let .buttonAttributedTitle(attributes, state), let button as UIButton):
            button.lg_setAttributedButtonTitle(with: !useDefaultValue ? attributes : [], state: state)
        case (let .contentEdgeInsets(insets), let button as UIButton):
            button.contentEdgeInsets = !useDefaultValue ? insets : UIEdgeInsets.zero
        case (let .titleEdgeInsets(insets), let button as UIButton):
            button.titleEdgeInsets = !useDefaultValue ? insets : UIEdgeInsets.zero
        case (let .imageEdgeInsets(insets), let button as UIButton):
            button.imageEdgeInsets = !useDefaultValue ? insets : UIEdgeInsets.zero
        case (let .reversesTitleShadowWhenHighlighted(should), let button as UIButton):
            button.reversesTitleShadowWhenHighlighted = !useDefaultValue ? should : false
        case (let .adjustsImageWhenHighlighted(should), let button as UIButton):
            button.adjustsImageWhenHighlighted = !useDefaultValue ? should : true
        case (let .adjustsImageWhenDisabled(should), let button as UIButton):
            button.adjustsImageWhenDisabled = !useDefaultValue ? should : true
        case (let .showsTouchWhenHighlighted(should), let button as UIButton):
            button.showsTouchWhenHighlighted = !useDefaultValue ? should : false

        // UIImageView
        case (let .ratio(ratioValue), let image as UIImageView):

            image.removeConstraints(image.constraints.filter({ (constraint) -> Bool in
                if constraint.mode == .ratio {
                    return true
                }
                return false
            }))

            if !useDefaultValue {
                let constraint = NSLayoutConstraint(item: image, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: image, attribute: NSLayoutAttribute.height, multiplier: ratioValue, constant: 0)
                constraint.lg_setIdentifier(with: .ratio)
                constraint.priority = 990
                image.addConstraint(constraint)
            }

        // UIScrollView
        case (let .scrollEnabled(scrollEnabled), let scrollView as UIScrollView):
            scrollView.isScrollEnabled = !useDefaultValue ? scrollEnabled : true
        case (let .contentOffset(offset), let scrollView as UIScrollView):
            scrollView.contentOffset = !useDefaultValue ? offset : CGPoint.zero
        case (let .contentSize(size), let scrollView as UIScrollView):
            scrollView.contentSize = !useDefaultValue ? size : CGSize.zero
        case (let .contentInset(inset), let scrollView as UIScrollView):
            scrollView.contentInset = !useDefaultValue ? inset : UIEdgeInsets.zero
        case (let .directionalLockEnabled(enabled), let scrollView as UIScrollView):
            scrollView.isDirectionalLockEnabled = !useDefaultValue ? enabled : false
        case (let .bounces(bounces), let scrollView as UIScrollView):
            scrollView.bounces = !useDefaultValue ? bounces : true
        case (let .alwaysBounceVertical(alwaysBounceVertical), let scrollView as UIScrollView):
            scrollView.alwaysBounceVertical = !useDefaultValue ? alwaysBounceVertical : false
        case (let .alwaysBounceHorizontal(alwaysBounceHorizontal), let scrollView as UIScrollView):
            scrollView.alwaysBounceHorizontal = !useDefaultValue ? alwaysBounceHorizontal : false
        case (let .pagingEnabled(pagingEnabled), let scrollView as UIScrollView):
            scrollView.isPagingEnabled = !useDefaultValue ? pagingEnabled : false
        case (let .showsHorizontalScrollIndicator(show), let scrollView as UIScrollView):
            scrollView.showsHorizontalScrollIndicator = !useDefaultValue ? show : true
        case (let .showsVerticalScrollIndicator(show), let scrollView as UIScrollView):
            scrollView.showsVerticalScrollIndicator = !useDefaultValue ? show : true
        case (let .scrollIndicatorInsets(insets), let scrollView as UIScrollView):
            scrollView.scrollIndicatorInsets = !useDefaultValue ? insets : UIEdgeInsets.zero
        case (let .indicatorStyle(style), let scrollView as UIScrollView):
            scrollView.indicatorStyle = !useDefaultValue ? style : .default
        case (let .decelerationRate(rate), let scrollView as UIScrollView):
            scrollView.decelerationRate = !useDefaultValue ? rate : UIScrollViewDecelerationRateNormal
        case (let .delaysContentTouches(delaysContentTouches), let scrollView as UIScrollView):
            scrollView.delaysContentTouches = !useDefaultValue ? delaysContentTouches : true
        case (let .canCancelContentTouches(canCancelContentTouches), let scrollView as UIScrollView):
            scrollView.canCancelContentTouches = !useDefaultValue ? canCancelContentTouches : true
        case (let .minimumZoomScale(minimumZoomScale), let scrollView as UIScrollView):
            scrollView.minimumZoomScale = !useDefaultValue ? minimumZoomScale : 1.0
        case (let .maximumZoomScale(maximumZoomScale), let scrollView as UIScrollView):
            scrollView.maximumZoomScale = !useDefaultValue ? maximumZoomScale : 1.0
        case (let .zoomScale(zoomScale), let scrollView as UIScrollView):
            scrollView.zoomScale = !useDefaultValue ? zoomScale : 1.0
        case (let .bouncesZoom(bouncesZoom), let scrollView as UIScrollView):
            scrollView.bouncesZoom = !useDefaultValue ? bouncesZoom : true
        case (let .scrollsToTop(scrollsToTop), let scrollView as UIScrollView):
            scrollView.scrollsToTop = !useDefaultValue ? scrollsToTop : true
        case (let .keyboardDismissMode(keyboardDismissMode), let scrollView as UIScrollView):
            scrollView.keyboardDismissMode = !useDefaultValue ? keyboardDismissMode :.none

        // Custom
        case (let .custom(dictionary), _):
          print("jackie: \(targetView)")
            !useDefaultValue ? targetView.lg_setupCustomStyle(dictionary) : targetView.lg_removeCustomStyle(dictionary)

        default:
            warningUnknownAppearance(targetView)
            break
        }
    }

    private func warningUnknownAppearance<View: UIView>(_ targetView: View) {<commit_msg>chore: clean up<commit_after>//
//  Appearance.swift
//  LeeGo
//
//  Created by <NAME> on 13/03/16.
//  Copyright © 2016 <NAME>. All rights reserved.
//

import Foundation

public typealias Attributes = [String: AnyObject]

/// Represent an appearance property of a UIView or it's subclass.
/// For all supported Appearance: 
/// 
/// [Please find the reference](https://github.com/wangshengjia/LeeGo/blob/master/Docs/Appearance.md)
public enum Appearance {
    // UIView
    case userInteractionEnabled(Bool), translatesAutoresizingMaskIntoConstraints(Bool), backgroundColor(UIColor), tintColor(UIColor), tintAdjustmentMode(UIViewTintAdjustmentMode), cornerRadius(CGFloat), borderWidth(CGFloat), borderColor(UIColor), multipleTouchEnabled(Bool), exclusiveTouch(Bool), clipsToBounds(Bool), alpha(CGFloat), opaque(Bool), clearsContextBeforeDrawing(Bool), hidden(Bool), contentMode(UIViewContentMode)

    // UIControl
    case enabled(Bool), selected(Bool), highlighted(Bool)

    // UILabel & UITextView & UITextField
    case font(UIFont), textColor(UIColor), textAlignment(NSTextAlignment), numberOfLines(Int), lineBreakMode(NSLineBreakMode), selectedRange(NSRange), editable(Bool), selectable(Bool), dataDetectorTypes(UIDataDetectorTypes), allowsEditingTextAttributes(Bool), clearsOnInsertion(Bool), textContainerInset(UIEdgeInsets), linkTextAttributes(Attributes), lineFragmentPadding(CGFloat), minimumScaleFactor(CGFloat), adjustsFontSizeToFitWidth(Bool), baselineAdjustment(UIBaselineAdjustment), shadowColor(UIColor), shadowOffset(CGSize), highlightedTextColor(UIColor)

    case attributedText([Attributes]) // TODO: handle also NSParagrapheStyle

    case text(String), borderStyle(UITextBorderStyle), defaultTextAttributes(Attributes), placeholder(String), clearsOnBeginEditing(Bool), background(UIImage), disabledBackground(UIImage), typingAttributes(Attributes), clearButtonMode(UITextFieldViewMode)

    // UIButton
    case buttonType(UIButtonType), buttonTitle(String, UIControlState), buttonTitleColor(UIColor, UIControlState), buttonTitleShadowColor(UIColor, UIControlState), buttonImage(UIImage, UIControlState), buttonBackgroundImage(UIImage, UIControlState), buttonAttributedTitle([Attributes], UIControlState), contentEdgeInsets(UIEdgeInsets), titleEdgeInsets(UIEdgeInsets), reversesTitleShadowWhenHighlighted(Bool), imageEdgeInsets(UIEdgeInsets), adjustsImageWhenHighlighted(Bool), adjustsImageWhenDisabled(Bool), showsTouchWhenHighlighted(Bool)

    // UIImageView
    case ratio(CGFloat)

    // UIScrollView
    case scrollEnabled(Bool), contentOffset(CGPoint), contentSize(CGSize), contentInset(UIEdgeInsets), directionalLockEnabled(Bool), bounces(Bool), alwaysBounceVertical(Bool), alwaysBounceHorizontal(Bool), pagingEnabled(Bool), showsHorizontalScrollIndicator(Bool), showsVerticalScrollIndicator(Bool), scrollIndicatorInsets(UIEdgeInsets), indicatorStyle(UIScrollViewIndicatorStyle), decelerationRate(CGFloat), delaysContentTouches(Bool), canCancelContentTouches(Bool), minimumZoomScale(CGFloat), maximumZoomScale(CGFloat), zoomScale(CGFloat), bouncesZoom(Bool), scrollsToTop(Bool), keyboardDismissMode(UIScrollViewKeyboardDismissMode)

    // ...

    // Custom
    case custom([String: AnyObject])
    case none
}

extension Appearance: Hashable {
    /// Same name, same hash value
    public var hashValue: Int {
        return asString().hashValue
    }
}

///  Equatable
///
///  - parameter lhs: an `Appearance` instance
///  - parameter rhs: anther `Appearance` instance
///
///  - returns: true if they have same name, even they have different associated value.
///
///  Ex:
///  ``` 
///  textColor(UIColor.whiteColor()) == textColor(UIColor.blackColor()) 
///  ```
public func ==(lhs: Appearance, rhs: Appearance) -> Bool {
    return lhs.asString() == rhs.asString()
}

extension Appearance {
    internal func apply<View: UIView>(to targetView: View, useDefaultValue: Bool = false) {

        switch (self, targetView) {

        // UIView
        case (let .backgroundColor(color), _):
            targetView.backgroundColor = !useDefaultValue ? color : nil
        case (let .userInteractionEnabled(userInteractionEnabled), _):
            targetView.isUserInteractionEnabled = !useDefaultValue ? userInteractionEnabled : !((targetView is UILabel) || (targetView is UIImageView))
        case (let .translatesAutoresizingMaskIntoConstraints(should), _):
            targetView.translatesAutoresizingMaskIntoConstraints = !useDefaultValue ? should : false
        case (let .tintColor(color), _):
            targetView.tintColor = !useDefaultValue ? color : nil
        case (let .tintAdjustmentMode(mode), _):
            targetView.tintAdjustmentMode = !useDefaultValue ? mode : .automatic
        case (let .cornerRadius(radius), _):
            targetView.layer.cornerRadius = !useDefaultValue ? radius : 0
        case (let .borderWidth(borderWidth), _):
            targetView.layer.borderWidth = !useDefaultValue ? borderWidth : 0
        case (let .borderColor(borderColor), _):
            targetView.layer.borderColor = !useDefaultValue ? borderColor.cgColor : nil
        case (let .multipleTouchEnabled(multipleTouchEnabled), _):
            targetView.isMultipleTouchEnabled = !useDefaultValue ? multipleTouchEnabled : false
        case (let .exclusiveTouch(exclusiveTouch), _):
            targetView.isExclusiveTouch = !useDefaultValue ? exclusiveTouch : false
        case (let .clipsToBounds(clipsToBounds), _):
            targetView.clipsToBounds = !useDefaultValue ? clipsToBounds : false
        case (let .alpha(alpha), _):
            targetView.alpha = !useDefaultValue ? alpha : 1.0
        case (let .opaque(opaque), _):
            targetView.isOpaque = !useDefaultValue ? opaque : true
        case (let .hidden(hidden), _):
            targetView.isHidden = !useDefaultValue ? hidden : false
        case (let .clearsContextBeforeDrawing(clearsContextBeforeDrawing), _):
            targetView.clearsContextBeforeDrawing = !useDefaultValue ? clearsContextBeforeDrawing : true
        case (let .contentMode(contentMode), _):
            targetView.contentMode = !useDefaultValue ? contentMode : .scaleToFill

        // UIControl
        case (let .selected(selected), let control as UIControl):
            control.isSelected = !useDefaultValue ? selected : false

        // UILabel
        case (let .shadowColor(color), let label as UILabel):
            label.shadowColor = !useDefaultValue ? color : nil
        case (let .shadowOffset(size), let label as UILabel):
            label.shadowOffset = !useDefaultValue ? size : CGSize(width: 0, height: -1)
        case (let .highlightedTextColor(color), let label as UILabel):
            label.highlightedTextColor = !useDefaultValue ? color : nil
        case (let .minimumScaleFactor(factor), let label as UILabel):
            label.minimumScaleFactor = !useDefaultValue ? factor : 0.0
        case (let .baselineAdjustment(adjustment), let label as UILabel):
            label.baselineAdjustment = !useDefaultValue ? adjustment : .alignBaselines

        // UILabel & UITextField & UITextView
        case (let .enabled(enabled), let view):
            if let label = view as? UILabel {
                label.isEnabled = !useDefaultValue ? enabled : true
            } else if let control = view as? UIControl {
                control.isEnabled = !useDefaultValue ? enabled : true
            } else {
                warningUnknownAppearance(targetView)
            }
        case (let .highlighted(highlighted), let view):
            if let label = view as? UILabel {
                label.isHighlighted = !useDefaultValue ? highlighted : false
            } else if let control = view as? UIControl {
                control.isHighlighted = !useDefaultValue ? highlighted : false
            } else {
                warningUnknownAppearance(targetView)
            }
        case (let .adjustsFontSizeToFitWidth(should), let view):
            if let label = view as? UILabel {
                label.adjustsFontSizeToFitWidth = !useDefaultValue ? should : false
            } else if let textField = view as? UITextField {
                textField.adjustsFontSizeToFitWidth = !useDefaultValue ? should : false
            } else {
                warningUnknownAppearance(targetView)
            }
        case (let .font(font), let view):
            if let label = view as? UILabel {
                label.font = !useDefaultValue ? font : nil
            } else if let textField = view as? UITextField {
                textField.font = !useDefaultValue ? font : nil
            } else if let textView = view as? UITextView {
                textView.font = !useDefaultValue ? font : nil
            } else {
                warningUnknownAppearance(targetView)
            }
        case (let .textColor(color), let view):
            if let label = view as? UILabel {
                label.textColor = !useDefaultValue ? color : nil
            } else if let textField = view as? UITextField {
                textField.textColor = !useDefaultValue ? color : nil
            } else if let textView = view as? UITextView {
                textView.textColor = !useDefaultValue ? color : nil
            } else {
                warningUnknownAppearance(targetView)
            }
        case (let .textAlignment(align), let view):
            if let label = view as? UILabel {
                label.textAlignment = !useDefaultValue ? align : .left
            } else if let textField = view as? UITextField {
                textField.textAlignment = !useDefaultValue ? align : .left
            } else if let textView = view as? UITextView {
                textView.textAlignment = !useDefaultValue ? align : .left
            } else {
                warningUnknownAppearance(targetView)
            }
        case (let .numberOfLines(number), let view):
            if let label = view as? UILabel {
                label.numberOfLines = !useDefaultValue ? number : 1
            } else if let textView = view as? UITextView {
                textView.textContainer.maximumNumberOfLines = !useDefaultValue ? number : 0
            } else {
                warningUnknownAppearance(targetView)
            }
        case (let .text(text), let view):
            if let label = view as? UILabel {
                label.text = !useDefaultValue ? text : nil
            } else if let textField = view as? UITextField {
                textField.text = !useDefaultValue ? text : nil
            } else if let textView = view as? UITextView {
                textView.text = !useDefaultValue ? text : nil
            } else {
                warningUnknownAppearance(targetView)
            }
        case (let .attributedText(attributes), let view):
            if let label = view as? UILabel {
                label.lg_setAttributedString(with: !useDefaultValue ? attributes : [])
            } else if let textField = view as? UITextField {
                textField.lg_setAttributedString(with: !useDefaultValue ? attributes : [])
            } else if let textView = view as? UITextView {
                textView.lg_setAttributedString(with: !useDefaultValue ? attributes : [])
            } else {
                warningUnknownAppearance(targetView)
            }
        case (let .lineBreakMode(mode), let view):
            if let label = view as? UILabel {
                label.lineBreakMode = !useDefaultValue ? mode : .byWordWrapping
            } else if let textView = view as? UITextView {
                textView.textContainer.lineBreakMode = !useDefaultValue ? mode : .byWordWrapping
            } else {
                warningUnknownAppearance(targetView)
            }

        case (let .allowsEditingTextAttributes(allows), let view):
            if let textField = view as? UITextField {
                textField.allowsEditingTextAttributes = !useDefaultValue ? allows : false
            } else if let textView = view as? UITextView {
                textView.allowsEditingTextAttributes = !useDefaultValue ? allows : false
            } else {
                warningUnknownAppearance(targetView)
            }

        case (let .clearsOnInsertion(clearsOnInsertion), let view):
            if let textField = view as? UITextField {
                textField.clearsOnInsertion = !useDefaultValue ? clearsOnInsertion : false
            } else if let textView = view as? UITextView {
                textView.clearsOnInsertion = !useDefaultValue ? clearsOnInsertion : false
            } else {
                warningUnknownAppearance(targetView)
            }

        // UITextView
        case (let .selectedRange(range), let textView as UITextView):
            textView.selectedRange = !useDefaultValue ? range : NSRange(location: 0, length: 0)
        case (let .editable(editable), let textView as UITextView):
            textView.isEditable = !useDefaultValue ? editable : false
        case (let .selectable(selectable), let textView as UITextView):
            textView.isSelectable = !useDefaultValue ? selectable : true
        case (let .dataDetectorTypes(types), let textView as UITextView):
            textView.dataDetectorTypes = !useDefaultValue ? types : UIDataDetectorTypes()
        case (let .textContainerInset(inset), let textView as UITextView):
            textView.textContainerInset = !useDefaultValue ? inset : UIEdgeInsets.zero
        case (let .linkTextAttributes(attrs), let textView as UITextView):
            textView.linkTextAttributes = !useDefaultValue ? attrs : nil
        case (let .lineFragmentPadding(padding), let textView as UITextView):
            textView.textContainer.lineFragmentPadding = !useDefaultValue ? padding : 5

        // UITextField
        case (let .borderStyle(style), let textField as UITextField):
            textField.borderStyle = !useDefaultValue ? style : .none
        case (let .defaultTextAttributes(attributes), let textField as UITextField):
            textField.defaultTextAttributes = !useDefaultValue ? attributes : [:]
        case (let .placeholder(text), let textField as UITextField):
            textField.placeholder = !useDefaultValue ? text : nil
        case (let .clearsOnBeginEditing(should), let textField as UITextField):
            textField.clearsOnBeginEditing = !useDefaultValue ? should : false
        case (let .background(image), let textField as UITextField):
            textField.background = !useDefaultValue ? image : nil
        case (let .disabledBackground(image), let textField as UITextField):
            textField.disabledBackground = !useDefaultValue ? image : nil
        case (let .typingAttributes(attributes), let textField as UITextField):
            textField.typingAttributes = !useDefaultValue ? attributes : nil
        case (let .clearButtonMode(mode), let textField as UITextField):
            textField.clearButtonMode = !useDefaultValue ? mode : .never

        // UIButton
        case (let .buttonType(type), _ as UIButton):
            print("For the moment, LeeGo only support default button type. Can't handle \(type)")
            break
        case (let .buttonTitle(title, state), let button as UIButton):
            button.setTitle(!useDefaultValue ? title : nil, for: state)
        case (let .buttonTitleColor(color, state), let button as UIButton):
            button.setTitleColor(!useDefaultValue ? color : nil, for: state)
        case (let .buttonTitleShadowColor(color, state), let button as UIButton):
            button.setTitleShadowColor(!useDefaultValue ? color : nil, for: state)
        case (let .buttonImage(image, state), let button as UIButton):
            button.setImage(!useDefaultValue ? image : nil, for: state)
        case (let .buttonBackgroundImage(image, state), let button as UIButton):
            button.setBackgroundImage(!useDefaultValue ? image : nil, for: state)
        case (let .buttonAttributedTitle(attributes, state), let button as UIButton):
            button.lg_setAttributedButtonTitle(with: !useDefaultValue ? attributes : [], state: state)
        case (let .contentEdgeInsets(insets), let button as UIButton):
            button.contentEdgeInsets = !useDefaultValue ? insets : UIEdgeInsets.zero
        case (let .titleEdgeInsets(insets), let button as UIButton):
            button.titleEdgeInsets = !useDefaultValue ? insets : UIEdgeInsets.zero
        case (let .imageEdgeInsets(insets), let button as UIButton):
            button.imageEdgeInsets = !useDefaultValue ? insets : UIEdgeInsets.zero
        case (let .reversesTitleShadowWhenHighlighted(should), let button as UIButton):
            button.reversesTitleShadowWhenHighlighted = !useDefaultValue ? should : false
        case (let .adjustsImageWhenHighlighted(should), let button as UIButton):
            button.adjustsImageWhenHighlighted = !useDefaultValue ? should : true
        case (let .adjustsImageWhenDisabled(should), let button as UIButton):
            button.adjustsImageWhenDisabled = !useDefaultValue ? should : true
        case (let .showsTouchWhenHighlighted(should), let button as UIButton):
            button.showsTouchWhenHighlighted = !useDefaultValue ? should : false

        // UIImageView
        case (let .ratio(ratioValue), let image as UIImageView):

            image.removeConstraints(image.constraints.filter({ (constraint) -> Bool in
                if constraint.mode == .ratio {
                    return true
                }
                return false
            }))

            if !useDefaultValue {
                let constraint = NSLayoutConstraint(item: image, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: image, attribute: NSLayoutAttribute.height, multiplier: ratioValue, constant: 0)
                constraint.lg_setIdentifier(with: .ratio)
                constraint.priority = 990
                image.addConstraint(constraint)
            }

        // UIScrollView
        case (let .scrollEnabled(scrollEnabled), let scrollView as UIScrollView):
            scrollView.isScrollEnabled = !useDefaultValue ? scrollEnabled : true
        case (let .contentOffset(offset), let scrollView as UIScrollView):
            scrollView.contentOffset = !useDefaultValue ? offset : CGPoint.zero
        case (let .contentSize(size), let scrollView as UIScrollView):
            scrollView.contentSize = !useDefaultValue ? size : CGSize.zero
        case (let .contentInset(inset), let scrollView as UIScrollView):
            scrollView.contentInset = !useDefaultValue ? inset : UIEdgeInsets.zero
        case (let .directionalLockEnabled(enabled), let scrollView as UIScrollView):
            scrollView.isDirectionalLockEnabled = !useDefaultValue ? enabled : false
        case (let .bounces(bounces), let scrollView as UIScrollView):
            scrollView.bounces = !useDefaultValue ? bounces : true
        case (let .alwaysBounceVertical(alwaysBounceVertical), let scrollView as UIScrollView):
            scrollView.alwaysBounceVertical = !useDefaultValue ? alwaysBounceVertical : false
        case (let .alwaysBounceHorizontal(alwaysBounceHorizontal), let scrollView as UIScrollView):
            scrollView.alwaysBounceHorizontal = !useDefaultValue ? alwaysBounceHorizontal : false
        case (let .pagingEnabled(pagingEnabled), let scrollView as UIScrollView):
            scrollView.isPagingEnabled = !useDefaultValue ? pagingEnabled : false
        case (let .showsHorizontalScrollIndicator(show), let scrollView as UIScrollView):
            scrollView.showsHorizontalScrollIndicator = !useDefaultValue ? show : true
        case (let .showsVerticalScrollIndicator(show), let scrollView as UIScrollView):
            scrollView.showsVerticalScrollIndicator = !useDefaultValue ? show : true
        case (let .scrollIndicatorInsets(insets), let scrollView as UIScrollView):
            scrollView.scrollIndicatorInsets = !useDefaultValue ? insets : UIEdgeInsets.zero
        case (let .indicatorStyle(style), let scrollView as UIScrollView):
            scrollView.indicatorStyle = !useDefaultValue ? style : .default
        case (let .decelerationRate(rate), let scrollView as UIScrollView):
            scrollView.decelerationRate = !useDefaultValue ? rate : UIScrollViewDecelerationRateNormal
        case (let .delaysContentTouches(delaysContentTouches), let scrollView as UIScrollView):
            scrollView.delaysContentTouches = !useDefaultValue ? delaysContentTouches : true
        case (let .canCancelContentTouches(canCancelContentTouches), let scrollView as UIScrollView):
            scrollView.canCancelContentTouches = !useDefaultValue ? canCancelContentTouches : true
        case (let .minimumZoomScale(minimumZoomScale), let scrollView as UIScrollView):
            scrollView.minimumZoomScale = !useDefaultValue ? minimumZoomScale : 1.0
        case (let .maximumZoomScale(maximumZoomScale), let scrollView as UIScrollView):
            scrollView.maximumZoomScale = !useDefaultValue ? maximumZoomScale : 1.0
        case (let .zoomScale(zoomScale), let scrollView as UIScrollView):
            scrollView.zoomScale = !useDefaultValue ? zoomScale : 1.0
        case (let .bouncesZoom(bouncesZoom), let scrollView as UIScrollView):
            scrollView.bouncesZoom = !useDefaultValue ? bouncesZoom : true
        case (let .scrollsToTop(scrollsToTop), let scrollView as UIScrollView):
            scrollView.scrollsToTop = !useDefaultValue ? scrollsToTop : true
        case (let .keyboardDismissMode(keyboardDismissMode), let scrollView as UIScrollView):
            scrollView.keyboardDismissMode = !useDefaultValue ? keyboardDismissMode :.none

        // Custom
        case (let .custom(dictionary), _):
            !useDefaultValue ? targetView.lg_setupCustomStyle(dictionary) : targetView.lg_removeCustomStyle(dictionary)

        default:
            warningUnknownAppearance(targetView)
            break
        }
    }

    private func warningUnknownAppearance<View: UIView>(_ targetView: View) {
{'fraction_non_alphanumeric': 0.08815470664160685, 'fraction_numerical': 0.0014945313738366432, 'mean_word_length': 2.883397819205065, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 15, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3802809', 'n_tokens_mistral': 11801, 'n_tokens_neox': 11450, 'n_words': 2904}