/* 
   PostgreSQL72Channel+Model.m

   Copyright (C) 1999 MDlink online service center GmbH and Helge Hess
   Copyright (C) 2000-2004 SKYRIX Software AG and Helge Hess

   Author: Helge Hess (helge.hess@opengroupware.org)
   
   This file is part of the PostgreSQL72 Adaptor Library

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this library; see the file COPYING.LIB.
   If not, write to the Free Software Foundation,
   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include "PostgreSQL72Channel.h"
#include "NSString+PostgreSQL72.h"
#include "EOAttribute+PostgreSQL72.h"
#import "common.h"

@interface EORelationship(FixMe)
- (void)addJoin:(id)_join;
@end

@implementation PostgreSQL72Channel(ModelFetching)

static BOOL debugOn = NO;

- (NSArray *)_attributesForTableName:(NSString *)_tableName {
  NSMutableArray *attributes;
  NSString       *sqlExpr;
  NSArray        *resultDescription;
  NSDictionary   *row;
  
  if (![_tableName length])
    return nil;
  
  attributes = [self->_attributesForTableName objectForKey:_tableName];
  if (attributes != nil)
    return attributes;

  sqlExpr = 
    @"SELECT a.attnum, a.attname, t.typname, a.attlen, a.attnotnull "
    @"FROM pg_class c, pg_attribute a, pg_type t "
    @"WHERE c.relname='%@' AND a.attnum>0 AND a.attrelid=c.oid AND "
    @"a.atttypid=t.oid "
    @"ORDER BY attnum;";
  sqlExpr = [NSString stringWithFormat:sqlExpr, _tableName];
  
  if (![self evaluateExpression:sqlExpr]) {
    fprintf(stderr,
	    "Could not evaluate column-describe '%s' on table '%s'\n",
	    [sqlExpr cString], [_tableName cString]);
    return nil;
  }
  
  resultDescription = [self describeResults];
  attributes = [NSMutableArray arrayWithCapacity:16];
  
  while ((row = [self fetchAttributes:resultDescription withZone:NULL])) {
      EOAttribute *attribute;
      NSString    *columnName, *externalType, *attrName;
      
      columnName   = [[row objectForKey:@"attname"] stringValue];
      attrName     = [columnName _pgModelMakeInstanceVarName];
      externalType = [[row objectForKey:@"typname"] stringValue];
    
      attribute = [[EOAttribute alloc] init];
      [attribute setColumnName:columnName];
      [attribute setName:attrName];
      [attribute setExternalType:externalType];
      [attribute loadValueClassForExternalPostgreSQLType:externalType];
      [attributes addObject:attribute];
      [attribute release]; attribute = nil;
  }
  [self->_attributesForTableName setObject:attributes forKey:_tableName];
  if (debugOn) NSLog(@"%s: got attrs: %@", __PRETTY_FUNCTION__, attributes);
  return attributes;
}

- (NSArray *)_primaryKeysNamesForTableName:(NSString *)_tableName {
  NSArray *pkNameForTableName = nil;
  NSMutableArray *primaryKeys       = nil;
  NSString       *selectExpression;
  NSArray        *resultDescription = nil;
  NSString       *columnNameKey     = nil;
  NSDictionary   *row               = nil;
  
  if ([_tableName length] == 0)
    return nil;

  pkNameForTableName =
    [self->_primaryKeysNamesForTableName objectForKey:_tableName];
  
  if (pkNameForTableName != nil)
    return pkNameForTableName;
  
  selectExpression = [NSString stringWithFormat:
                                 @"SELECT attname FROM pg_attribute WHERE "
                                 @"attrelid IN (SELECT a.indexrelid FROM "
                                 @"pg_index a, pg_class b WHERE "
                                 @"a.indexrelid = b.oid AND "
                                 @"b.relname in (SELECT indexname FROM "
                                 @"pg_indexes WHERE "
                                 @"tablename = '%@') "
                                 @"AND a.indisprimary)", _tableName];

  if (![self evaluateExpression:selectExpression])
    return nil;
    
  resultDescription = [self describeResults];
  columnNameKey = [(EOAttribute *)[resultDescription objectAtIndex:0] name];
  primaryKeys   = [NSMutableArray arrayWithCapacity:4];
    
  while ((row = [self fetchAttributes:resultDescription withZone:NULL]))
    [primaryKeys addObject:[row objectForKey:columnNameKey]];

  pkNameForTableName = primaryKeys;
  [self->_primaryKeysNamesForTableName setObject:pkNameForTableName
                                       forKey:_tableName];
  return pkNameForTableName;
}

- (NSArray *)_foreignKeysForTableName:(NSString *)_tableName {
  return nil;
}

- (EOModel *)describeModelWithTableNames:(NSArray *)_tableNames {
  NSMutableArray *buildRelShips = [NSMutableArray arrayWithCapacity:64];
  EOModel *model = AUTORELEASE([EOModel new]);
  int cnt, tc = [_tableNames count];

  for (cnt = 0; cnt < tc; cnt++) {
    NSMutableDictionary *relNamesUsed;
    NSMutableArray      *classProperties;
    NSMutableArray      *primaryKeyAttributes;
    NSString *tableName;
    NSArray  *attributes;
    NSArray  *pkeys;
    NSArray  *fkeys;
    EOEntity *entity;
    int      cnt2, ac, fkc;
    
    relNamesUsed         = [NSMutableDictionary dictionaryWithCapacity:4];
    classProperties      = [NSMutableArray arrayWithCapacity:16];
    primaryKeyAttributes = [NSMutableArray arrayWithCapacity:2];
    
    tableName  = [_tableNames objectAtIndex:cnt];
    attributes = [self _attributesForTableName:tableName];
    pkeys      = [self _primaryKeysNamesForTableName:tableName];
    fkeys      = [self _foreignKeysForTableName:tableName];
    entity     = [[EOEntity new] autorelease];
    ac         = [attributes count];
    fkc        = [fkeys      count];
    
    [entity setName:[tableName _pgModelMakeClassName]];
    [entity setClassName:
              [@"EO" stringByAppendingString:
		  [tableName _pgModelMakeClassName]]];
    [entity setExternalName:tableName];
    [classProperties addObjectsFromArray:[entity classProperties]];
    [primaryKeyAttributes addObjectsFromArray:[entity primaryKeyAttributes]];
    [model addEntity:entity];

    for (cnt2 = 0; cnt2 < ac; cnt2++) {
      EOAttribute *attribute  = [attributes objectAtIndex:cnt2];
      NSString    *columnName = [attribute columnName];

      attribute  = [attributes objectAtIndex:cnt2];
      columnName = [attribute columnName];
      
      [entity addAttribute:attribute];
      [classProperties addObject:attribute];

      if ([pkeys containsObject:columnName])
        [primaryKeyAttributes addObject:attribute];
    }
    [entity setClassProperties:classProperties];
    [entity setPrimaryKeyAttributes:primaryKeyAttributes];
    
    for (cnt2 = 0; cnt2 < fkc; cnt2++) {
      NSDictionary   *fkey;
      NSMutableArray *classProperties;
      NSString       *sa, *da, *dt;
      EORelationship *rel;
      EOJoin         *join;
      NSString       *relName = nil;
      
      fkey             = [fkeys objectAtIndex:cnt2];
      classProperties  = [NSMutableArray arrayWithCapacity:16];
      sa               = [fkey objectForKey:@"sourceAttr"];
      da               = [fkey objectForKey:@"targetAttr"];
      dt               = [fkey objectForKey:@"targetTable"];
      rel              = [[[EORelationship alloc] init] autorelease];
      join             = [[[EOJoin alloc] init] autorelease];
      
      if ([pkeys containsObject:sa])
        relName = [@"to" stringByAppendingString:[dt _pgModelMakeClassName]];
      else {
        relName = [@"to" stringByAppendingString:
                    [[sa _pgModelMakeInstanceVarName]
                         _pgStringWithCapitalizedFirstChar]];
        if ([relName hasSuffix:@"Id"]) {
          int cLength = [relName cStringLength];

          relName = [relName substringToIndex:cLength - 2];
        }
      }
      if ([relNamesUsed objectForKey:relName] != nil) {
        int useCount = [[relNamesUsed objectForKey:relName] intValue];
        
        [relNamesUsed setObject:[NSNumber numberWithInt:(useCount++)] 
		      forKey:relName];
        relName = [NSString stringWithFormat:@"%s%d",
                              [relName cString], useCount];
      }
      else
        [relNamesUsed setObject:[NSNumber numberWithInt:0] forKey:relName];
      
      [rel setName:relName];
      //[rel setDestinationEntity:(EOEntity *)[dt _pgModelMakeClassName]];
      [rel setToMany:NO];

      // TODO: EOJoin is removed, fix this ...
      [(id)join setSourceAttribute:
	     (EOAttribute *)[sa _pgModelMakeInstanceVarName]];
      [(id)join setDestinationAttribute:
	     (EOAttribute *)[da _pgModelMakeInstanceVarName]];
      [rel addJoin:join];
      
      [entity addRelationship:rel];
      [classProperties addObjectsFromArray:[entity classProperties]];
      [classProperties addObject:rel];
      [entity setClassProperties:classProperties];
      [buildRelShips addObject:rel];
    }

    [entity setAttributesUsedForLocking:[entity attributes]];
  }

  [buildRelShips makeObjectsPerformSelector:
                   @selector(replaceStringsWithObjects)];
  /*
  // make reverse relations
  {
    int cnt, rc = [buildRelShips count];

    for (cnt = 0; cnt < rc; cnt++) {
      EORelationship *rel     = [buildRelShips objectAtIndex:cnt];
      NSMutableArray *classProperties  = [NSMutableArray new];   
      EORelationship *reverse = [rel reversedRelationShip];
      EOEntity       *entity  = [rel destinationEntity];
      NSArray        *pkeys   = [entity primaryKeyAttributes];
      BOOL           isToMany = [reverse isToMany];
      EOAttribute    *sa      = [[[reverse joins] lastObject] sourceAttribute];
      NSString       *relName = nil;

      if ([pkeys containsObject:sa]
          || isToMany)
        relName = [@"to" stringByAppendingString:
                    [(EOEntity *)[reverse destinationEntity] name]];
      else {
        relName = [@"to" stringByAppendingString:
                    [[[sa name] _pgModelMakeInstanceVarName]
                          _pgStringWithCapitalizedFirstChar]];
        if ([relName hasSuffix:@"Id"]) {
          int cLength = [relName cStringLength];

          relName = [relName substringToIndex:cLength - 2];
        }
      }

      if ([entity relationshipNamed:relName]) {
        int cnt = 1;
        NSString *numName;

        numName = [NSString stringWithFormat:@"%s%d", [relName cString], cnt];
        while ([entity relationshipNamed:numName]) {
          cnt++;
          numName = [NSString stringWithFormat:@"%s%d", [relName cString], cnt];
        }

        relName = numName;
      }

      [reverse setName:relName];

      [entity addRelationship:reverse];
      
      [classProperties addObjectsFromArray:[entity classProperties]];
      [classProperties addObject:reverse];
      [entity setClassProperties:classProperties];
    }
  }
  */
  [model setAdaptorName:@"PostgreSQL72"];
  [model setAdaptorClassName:@"PostgreSQL72Adaptor"];
  [model setConnectionDictionary:
           [[adaptorContext adaptor] connectionDictionary]];
  
  return model;
}

- (NSArray *)_runSingleColumnQuery:(NSString *)_query {
  NSMutableArray *names;
  NSArray        *resultDescription;
  NSString       *attributeName;
  NSDictionary   *row;
  
  if (![self evaluateExpression:_query]) {
    fprintf(stderr, "Could not evaluate expression: '%s'\n", [_query cString]);
    return nil;
  }
  
  resultDescription = [self describeResults];
  attributeName    = [(EOAttribute *)[resultDescription objectAtIndex:0] name];
  names            = [NSMutableArray arrayWithCapacity:16];
  
  while ((row = [self fetchAttributes:resultDescription withZone:NULL]))
    [names addObject:[row objectForKey:attributeName]];
  
  return names;
}

- (NSArray *)describeTableNames {
  NSString *sql;
  
  sql = 
    @"SELECT relname "
    @"FROM pg_class "
    @"WHERE (relkind='r') AND (relname !~ '^pg_') AND "
    @"(relname !~ '^xinv[0-9]+') "
    @"ORDER BY relname";
  return [self _runSingleColumnQuery:sql];
}

- (NSArray *)describeDatabaseNames {
  return [self _runSingleColumnQuery:
		 @"SELECT datname FROM pg_database ORDER BY datname"];
}

- (NSArray *)describeUserNames {
  return [self _runSingleColumnQuery:@"SELECT usename FROM pg_user"];
}

@end /* PostgreSQL72Channel(ModelFetching) */

void __link_PostgreSQL72ChannelModel() {
  // used to force linking of object file
  __link_PostgreSQL72ChannelModel();
}
