import re
import StringIO
import csv
import datetime
import time
import logging

import utils
from vote_info import output_vote


def run(options):
    congress = options.get("congress", None)
    congress = int(congress) if congress else utils.current_congress()

    chamber = options.get('chamber', None)

    # we're going to need to map votes to sessions because in modern history the numbering resets by session
    session_dates = list(csv.DictReader(StringIO.StringIO(utils.download("http://www.govtrack.us/data/us/sessions.tsv").encode("utf8")), delimiter="\t"))

    # download the vote data now
    if chamber and chamber in [ "h", "s" ]:
        votes = get_votes(chamber, congress, options, session_dates)
    else:
        votes = get_votes("h", congress, options, session_dates) + get_votes("s", congress, options, session_dates)

    utils.process_set(votes, put_vote, options)


def vote_list_source_urls_for(congress, chamber, options):
    url = "http://www.voteview.com/%s%02d.htm" % (("house" if chamber == "h" else "senate"), congress)
    index_page = utils.download(url, cache_file_for(congress, chamber, "html"), options)
    if index_page == None:
        raise Exception("No data.")  # should only happen on a 404

    def match(pattern):
        matches = re.findall(pattern, index_page, re.I)
        if len(matches) != 1:
            raise ValueError("Index page %s did not match one value for pattern %s." % (url, pattern))
        return matches[0]

    return match("ftp://voteview.com/[^\.\s]+\.ord"), match("ftp://voteview.com/dtl/[^\.\s]+\.dtl")


def cache_file_for(congress, chamber, file_type):
    return "voteview/%s-%s.%s" % (congress, chamber, file_type)


def get_state_from_icpsr_state_code(icpsr_state_code):
    icpsr_state_code_map = {
        1: "CT",
        2: "ME",
        3: "MA",
        4: "NH",
        5: "RI",
        6: "VT",
        11: "DE",
        12: "NJ",
        13: "NY",
        14: "PA",
        21: "IL",
        22: "IN",
        23: "MI",
        24: "OH",
        25: "WI",
        31: "IA",
        32: "KS",
        33: "MN",
        34: "MO",
        35: "NE",
        36: "ND",
        37: "SD",
        40: "VA",
        41: "AL",
        42: "AR",
        43: "FL",
        44: "GA",
        45: "LA",
        46: "MS",
        47: "NC",
        48: "SC",
        49: "TX",
        51: "KY",
        52: "MD",
        53: "OK",
        54: "TN",
        55: "DC",
        56: "WV",
        61: "AZ",
        62: "CO",
        63: "ID",
        64: "MT",
        65: "NV",
        66: "NM",
        67: "UT",
        68: "WY",
        71: "CA",
        72: "OR",
        73: "WA",
        81: "AK",
        82: "HI",
        99: None,  # Used by presidents
    }

    return icpsr_state_code_map[icpsr_state_code]


def get_party_from_icpsr_party_code(icpsr_party_code):
    icpsr_party_code_map = {
        1: "Federalist",
        9: "Jefferson Republican",
        10: "Anti-Federalist",
        11: "Jefferson Democrat",
        13: "Democrat-Republican",
        22: "Adams",
        25: "National Republican",
        26: "Anti Masonic",
        29: "Whig",
        34: "Whig and Democrat",
        37: "Constitutional Unionist",
        40: "Anti-Democrat and States Rights",
        41: "Anti-Jackson Democrat",
        43: "Calhoun Nullifier",
        44: "Nullifier",
        46: "States Rights",
        48: "States Rights Whig",
        100: "Democrat",
        101: "Jackson Democrat",
        103: "Democrat and Anti-Mason",
        104: "Van Buren Democrat",
        105: "Conservative Democrat",
        108: "Anti-Lecompton Democrat",
        110: "Popular Sovereignty Democrat",
        112: "Conservative",
        114: "Readjuster",
        117: "Readjuster Democrat",
        118: "Tariff for Revenue Democrat",
        119: "United Democrat",
        200: "Republican",
        202: "Union Conservative",
        203: "Unconditional Unionist",
        206: "Unionist",
        208: "Liberal Republican",
        212: "United Republican",
        213: "Progressive Republican",
        214: "Non-Partisan and Republican",
        215: "War Democrat",
        300: "Free Soil",
        301: "Free Soil Democrat",
        302: "Free Soil Whig",
        304: "Anti-Slavery",
        308: "Free Soil American and Democrat",
        310: "American",
        326: "National Greenbacker",
        328: "Independent",
        329: "Ind. Democrat",
        331: "Ind. Republican",
        333: "Ind. Republican-Democrat",
        336: "Anti-Monopolist",
        337: "Anti-Monopoly Democrat",
        340: "Populist",
        341: "People's",
        347: "Prohibitionist",
        353: "Ind. Silver Republican",
        354: "Silver Republican",
        355: "Union",
        356: "Union Labor",
        370: "Progressive",
        380: "Socialist",
        401: "Fusionist",
        402: "Liberal",
        403: "Law and Order",
        522: "American Labor",
        537: "Farmer-Labor",
        555: "Jackson",
        603: "Ind. Whig",
        1060: "Silver",
        1061: "Emancipationist",
        1111: "Liberty",
        1116: "Conservative Republican",
        1275: "Anti-Jackson",
        1346: "Jackson Republican",
        3333: "Opposition",
        4000: "Anti-Administration",
        4444: "Union",
        5000: "Pro-Administration",
        6000: "Crawford F
ederalist",
        6666: "Crawford Republican",
        7000: "Jackson Federalist",
        7777: "Crawford Republican",
        8000: "Adams-Clay Federalist",
        8888: "Adams-Clay Republican",
        9000: "Unknown",
        9999: "Unknown",
    }

    return icpsr_party_code_map.get(icpsr_party_code)


def parse_icpsr_vote_string(icpsr_vote_string):

    # Convert the integer codes into a tuple containing:
    #    standard vote options "Yea", "Nay", "Not Voting", "Present"
    #    an additional string so that we don't lose any information provided by voteview
    # Probably the House used Aye and No in some votes, but we don't
    # know which. "Yea" and "Nay" are always used by the Senate, and always
    # in the House on the passage of bills.
    icpsr_vote_code_map = {
        0: None,  # not a member
        1: ("Yea", None),
        2: ("Yea", "paired"),
        3: ("Not Voting", "announced-yea"),
        4: ("Not Voting", "announced-nay"),
        5: ("Nay", "paired"),
        6: ("Nay", None),
        7: ("Present", "type-seven"),
        8: ("Present", "type-eight"),
        9: ("Not Voting", None),
    }
    return [icpsr_vote_code_map[int(icpsr_vote_code)] for icpsr_vote_code in icpsr_vote_string]


def parse_vote_list_line(vote_list_line):
    return re.match(r"^([\s\d]{2}\d)([\s\d]{4}\d)([\s\d]\d)([\s\d]{2})([^\d]+?)([\s\d]{3}\d)([\s\d])([\s\d])([^\s\d][^\d]+?(?:\d\s+)?)(\d+)$", vote_list_line).groups()


def parse_rollcall_dtl_list_line(rollcall_list_line):
    return re.match(r"^([\s\d]{3}\d)([\s\d]{4}\d)?([\s\d]\d)\s(.*?)\s*$", rollcall_list_line).groups()


def parse_rollcall_dtl_list_first_line(rollcall_dtl_first_line):
    return re.match(r"^(.{14})(.{15})(.{10})?(.+?)(?:\s{3,}\d{2,3})?$", rollcall_dtl_first_line).groups()


def parse_rollcall_dtl_date(rollcall_dtl_date):
    from datetime import datetime

    potential_date_formats = [
        "%b %d, %Y",  # JAN 1, 1900
        "%B %d, %Y",  # JANUARY 1, 1900
        "%b, %d, %Y",  # JAN, 1, 1900
        "%B, %d, %Y",  # JANUARY, 1, 1900
        "%b.%d, %Y",  # JAN.1, 1900
    ]

    # Make things easier by removing periods after month abbreviations.
    rollcall_dtl_date = rollcall_dtl_date.replace(". ", " ")

    # Make things easier by inserting spaces after commas where they are missing.
    rollcall_dtl_date = rollcall_dtl_date.replace(",1", ", 1")

    # Python doesn't consider "SEPT" a valid abbreviation for September.
    rollcall_dtl_date = rollcall_dtl_date.replace("SEPT ", "SEP ")

    parsed_date = None

    for potential_date_format in potential_date_formats:
        try:
            parsed_date = datetime.strptime(rollcall_dtl_date, potential_date_format)
        except ValueError:
            pass
        else:
            break

    formatted_date = utils.format_da