#!/usr/bin/env python

# tt2mat -- convert truth table to affiliation matrix

# Copyright (c) 2012-2014 Claude Rubinson

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License along with this program.  If not, see
# <http://www.gnu.org/licenses/>.

import argparse
import csv
import sys

#
# INITIALIZATIONS
#
progname = sys.argv[0]
__version__ = 1.0

def err(msg, file_descr=sys.stderr, signal=1):
    """General error handling routine."""
    print >> file_descr, '%s: %s' % (progname, msg)
    sys.exit(signal)

#
# PROCESS COMMAND-LINE ARGUMENTS AND OPTIONS
#

parser = argparse.ArgumentParser(description='Convert truth table to affiliation matrix.', epilog="""Optional arguments permit specification of (1) whether to exclude the truth table's outcome column and (2) which rows of the truth table to include/exclude.  Default is to include the outcome column, include rows in which the outcome is 'True' or 'False', and exclude Contradictions, Remainders, and Impossible conditions.  When including Contradictions, Remainders, or Impossibles, you must also specify whether to cast them as 0 or 1.  Invert 'True' and 'False' outcomes by passing -j or -k.""")
parser.add_argument('-v', '--version', action='version', version='%(prog)s '+str(__version__))
parser.add_argument('-y', '--input', metavar='FILE', dest='tt', default='/dev/stdin', help='if absent, or when FILE is -, read standard input')
parser.add_argument('-n', '--no-outcome', action='store_true', help='exclude outcome column')

# for nargs='?':
# const -- value if option is omitted
# default -- value if option included without argument

parser.add_argument('-c', '--con', dest='cast_con_as', choices='01', help='include Contradictions, casting outcome as N', metavar='N')
parser.add_argument('-r', '--rem', dest='cast_rem_as', choices='01', help='include Remainders, casting outcome as N', metavar='N')
parser.add_argument('-i', '--imp', dest='cast_imp_as', choices='01', help='include Impossibles, casting outcome as N', metavar='N')
parser.add_argument('-T', '--no-true', dest='include_trues', action='store_false', help='exclude rows when outcome is True')
parser.add_argument('-F', '--no-false', dest='include_falses', action='store_false', help='exclude rows when outcome is False')
parser.add_argument('-j', '--invert-true', dest='cast_true_as', action='store_const', const=0, default=1, help='when outcome is True, cast outcome as 0')
parser.add_argument('-k', '--invert-false', dest='cast_false_as', action='store_const', const=1, default=0, help='when outcome is False, cast outcome as 1')

args = parser.parse_args()
tt = '/dev/stdin' if args.tt=='-' else args.tt
no_outcome = args.no_outcome

# castings
cast_true_as = args.cast_true_as
cast_false_as = args.cast_false_as
cast_con_as = args.cast_con_as
cast_rem_as = args.cast_rem_as
cast_imp_as = args.cast_imp_as

# which rows to include?
include_rows = []
if args.include_trues:
    include_rows += ['True']
if args.include_falses:
    include_rows += ['False']
if cast_con_as in ('0','1'):
    include_rows += ['Con']
if cast_rem_as in ('0','1'):
    include_rows += ['Rem']
if cast_imp_as in (0,1):
    include_rows += ['Imp']

def cc2int(infield):
    if infield == 'True':
        out = 1
    elif infield == 'False':
        out = 0
    else:
        out = infield
    return str(out)

def outcome2int(infield, true=cast_true_as, false=cast_false_as, rem=cast_rem_as, con=cast_con_as, imp=cast_imp_as):
    if infield == 'True':
        out = true
    elif infield == 'False':
        out = false
    elif infield == 'Rem':
        out = rem
    elif infield == 'Con':
        out = con
    elif infield == 'Imp':
        out = imp
    elif infield == 'Outcome':  # header row
        out = infield
    else:  # shouldn't get here
        raise ValueError, 'bad value for outcome: %s' % infield
    return str(out)

def obs2actors(observations, delim=', ', obs_header='Obs'):
    if observations == ['ObsConsist', 'ObsInconsist']:
        if not obs_header:  # pass None to obs_header if you don't want one
            out = ' '
        else:
            out = obs_header
    elif observations == ['-', '-']:  # Remainders
        out = 'remainder'
    else:
        out = delim.join([ob for ob in observations if ob != '-'])
    return out

for i,row in enumerate(open(tt)):
    outcome = row.split()[-3]
    if i == 0 or outcome in include_rows:
        causal_conds = map(cc2int, row.split()[0:-5])
        obs = obs2actors(row.split()[-2:])
        if obs == 'remainder':
            obs = 'Row'+str(i)

        if no_outcome:
            print ' '.join([obs] + causal_conds)
        else:
            print ' '.join([obs] + causal_conds + [outcome2int(outcome)])
